From 459a8bd101ae8b1484886c544a377de3712d6fa0 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sun, 25 Jul 2004 01:34:59 +0000 Subject: [PATCH] fixed / added mssql support for egw and fixed some other Windows probs on the way ;-) - knowledgebase is not working: select distinct is not possible with text-columns - fudforum is not working: is does not use ADOdb and has no own db-layer for mssql - other apps seem to work, everthing needs through testing - incl. our "old" db's --- etemplate/inc/class.soetemplate.inc.php | 929 ++++++++++++ infolog/inc/class.soinfolog.inc.php | 578 ++++++++ phpgwapi/inc/class.categories.inc.php | 824 +++++++++++ phpgwapi/inc/class.db.inc.php | 32 +- phpgwapi/inc/class.preferences.inc.php | 1270 ++++++++++++++++ phpgwapi/inc/class.schema_proc.inc.php | 590 ++++++++ phpgwapi/inc/class.schema_proc_mssql.inc.php | 364 +++++ phpgwapi/inc/class.schema_proc_mysql.inc.php | 426 ++++++ phpgwapi/inc/class.schema_proc_pgsql.inc.php | 748 ++++++++++ phpgwapi/inc/class.setup.inc.php | 964 ++++++++++++ phpgwapi/inc/class.translation_sql.inc.php | 2 +- phpgwapi/inc/class.vfs_shared.inc.php | 1370 ++++++++++++++++++ setup/setup_demo.php | 183 +++ 13 files changed, 8272 insertions(+), 8 deletions(-) create mode 100644 etemplate/inc/class.soetemplate.inc.php create mode 100644 infolog/inc/class.soinfolog.inc.php create mode 100644 phpgwapi/inc/class.categories.inc.php create mode 100644 phpgwapi/inc/class.preferences.inc.php create mode 100644 phpgwapi/inc/class.schema_proc.inc.php create mode 100644 phpgwapi/inc/class.schema_proc_mssql.inc.php create mode 100644 phpgwapi/inc/class.schema_proc_mysql.inc.php create mode 100644 phpgwapi/inc/class.schema_proc_pgsql.inc.php create mode 100644 phpgwapi/inc/class.setup.inc.php create mode 100644 phpgwapi/inc/class.vfs_shared.inc.php create mode 100644 setup/setup_demo.php diff --git a/etemplate/inc/class.soetemplate.inc.php b/etemplate/inc/class.soetemplate.inc.php new file mode 100644 index 0000000000..99ee6cb5e1 --- /dev/null +++ b/etemplate/inc/class.soetemplate.inc.php @@ -0,0 +1,929 @@ + * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + \**************************************************************************/ + + /* $Id$ */ + + /*! + @class soetemplate + @author ralfbecker + @abstract Storage Objects: Everything to store and retrive the eTemplates. + @discussion eTemplates are stored in the db in table 'phpgw_etemplate' and gets distributed + @discussion through the file 'etemplates.inc.php' in the setup dir of each app. That file gets + @discussion automatically imported in the db, whenever you show a eTemplate of the app. For + @discussion performace reasons the timestamp of the file is stored in the db, so 'new' + @discussion eTemplates need to have a newer file. The distribution-file is generated with the + @discussion function dump, usually by pressing a button in the editor. + @discussion writeLangFile writes an lang-file with all Labels, incorporating an existing one. + @discussion Beside a name eTemplates use the following keys to find the most suitable template + @discussion for an user (in order of precedence): + @discussion 1) User-/Group-Id (not yet implemented) + @discussion 2) preferd languages of the user (templates for all langs have $lang='') + @discussion 3) selected template: verdilak, ... (the default is called '' in the db, not default) + @discussion 4) a version-number of the form, eg: '0.9.13.001' (filled up with 0 same size) + */ + class soetemplate + { + var $public_functions = array( + 'init' => True, + 'empty_cell' => True, + 'new_cell' => True, + 'read' => True, + 'search' => True, + 'save' => True, + 'delete' => True, + 'dump2setup' => True, + 'import_dump' => True, + 'writeLangFile' => True + ); + var $debug; // =1 show some debug-messages, = 'app.name' show messages only for eTemplate 'app.name' + var $name; // name of the template, e.g. 'infolog.edit' + var $template; // '' = default (not 'default') + var $lang; // '' if general template else language short, e.g. 'de' + var $group; // 0 = not specific else groupId or if < 0 userId + var $version; // like 0.9.13.001 + var $size; // witdh,height,border of table + var $style; // embeded CSS style-sheet + var $db,$db_name = 'phpgw_etemplate'; // DB name + var $db_key_cols = array( + 'et_name' => 'name', + 'et_template' => 'template', + 'et_lang' => 'lang', + 'et_group' => 'group', + 'et_version' => 'version' + ); + var $db_data_cols = array( + 'et_data' => 'data', + 'et_size' => 'size', + 'et_style' => 'style', + 'et_modified' => 'modified' + ); + var $db_cols; + + /*! + @function soetemplate + @abstract constructor of the class + @syntax soetemplate($name='',$template='',$lang='',$group=0,$version='',$rows=2,$cols=2) + @param as read + */ + function soetemplate($name='',$template='',$lang='',$group=0,$version='',$rows=2,$cols=2) + { + $this->db = $GLOBALS['phpgw']->db; + $this->db_cols = $this->db_key_cols + $this->db_data_cols; + + if (empty($name)) + { + $this->init($name,$template,$lang,$group,$version,$rows,$cols); + } + else + { + $this->read($name,$template,$lang,$group,$version,$rows,$cols); + } + } + + /*! + @function num2chrs + @abstract generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!) + @syntax num2chrs($num) + @param $num index to generate name from 1 => 'A' + @result the name + */ + function num2chrs($num) + { + $min = ord('A'); + $max = ord('Z') - $min + 1; + if ($num >= $max) + { + $chrs = chr(($num / $max) + $min - 1); + } + $chrs .= chr(($num % $max) + $min); + + return $chrs; + } + + /*! + @function empty_cell + @abstracts constructor for a new / empty cell (nothing fancy so far) + @syntax empty_cell() + @result the cell + */ + function empty_cell($type='label',$name='') + { + return array( + 'type' => $type, + 'name' => $name, + ); + } + + /*! + @function new_cell + @abstract constructs a new cell in a give row or the last row, not existing rows will be created + @syntax new_cell( $row=False ) + @param int $row row-number starting with 1 (!) + @param string $type type of the cell + @param string $label label for the cell + @param string $name name of the cell (index in the content-array) + @param array $attributes other attributes for the cell + @returns a reference to the new cell, use $new_cell = &$tpl->new_cell(); (!) + */ + function &new_cell($row=False,$type='label',$label='',$name='',$attributes=False) + { + $row = $row >= 0 ? intval($row) : 0; + if ($row && !isset($this->data[$row]) || !isset($this->data[1])) // new row ? + { + if (!$row) $row = 1; + + $this->data[$row] = array(); + } + if (!$row) // use last row + { + $row = count($this->data); + while (!isset($this->data[$row])) + { + --$row; + } + } + $row = &$this->data[$row]; + $col = $this->num2chrs(count($row)); + $cell = &$row[$col]; + $cell = $this->empty_cell($type,$name); + if ($label !== '') + { + $attributes['label'] = $label; + } + if (is_array($attributes)) + { + foreach($attributes as $name => $value) + { + $cell[$name] = $value; + } + } + return $cell; + } + + /*! + @function set_rows_cols() + @abstract initialises rows & cols from the size of the data-array + @syntax set_rows_cols() + */ + function set_rows_cols() + { + $this->rows = count($this->data) - 1; + $this->cols = 0; + for($r = 1; $r <= $this->rows; ++$r) + { + $cols = count($this->data[$r]); + if ($this->cols < $cols) + { + $this->cols = $cols; + } + } + } + + /*! + @function init + @abstract initialises all internal data-structures of the eTemplate and sets the keys + @syntax init($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1) + @param $name name of the eTemplate or array with the keys or all data + @param $template,$lang,$group,$version see class + @param $rows,$cols initial size of the template + */ + function init($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1) + { + reset($this->db_cols); + while (list($db_col,$col) = each($this->db_cols)) + { + $this->$col = is_array($name) ? $name[$col] : $$col; + } + if ($this->template == 'default') + { + $this->template = ''; + } + if ($this->lang == 'default') + { + $this->lang = ''; + } + $this->tpls_in_file = is_array($name) ? $name['tpls_in_file'] : 0; + + if (is_array($name) && isset($name['data'])) + { + $this->set_rows_cols(); + return; // data already set + } + $this->size = $this->style = ''; + $this->data = array(0 => array()); + $this->rows = $rows < 0 ? 1 : $rows; + $this->cols = $cols < 0 ? 1 : $cols; + for ($row = 1; $row <= $rows; ++$row) + { + for ($col = 0; $col < $cols; ++$col) + { + $this->data[$row][$this->num2chrs($col)] = $this->empty_cell(); + } + } + } + + /*! + @function read + @abstract Reads an eTemplate from the database + @syntax read($name,$template='default',$lang='default',$group=0,$version='') + @param as discripted with the class, with the following exeptions + @param $template as '' loads the prefered template 'default' loads the default one '' in the db + @param $lang as '' loads the pref. lang 'default' loads the default one '' in the db + @param $group is NOT used / implemented yet + @result True if a fitting template is found, else False + */ + function read($name,$template='default',$lang='default',$group=0,$version='') + { + $this->init($name,$template,$lang,$group,$version); + if ($this->debug == 1 || $this->debug == $this->name) + { + echo "

soetemplate::read('$this->name','$this->template','$this->lang',$this->group,'$this->version')

\n"; + } + if (($GLOBALS['phpgw_info']['server']['eTemplate-source'] == 'files' || + $GLOBALS['phpgw_info']['server']['eTemplate-source'] == 'xslt') && $this->readfile()) + { + return True; + } + if ($this->name) + { + $this->test_import($this->name); // import updates in setup-dir + } + $pref_lang = $GLOBALS['phpgw_info']['user']['preferences']['common']['lang']; + $pref_templ = $GLOBALS['phpgw_info']['server']['template_set']; + + $sql = "SELECT * FROM $this->db_name WHERE et_name='".$this->db->db_addslashes($this->name)."' AND "; + if (is_array($name)) + { + $template = $name['template']; + } + if ($template == 'default') + { + $sql .= "(et_template='".$this->db->db_addslashes($pref_templ)."' OR et_template='')"; + } + else + { + $sql .= "et_template='".$this->db->db_addslashes($this->template)."'"; + } + $sql .= ' AND '; + if (is_array($name)) + { + $lang = $name['lang']; + } + if ($lang == 'default' || $name['lang'] == 'default') + { + $sql .= "(et_lang='".$this->db->db_addslashes($pref_lang)."' OR et_lang='')"; + } + else + { + $sql .= "et_lang='".$this->db->db_addslashes($this->lang)."'"; + } + if ($this->version != '') + { + $sql .= "AND et_version='".$this->db->db_addslashes($this->version)."'"; + } + $sql .= " ORDER BY et_lang DESC,et_template DESC,et_version DESC"; + + if ($this->debug == $this->name) + { + echo "

soetemplate::read: sql='$sql'

\n"; + } + $this->db->query($sql,__LINE__,__FILE__); + if (!$this->db->next_record()) + { + $version = $this->version; + return $this->readfile() && (empty($version) || $version == $this->version); + } + $this->db2obj(); + + return True; + } + + /*! + @function readfile + @abstract Reads an eTemplate from the filesystem, the keys are already set by init in read + @syntax readfile() + @result True if a template is found, else False + */ + function readfile() + { + list($app,$name) = split("\.",$this->name,2); + $template = $this->template == '' ? 'default' : $this->template; + + if ($this->lang) + { + $lang = '.' . $this->lang; + } + $first_try = $ext = $GLOBALS['phpgw_info']['server']['eTemplate-source'] == 'xslt' ? '.xsl' : '.xet'; + + while ((!$lang || !@file_exists($file = PHPGW_SERVER_ROOT . "/$app/templates/$template/$name$lang$ext") && + !@file_exists($file = PHPGW_SERVER_ROOT . "/$app/templates/default/$name$lang$ext")) && + !@file_exists($file = PHPGW_SERVER_ROOT . "/$app/templates/$template/$name$ext") && + !@file_exists($file = PHPGW_SERVER_ROOT . "/$app/templates/default/$name$ext")) + { + if ($ext == $first_try) + { + $ext = $ext == '.xet' ? '.xsl' : '.xet'; + + if ($this->debug == 1 || $this->name != '' && $this->debug == $this->name) + { + echo "

tried '$file' now trying it with extension '$ext' !!!

\n"; + } + } + else + { + break; + } + } + if ($this->name == '' || $app == '' || $name == '' || !@file_exists($file) || !($f = @fopen($file,'r'))) + { + if ($this->debug == 1 || $this->name != '' && $this->debug == $this->name) + { + echo "

Can't open template '$this->name' / '$file' !!!

\n"; + } + return False; + } + $xml = fread ($f, filesize ($file)); + fclose($f); + + if ($ext == '.xsl') + { + $cell = $this->empty_cell(); + $cell['type'] = 'xslt'; + $cell['size'] = $this->name; + //$cell['xslt'] = &$xml; xslttemplate class cant use it direct at the moment + $cell['name'] = ''; + $this->data = array(0 => array(),1 => array('A' => &$cell)); + $this->rows = $this->cols = 1; + } + else + { + if (!is_object($this->xul_io)) + { + $this->xul_io = CreateObject('etemplate.xul_io'); + } + $loaded = $this->xul_io->import($this,$xml); + + if (!is_array($loaded)) + { + return False; + } + $this->name = $app . '.' . $name; // if template was copied or app was renamed + + $this->tpls_in_file = count($loaded); + } + return True; + } + + /*! + @function search + @syntax search($name,$template='default',$lang='default',$group=0,$version='') + @author ralfbecker + @abstract Lists the eTemplates matching the given criteria + @param as discripted with the class, with the following exeptions + @param $template as '' loads the prefered template 'default' loads the default one '' in the db + @param $lang as '' loads the pref. lang 'default' loads the default one '' in the db + @param $group is NOT used / implemented yet + @result array of arrays with the template-params + */ + function search($name,$template='default',$lang='default',$group=0,$version='') + { + if ($this->name) + { + $this->test_import($this->name); // import updates in setup-dir + } + $pref_lang = $GLOBALS['phpgw_info']['user']['preferences']['common']['lang']; + $pref_templ = $GLOBALS['phpgw_info']['server']['template_set']; + + if (is_array($name)) + { + $template = $name['template']; + $lang = $name['lang']; + $group = $name['group']; + $version = $name['version']; + $name = $name['name']; + } + $sql = "SELECT et_name,et_template,et_lang,et_group,et_version FROM $this->db_name WHERE et_name LIKE '".$this->db->db_addslashes($name)."%'"; + + if ($template != '' && $template != 'default') + { + $sql .= " AND et_template LIKE '".$this->db->db_addslashes($template)."%'"; + } + if ($lang != '' && $lang != 'default') + { + $sql .= " AND et_lang LIKE '".$this->db->db_addslashes($lang)."%'"; + } + if ($this->version != '') + { + $sql .= " AND et_version LIKE '".$this->db->db_addslashes($version)."%'"; + } + $sql .= " ORDER BY et_name DESC,et_lang DESC,et_template DESC,et_version DESC"; + + $tpl = new soetemplate; + $tpl->db->query($sql,__LINE__,__FILE__); + $result = array(); + while ($tpl->db->next_record()) + { + if ($tpl->db->f('et_lang') != '##') // exclude or import-time-stamps + { + $tpl->db2obj(); + + $result[] = $tpl->as_array(); + } + } + if ($this->debug) + { + echo "

soetemplate::search('$name') sql='$sql'

\n
\n";
+				print_r($result);
+				echo "
\n"; + } + return $result; + } + + /*! + @function db2obj + @abstract copies all cols into the obj and unserializes the data-array + @syntax db2obj() + */ + function db2obj() + { + for (reset($this->db_cols); list($db_col,$name) = each($this->db_cols); ) + { + $this->$name = $this->db->f($db_col); + } + $this->data = unserialize(stripslashes($this->data)); + if (!is_array($this->data)) $this->data = array(); + + if ($this->name[0] != '.') + { + reset($this->data); each($this->data); + while (list($row,$cols) = each($this->data)) + { + while (list($col,$cell) = each($cols)) + { + if (is_array($cell['type'])) + { + $this->data[$row][$col]['type'] = $cell['type'][0]; + //echo "corrected in $this->name cell $col$row attribute type
\n"; + } + if (is_array($cell['align'])) + { + $this->data[$row][$col]['align'] = $cell['align'][0]; + //echo "corrected in $this->name cell $col$row attribute align
\n"; + } + } + } + } + $this->set_rows_cols(); + } + + /*! + @function compress_array + @syntax compress_array( $arr ) + @author ralfbecker + @abstract to save space in the db all empty values in the array got unset + @discussion The never-'' type field ensures a cell does not disapear completely. + @discussion Calls it self recursivly for arrays / the rows + @param $arr the array to compress + @result the compressed array + */ + function compress_array($arr) + { + if (!is_array($arr)) + { + return $arr; + } + while (list($key,$val) = each($arr)) + { + if (is_array($val)) + { + $arr[$key] = $this->compress_array($val); + } + elseif ($val == '') + { + unset($arr[$key]); + } + } + return $arr; + } + + /*! + @function as_array + @abstract returns obj-data as array + @syntax as_array($data_too=0) + @param $data_too 0 = no data array, 1 = data array too, 2 = serialize data array + @result the array + */ + function as_array($data_too=0) + { + $arr = array(); + reset($this->db_cols); + while (list($db_col,$col) = each($this->db_cols)) + { + if ($col != 'data' || $data_too) + { + $arr[$col] = $this->$col; + } + } + if ($data_too == 2) + { + $arr['data'] = serialize($arr['data']); + } + if ($this->tpls_in_file) { + $arr['tpls_in_file'] = $this->tpls_in_file; + } + return $arr; + } + + /*! + @function save + @abstract saves eTemplate-object to db, can be used as saveAs by giving keys as params + @syntax save($name='',$template='.',$lang='.',$group='',$version='.') + @params keys see class + @result the number of affected rows, 1 should be ok, 0 somethings wrong + */ + function save($name='',$template='.',$lang='.',$group='',$version='.') + { + if (is_array($name)) + { + $template = $name['template']; + $lang = $name['lang']; + $group = $name['group']; + $version = $name['version']; + $name = $name['name']; + } + if ($name != '') + { + $this->name = $name; + } + if ($lang != '.') + { + $this->lang = $lang; + } + if ($template != '.') + { + $this->template = $template; + } + if ($group != '') + { + $this->group = $group; + } + if ($version != '.') + { + $this->version = $version; + } + if ($this->name == '') // name need to be set !!! + { + return False; + } + if ($this->debug > 0 || $this->debug == $this->name) + { + echo "

soetemplate::save('$this->name','$this->template','$this->lang',$this->group,'$this->version')

\n"; + } + $this->delete(); // so we have always a new insert + + if ($this->name[0] != '.') // correct up old messed up templates + { + reset($this->data); each($this->data); + while (list($row,$cols) = each($this->data)) + { + while (list($col,$cell) = each($cols)) + { + if (is_array($cell['type'])) { + $this->data[$row][$col]['type'] = $cell['type'][0]; + //echo "corrected in $this->name cell $col$row attribute type
\n"; + } + if (is_array($cell['align'])) { + $this->data[$row][$col]['align'] = $cell['align'][0]; + //echo "corrected in $this->name cell $col$row attribute align
\n"; + } + } + } + } + if (!$this->modified) + { + $this->modified = time(); + } + $data = $this->as_array(1); + $data['data'] = serialize($this->compress_array($data['data'])); + + $sql = "INSERT INTO $this->db_name ("; + foreach ($this->db_cols as $db_col => $col) + { + $sql .= $db_col . ','; + $vals .= $db_col == 'et_group' ? intval($data[$col]).',' : "'" . $this->db->db_addslashes($data[$col]) . "',"; + } + $sql[strlen($sql)-1] = ')'; + $sql .= " VALUES ($vals"; + $sql[strlen($sql)-1] = ')'; + + $this->db->query($sql,__LINE__,__FILE__); + + return $this->db->affected_rows(); + } + + /*! + @function delete + @abstract Deletes the eTemplate from the db, object itself is unchanged + @syntax delete() + @result the number of affected rows, 1 should be ok, 0 somethings wrong + */ + function delete() + { + foreach ($this->db_key_cols as $db_col => $col) + { + $vals .= ($vals ? ' AND ' : '') . $db_col . '=' . ($db_col == 'et_group' ? intval($this->$col) : "'".$this->$col."'"); + } + $this->db->query("DELETE FROM $this->db_name WHERE $vals",__LINE__,__FILE__); + + return $this->db->affected_rows(); + } + + /*! + @function dump2setup + @abstract dumps all eTemplates to /setup/etemplates.inc.php for distribution + @syntax dump2setup($app) + @param $app app- or template-name + @result the number of templates dumped as message + */ + function dump2setup($app) + { + list($app) = explode('.',$app); + + $this->db->query("SELECT * FROM $this->db_name WHERE et_name LIKE '$app%'"); + + $dir = PHPGW_SERVER_ROOT . "/$app/setup"; + if (!is_writeable($dir)) + { + return lang("Error: webserver is not allowed to write into '%1' !!!",$dir); + } + $file = "$dir/etemplates.inc.php"; + if (file_exists($file)) + { + $old_file = "$dir/etemplates.old.inc.php"; + if (file_exists($old_file)) + { + unlink($old_file); + } + rename($file,$old_file); + } + + if (!($f = fopen($file,'w'))) + { + return 0; + } + fwrite($f,"db->next_record(); ++$n) + { + $str = '$templ_data[] = array('; + for (reset($this->db_cols); list($db_col,$name) = each($this->db_cols); ) + { + $str .= "'$name' => '".$this->db->db_addslashes($this->db->f($db_col))."',"; + } + $str .= ");\n\n"; + fwrite($f,$str); + } + fclose($f); + + return lang("%1 eTemplates for Application '%2' dumped to '%3'",$n,$app,$file); + } + + function getToTranslateCell($cell,&$to_trans) + { + $strings = explode('|',$cell['help']); + + if ($cell['type'] != 'image') + { + $strings = array_merge($strings,explode('|',$cell['label'])); + } + list($extra_row) = explode(',',$cell['size']); + if (substr($cell['type'],0,6) == 'select' && !empty($extra_row) && !intval($extra_row)) + { + $strings[] = $extra_row; + } + if (!empty($cell['blur'])) + { + $strings[] = $cell['blur']; + } + foreach($strings as $str) + { + if (strlen($str) > 1 && $str[0] != '@') + { + $to_trans[trim(strtolower($str))] = $str; + } + } + } + + /*! + @function getToTranslate + @abstract extracts all texts: labels and helptexts from an eTemplate-object + @discussion some extensions use a '|' to squezze multiple texts in a label or help field + @syntax getToTranslate() + @result array with messages as key AND value + */ + function getToTranslate() + { + $to_trans = array(); + + reset($this->data); each($this->data); // skip width + while (list($row,$cols) = each($this->data)) + { + foreach($cols as $col => $cell) + { + $this->getToTranslateCell($cell,$to_trans); + + if ($cell['type'] == 'vbox' || $cell['type'] == 'hbox') + { + for ($n = 1; $n <= $cell['size']; ++$n) + { + $this->getToTranslateCell($cell[$n],$to_trans); + } + } + } + } + return $to_trans; + } + + /*! + @function getToTranslateApp + @abstract Read all eTemplates of an app an extracts the texts to an array + @syntax getToTranslateApp($app) + @param $app name of the app + @result the array with texts + */ + function getToTranslateApp($app) + { + $to_trans = array(); + + $tpls = $this->search($app); + + $tpl = new soetemplate; // to not alter our own data + + while (list(,$keys) = each($tpls)) + { + if (($keys['name'] != $last['name'] || // write only newest version + $keys['template'] != $last['template']) && + !strstr($keys['name'],'test')) + { + $tpl->read($keys); + $to_trans += $tpl->getToTranslate(); + $last = $keys; + } + } + return $to_trans; + } + + /*! + @function writeLangFile + @abstract Write new lang-file using the existing one and all text from the eTemplates + @syntax writeLangFile($app,$lang='en',$additional='') + @param $app app- or template-name + @param $lang language the messages in the template are, defaults to 'en' + @param $additional extra texts to translate, if you pass here an array with all messages and + @param select-options they get writen too (form is => ) + @result message with number of messages written (total and new) + */ + function writeLangFile($app,$lang='en',$additional='') + { + if (!$additional) + { + $addtional = array(); + } + list($app) = explode('.',$app); + + if (!file_exists(PHPGW_SERVER_ROOT.'/developer_tools/inc/class.solangfile.inc.php')) + { + $solangfile = CreateObject('etemplate.solangfile'); + } + else + { + $solangfile = CreateObject('developer_tools.solangfile'); + } + $langarr = $solangfile->load_app($app,$lang); + if (!is_array($langarr)) + { + $langarr = array(); + } + $commonarr = $solangfile->load_app('phpgwapi',$lang) + $solangfile->load_app('etemplate',$lang); + + $to_trans = $this->getToTranslateApp($app); + if (is_array($additional)) + { + //echo "writeLangFile: additional ="; _debug_array($additional); + foreach($additional as $msg) + { + $to_trans[trim(strtolower($msg))] = $msg; + } + } + unset($to_trans['']); + + for ($new = $n = 0; list($message_id,$content) = each($to_trans); ++$n) + { + if (!isset($langarr[$message_id]) && !isset($commonarr[$message_id])) + { + if (@isset($langarr[$content])) // caused by not lowercased-message_id's + { + unset($langarr[$content]); + } + $langarr[$message_id] = array( + 'message_id' => $message_id, + 'app_name' => $app, + 'content' => $content + ); + ++$new; + } + } + ksort($langarr); + + $dir = PHPGW_SERVER_ROOT . "/$app/setup"; + if (!is_writeable($dir)) + { + return lang("Error: webserver is not allowed to write into '%1' !!!",$dir); + } + $file = "$dir/phpgw_$lang.lang"; + if (file_exists($file)) + { + $old_file = "$dir/phpgw_$lang.old.lang"; + if (file_exists($old_file)) + { + unlink($old_file); + } + rename($file,$old_file); + } + $solangfile->write_file($app,$langarr,$lang); + $solangfile->loaddb($app,$lang); + + return lang("%1 (%2 new) Messages writen for Application '%3' and Languages '%4'",$n,$new,$app,$lang); + } + + /*! + @function import_dump + @abstract Imports the dump-file /$app/setup/etempplates.inc.php unconditional (!) + @syntax import_dump($app) + @param $app app name + @result message with number of templates imported + */ + function import_dump($app) + { + include($path = PHPGW_SERVER_ROOT."/$app/setup/etemplates.inc.php"); + $templ = new etemplate($app); + + for ($n = 0; isset($templ_data[$n]); ++$n) + { + for (reset($this->db_cols); list($db_col,$name) = each($this->db_cols); ) + { + $templ->$name = $templ_data[$n][$name]; + } + $templ->data = unserialize(stripslashes($templ->data)); + if (!$templ->modified) + { + $templ->modified = filemtime($path); + } + $templ->save(); + } + return lang("%1 new eTemplates imported for Application '%2'",$n,$app); + } + + /*! + @function test_import + @abstract test if new template-import necessary for app and does the import + @discussion Get called on every read of a eTemplate, caches the result in phpgw_info. + @discussion The timestamp of the last import for app gets written into the db. + @syntax test_import($app) + @param $app app- or template-name + */ + function test_import($app) // should be done from the setup-App + { + list($app) = explode('.',$app); + + if (!$app || $GLOBALS['phpgw_info']['etemplate']['import_tested'][$app]) + { + return ''; // ensure test is done only once per call and app + } + $GLOBALS['phpgw_info']['etemplate']['import_tested'][$app] = True; // need to be done before new ... + + $path = PHPGW_SERVER_ROOT."/$app/setup/etemplates.inc.php"; + + if ($time = @filemtime($path)) + { + $templ = new soetemplate(".$app",'','##'); + if ($templ->lang != '##' || $templ->modified < $time) // need to import + { + $ret = $this->import_dump($app); + $templ->modified = $time; + $templ->save(".$app",'','##'); + } + } + return $ret; + } + }; diff --git a/infolog/inc/class.soinfolog.inc.php b/infolog/inc/class.soinfolog.inc.php new file mode 100644 index 0000000000..57f0abe8b5 --- /dev/null +++ b/infolog/inc/class.soinfolog.inc.php @@ -0,0 +1,578 @@ + * + * originaly based on todo written by Joseph Engo * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + \**************************************************************************/ + + /* $Id$ */ + + /*! + @class soinfolog + @abstract storage object / db-layer for InfoLog + @author Ralf Becker + @copyright GPL - GNU General Public License + @note all values passed to this class are run either through intval or addslashes to prevent query-insertion + and for pgSql 7.3 compatibility + */ + class soinfolog // DB-Layer + { + var $db,$db2; + var $grants; + var $data = array( ); + var $user; + + /*! + @function soinfolog + @abstract constructor + */ + function soinfolog( $info_id = 0) + { + $this->db = $GLOBALS['phpgw']->db; + $this->grants = $GLOBALS['phpgw']->acl->get_grants('infolog'); + $this->user = $GLOBALS['phpgw_info']['user']['account_id']; + + $this->links = CreateObject('infolog.solink'); + + $this->tz_offset = $GLOBALS['phpgw_info']['user']['preferences']['common']['tz_offset']; + + $this->read( $info_id ); + } + + /*! + @function check_access + @abstract checks if user has the $required_rights to access $info_id (private access is handled too) + @syntax check_access( $info_id,$required_rights ) + @param $info_id Id of InfoLog entry + @param $required_rights PHPGW_ACL_xyz anded together + @returns True if access is granted else False + */ + function check_access( $info_id,$required_rights ) + { + if ($info_id != $this->data['info_id']) // already loaded? + { + // dont change our own internal data, + // dont use new as it changes $phpgw->db + $private_info = $this; + $info = $private_info->read($info_id); + } + else + { + $info = $this->data; + } + if (!$info || !$info_id) + { + return False; + } + $owner = $info['info_owner']; + + $access_ok = $owner == $this->user || // user has all rights + // ACL only on public entrys || $owner granted _PRIVATE + (!!($this->grants[$owner] & $required_rights) || + // implicite read-rights for responsible user !!! + $info['info_responsible'] == $this->user && $required_rights == PHPGW_ACL_READ) && + ($info['info_access'] == 'public' || + !!($this->grants[$owner] & PHPGW_ACL_PRIVATE)); + + //echo "

check_access(info_id=$info_id (owner=$owner, user=$user),required_rights=$required_rights): access".($access_ok?"Ok":"Denied")."

\n"; + return $access_ok; + } + + /*! + @function aclFilter + @abstract generate sql to be AND'ed into a query to ensure ACL is respected (incl. _PRIVATE) + @param $filter: none|all - list all entrys user have rights to see
+ private|own - list only his personal entrys (incl. those he is responsible for !!!) + @returns the necesary sql + */ + function aclFilter($filter = False) + { + preg_match('/(own|privat|all|none|user)([0-9]*)/',$filter_was=$filter,$vars); + $filter = $vars[1]; + $f_user = intval($vars[2]); + + if (isset($this->acl_filter[$filter.$user])) + { + return $this->acl_filter[$filter.$user]; // used cached filter if found + } + if (is_array($this->grants)) + { + foreach($this->grants as $user => $grant) + { + // echo "

grants: user=$user, grant=$grant

"; + if ($grant & (PHPGW_ACL_READ|PHPGW_ACL_EDIT)) + { + $public_user_list[] = $user; + } + if ($grant & PHPGW_ACL_PRIVATE) + { + $private_user_list[] = $user; + } + } + if (count($private_user_list)) + { + $has_private_access = 'info_owner IN ('.implode(',',$private_user_list).')'; + } + } + $filtermethod = " (info_owner=$this->user"; // user has all rights + + // implicit read-rights for responsible user + $filtermethod .= " OR (info_responsible=$this->user AND info_access='public')"; + + // private: own entries plus the one user is responsible for + if ($filter == 'private' || $filter == 'own') + { + $filtermethod .= " OR (info_responsible=$this->user OR info_status = 'offer')". + " AND (info_access='public'".($has_private_access?" OR $has_private_access":'').')'; + } + else // none --> all entrys user has rights to see + { + if ($has_private_access) + { + $filtermethod .= " OR $has_private_access"; + } + if (count($public_user_list)) + { + $filtermethod .= " OR (info_access='public' AND info_owner IN(" . implode(',',$public_user_list) . '))'; + } + } + $filtermethod .= ') '; + + if ($filter == 'user' && $f_user > 0) + { + $filtermethod = " ((info_owner=$f_user AND info_responsible=0 OR info_responsible=$f_user) AND $filtermethod)"; + } + //echo "

aclFilter(filter='$filter_was',user='$user') = '$filtermethod', privat_user_list=".print_r($privat_user_list,True).", public_user_list=".print_r($public_user_list,True)."

\n"; + return $this->acl_filter[$filter.$user] = $filtermethod; // cache the filter + } + + /*! + @function statusFilter + @abstract generate sql to filter based on the status of the log-entry + @syntax statusFilter($filter = '') + @param $filter done = done or billed, open = not ()done or billed), offer = offer + @returns the necesary sql + */ + function statusFilter($filter = '') + { + preg_match('/(done|open|offer)/',$filter,$vars); + $filter = $vars[1]; + + switch ($filter) + { + case 'done': return " AND info_status IN ('done','billed')"; + case 'open': return " AND NOT (info_status IN ('done','billed'))"; + case 'offer': return " AND info_status = 'offer'"; + } + return ''; + } + + /*! + @function dateFilter + @abstract generate sql to filter based on the start- and enddate of the log-entry + @syntax dateFilter($filter = '') + @param $filter upcoming = startdate is in the future
+ today startdate < tomorrow
+ overdue enddate < tomorrow
+ limitYYYY/MM/DD not older or open + @returns the necesary sql + */ + function dateFilter($filter = '') + { + preg_match('/(upcoming|today|overdue|date)([-\\/.0-9]*)/',$filter,$vars); + $filter = $vars[1]; + + if (isset($vars[2]) && !empty($vars[2]) && ($date = split('[-/.]',$vars[2]))) + { + $today = mktime(-$this->tz_offset,0,0,intval($date[1]),intval($date[2]),intval($date[0])); + $tomorrow = mktime(-$this->tz_offset,0,0,intval($date[1]),intval($date[2])+1,intval($date[0])); + } + else + { + $now = getdate(time()-60*60*$this->tz_offset); + $tomorrow = mktime(-$this->tz_offset,0,0,$now['mon'],$now['mday']+1,$now['year']); + } + switch ($filter) + { + case 'upcoming': + return " AND info_startdate >= '$tomorrow'"; + case 'today': + return " AND info_startdate < '$tomorrow'"; + case 'overdue': + return " AND (info_enddate != 0 AND info_enddate < '$tomorrow')"; + case 'date': + if (!$today || !$tomorrow) + { + return ''; + } + return " AND ($today <= info_startdate AND info_startdate < $tomorrow)"; + case 'limit': + return " AND (info_modified >= '$today' OR NOT (info_status IN ('done','billed')))"; + } + return ''; + } + + /*! + @function init + @abstract initialise the internal $this->data to be empty + @discussion only non-empty values got initialised + */ + function init() + { + $this->data = array( + 'info_owner' => $this->user, + 'info_pri' => 'normal' + ); + } + + /*! + @function db2data + @abstract copy data after a query into $data + @syntax db2data(&$data) + @param $data array to copy the data + @description copy only non-numeric keys + */ + function db2data(&$data) + { + $data = array(); + foreach ($this->db->Record as $key => $val) + { + if (!is_numeric($key)) + { + $data[$key] = $val; + } + } + } + + /*! + @function read + @abstract read InfoLog entry $info_id + @syntax read( $info_id ) + @param $info_id id of log-entry + @description some cacheing is done to prevent multiple reads of the same entry + @returns the entry as array + */ + function read($info_id) // did _not_ ensure ACL + { + $info_id = intval($info_id); + + if ($info_id <= 0 || $info_id != $this->data['info_id'] && + (!$this->db->query("select * FROM phpgw_infolog WHERE info_id=$info_id",__LINE__,__FILE__) || + !$this->db->next_record())) + { + $this->init( ); + return False; + } + if ($info_id != $this->data['info_id']) // data yet read in + { + $this->db2data($this->data); + + $this->db->query("SELECT info_extra_name,info_extra_value FROM phpgw_infolog_extra WHERE info_id=$info_id",__LINE__,__FILE__); + while ($this->db->next_record()) + { + $this->data['#'.$this->db->f(0)] = $this->db->f(1); + } + } + return $this->data; + } + + /*! + @function delete + @abstract delete InfoLog entry $info_id AND the links to it + @syntax delete( $info_id ) + @param int $info_id id of log-entry + @param bool $delete_children delete the children, if not set there parent-id to $new_parent + @param int new_parent new parent-id to set for subs + */ + function delete($info_id,$delete_children=True,$new_parent=0) // did _not_ ensure ACL + { + //echo "

soinfolog::delete($info_id,'$delete_children',$new_parent)

\n"; + if (($info_id = intval($info_id)) <= 0) + { + return; + } + $this->db->query("DELETE FROM phpgw_infolog WHERE info_id=$info_id",__LINE__,__FILE__); + $this->db->query("DELETE FROM phpgw_infolog_extra WHERE info_id=$info_id"); + $this->links->unlink(0,'infolog',$info_id); + + if ($this->data['info_id'] == $info_id) + { + $this->init( ); + } + // delete children, if they are owned by the user + if ($delete_children) + { + $db2 = $this->db; // we need an extra result-set + $db2->query("SELECT info_id FROM phpgw_infolog WHERE info_id_parent=$info_id AND info_owner=$this->user",__LINE__,__FILE__); + while ($db2->next_record()) + { + $this->delete($db2->f(0),$delete_children); + } + } + // set parent_id to $new_parent for all not deleted children + $new_parent = intval($new_parent); + $this->db->query("UPDATE phpgw_infolog SET info_id_parent=$new_parent WHERE info_id_parent=$info_id",__LINE__,__FILE__); + } + + /*! + @function change_delete_owner + @abstract changes or deletes entries with a spezified owner (for hook_delete_account) + @syntax change_delete_owner( $owner,$new_owner=0 ) + @param $owner old owner + @param $new_owner new owner or 0 if entries should be deleted + */ + function change_delete_owner($owner,$new_owner=0) // new_owner=0 means delete + { + $owner = intval($owner); + if (!($new_owner = intval($new_owner))) + { + $db2 = $this->db; // we need an extra result-set + $db2->query("SELECT info_id FROM phpgw_infolog WHERE info_owner=$owner",__LINE__,__FILE__); + while($db2->next_record()) + { + $this->delete($this->db->f(0),False); + } + } + else + { + $this->db->query("UPDATE phpgw_infolog SET info_owner=$new_owner WHERE info_owner=$owner",__LINE__,__FILE__); + } + $this->db->query("UPDATE phpgw_infolog SET info_responsible=$new_owner WHERE info_responsible=$owner",__LINE__,__FILE__); + } + + /*! + @function write + @abstract writes the given $values to InfoLog, a new entry gets created if info_id is not set or 0 + @syntax write( $values ) + @param $values array with the data of the log-entry + @return the info_id + */ + function write($values) // did _not_ ensure ACL + { + include(PHPGW_SERVER_ROOT.'/infolog/setup/tables_current.inc.php'); + $db_cols = $phpgw_baseline['phpgw_infolog']['fd']; + unset($phpgw_baseline); + + $info_id = intval($values['info_id']) > 0 ? intval($values['info_id']) : 0; + + foreach($values as $key => $val) + { + if ($key != 'info_id') + { + if (!isset($db_cols[$key])) + { + continue; // not in infolog-table + } + $this->data[$key] = $val; // update internal data + + switch($db_cols[$key]['type']) // protection against query-insertion + { + case 'int': case 'auto': + $val = intval($val); + break; + default: + $val = "'".$this->db->db_addslashes($val)."'"; + break; + } + $cols .= (strlen($cols) ? ',' : '').$key; + $vals .= (strlen($vals) ? ',' : '').$val; + $query .= (strlen($query) ? ',' : '')."$key=$val"; + } + } + if (($this->data['info_id'] = $info_id)) + { + $query = "UPDATE phpgw_infolog SET $query WHERE info_id=$info_id"; + $this->db->query($query,__LINE__,__FILE__); + } + else + { + $query = "INSERT INTO phpgw_infolog ($cols) VALUES ($vals)"; + $this->db->query($query,__LINE__,__FILE__); + $this->data['info_id']=$this->db->get_last_insert_id('phpgw_infolog','info_id'); + } + //echo "

soinfolog.write values= "; _debug_array($values); + + // write customfields now + $existing = array(); + if ($info_id) // existing entry + { + $this->db->query("SELECT info_extra_name FROM phpgw_infolog_extra WHERE info_id=$info_id",__LINE__,__FILE__); + while($this->db->next_record()) + { + $existing[strtolower($this->db->f(0))] = True; + } + } + foreach($values as $key => $val) + { + if ($key[0] != '#') + { + continue; // no customfield + } + $this->data[$key] = $val; // update internal data + + $val = $this->db->db_addslashes($val); + $name = $this->db->db_addslashes($key = substr($key,1)); + if ($existing[strtolower($key)]) + { + $query = "UPDATE phpgw_infolog_extra SET info_extra_value='$val' WHERE info_id=$info_id AND info_extra_name='$name'"; + } + else + { + $query = "INSERT INTO phpgw_infolog_extra (info_id,info_extra_name,info_extra_value) VALUES (".$this->data['info_id'].",'$name','$val')"; + } + $this->db->query($query,__LINE__,__FILE__); + } + // echo "

soinfolog.write this->data= "; _debug_array($this->data); + + return $this->data['info_id']; + } + + /*! + @function anzSubs + @abstract count the sub-entries of $info_id + @syntax anzSubs( $info_id ) + @param $info_id id of log-entry + @returns the number of sub-entries + */ + function anzSubs( $info_id ) + { + if (($info_id = intval($info_id)) <= 0) + { + return 0; + } + $this->db->query($sql="select count(*) FROM phpgw_infolog WHERE info_id_parent=$info_id AND ".$this->aclFilter(),__LINE__,__FILE__); + + $this->db->next_record(); + //echo "

anzSubs($info_id) = ".$this->db->f(0)." ($sql)

\n"; + return $this->db->f(0); + } + + /*! + @function search + @abstract searches InfoLog for a certain pattern in $query + @syntax search( $query ) + @param $query[order] column-name to sort after + @param $query[sort] sort-order DESC or ASC + @param $query[filter] string with combination of acl-, date- and status-filters, eg. 'own-open-today' or '' + @param $query[cat_id] category to use or 0 or unset + @param $query[search] pattern to search, search is done in info_from, info_subject and info_des + @param $query[action] / $query[action_id] if only entries linked to a specified app/entry show be used + @param &$query[start], &$query[total] nextmatch-parameters will be used and set if query returns less entries + @param $query[col_filter] array with column-name - data pairs, data == '' means no filter (!) + @returns array with id's as key of the matching log-entries + */ + function search(&$query) + { + //echo "

soinfolog.search(".print_r($query,True).")

\n"; + $action2app = array( + 'addr' => 'addressbook', + 'proj' => 'projects', + 'event' => 'calendar' + ); + $action = isset($action2app[$query['action']]) ? $action2app[$query['action']] : $query['action']; + + if ($action != '') + { + $links = $this->links->get_links($action=='sp'?'infolog':$action,$query['action_id'],'infolog'); + + if (count($links)) + { + $link_extra = ($action == 'sp' ? 'OR' : 'AND').' phpgw_infolog.info_id IN ('.implode(',',$links).')'; + } + } + if (!empty($query['order']) && eregi('^[a-z_0-9, ]+$',$query['order']) && (empty($query['sort']) || eregi('^(DESC|ASC)$',$query['sort']))) + { + $order = array(); + foreach(explode(',',$query['order']) as $val) + { + $val = trim($val); + $val = (substr($val,0,5) != 'info_' ? 'info_' : '').$val; + if ($val == 'info_des' && $this->db->Type == 'mssql') + { + $val = "CAST($val AS varchar)"; + } + $order[] = $val; + } + $ordermethod = 'ORDER BY ' . implode(',',$order) . ' ' . $query['sort']; + } + else + { + $ordermethod = 'ORDER BY info_datemodified DESC'; // newest first + } + $filtermethod = $this->aclFilter($query['filter']); + $filtermethod .= $this->statusFilter($query['filter']); + $filtermethod .= $this->dateFilter($query['filter']); + + if (is_array($query['col_filter'])) + { + foreach($query['col_filter'] as $col => $data) + { + $data = $this->db->db_addslashes($data); + if (substr($col,0,5) != 'info_') $col = 'info_'.$col; + if (!empty($data) && eregi('^[a-z_0-9]+$',$col)) + { + $filtermethod .= " AND $col = '$data'"; + } + } + } + //echo "

filtermethod='$filtermethod'

"; + + if ((int)$query['cat_id']) + { + //$filtermethod .= ' AND info_cat='.intval($query['cat_id']).' '; + if (!is_object($GLOBALS['phpgw']->categories)) + { + $GLOBALS['phpgw']->categories = CreateObject('phpgwapi.categories'); + } + $cats = $GLOBALS['phpgw']->categories->return_all_children((int)$query['cat_id']); + $filtermethod .= ' AND info_cat'.(count($cats)>1? ' IN ('.implode(',',$cats).') ' : '='.(int)$query['cat_id']); + } + $join = ''; + if ($query['query']) $query['search'] = $query['query']; // allow both names + if ($query['search']) // we search in _from, _subject, _des and _extra_value for $query + { + $pattern = "'%".$this->db->db_addslashes($query['search'])."%'"; + $sql_query = "AND (info_from like $pattern OR info_subject LIKE $pattern OR info_des LIKE $pattern OR info_extra_value LIKE $pattern) "; + $join = 'LEFT JOIN phpgw_infolog_extra ON phpgw_infolog.info_id=phpgw_infolog_extra.info_id'; + } + $pid = 'AND info_id_parent='.($action == 'sp' ? $query['action_id'] : 0); + + if (!$GLOBALS['phpgw_info']['user']['preferences']['infolog']['listNoSubs'] && + $action != 'sp') + { + $pid = ''; + } + $ids = array( ); + if ($action == '' || $action == 'sp' || count($links)) + { + $sql_query = "FROM phpgw_infolog $join WHERE ($filtermethod $pid $sql_query) $link_extra"; + // mssql cant use DISTICT of text columns (info_des) are involved + $distinct = $this->db->Type != 'mssql' ? 'DISTINCT' : ''; + $this->db->query($sql="SELECT $distinct phpgw_infolog.info_id ".$sql_query,__LINE__,__FILE__); + $query['total'] = $this->db->num_rows(); + + if (!$query['start'] || $query['start'] > $query['total']) + { + $query['start'] = 0; + } + $this->db->limit_query($sql="SELECT $distinct phpgw_infolog.* $sql_query $ordermethod",$query['start'],__LINE__,__FILE__); + //echo "

sql='$sql'

\n"; + while ($this->db->next_record()) + { + $this->db2data($info); + $ids[$info['info_id']] = $info; + } + } + else + { + $query['start'] = $query['total'] = 0; + } + return $ids; + } + } diff --git a/phpgwapi/inc/class.categories.inc.php b/phpgwapi/inc/class.categories.inc.php new file mode 100644 index 0000000000..53620561df --- /dev/null +++ b/phpgwapi/inc/class.categories.inc.php @@ -0,0 +1,824 @@ + * + * and Bettina Gille [ceb@phpgroupware.org] * + * Category manager * + * Copyright (C) 2000, 2001 Joseph Engo, Bettina Gille * + * Copyright (C) 2002, 2003 Bettina Gille * + * ------------------------------------------------------------------------ * + * This library is part of the eGroupWare API * + * http://www.egroupware.org * + * ------------------------------------------------------------------------ * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by * + * the Free Software Foundation; either version 2.1 of the License, * + * or any later version. * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU Lesser General Public License for more details. * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + \**************************************************************************/ + // $Id$ + // $Source$ + + /*! + @class categories + @abstract class adds ability for applications to make use of categories + @discussion examples can be found in notes app + */ + class categories + { + var $account_id; + var $app_name; + var $cats; + var $db; + var $total_records; + var $grants; + + /*! + @function categories + @abstract constructor for categories class + @param $accountid account id + @param $app_name app name defaults to current app + */ + function categories($accountid = '',$app_name = '') + { + $account_id = get_account_id($accountid); + + if (! $app_name) + { + $app_name = $GLOBALS['phpgw_info']['flags']['currentapp']; + } + + $this->account_id = $account_id; + $this->app_name = $GLOBALS['phpgw']->db->db_addslashes($app_name); + $this->db = $GLOBALS['phpgw']->db; + $this->db2 = $this->db; + $this->grants = $GLOBALS['phpgw']->acl->get_grants($app_name); + } + + /*! + @function filter + @abstract ? + @param $type string + @result string either subs or mains + */ + function filter($type) + { + switch ($type) + { + case 'subs': $s = ' AND cat_parent != 0'; break; + case 'mains': $s = ' AND cat_parent = 0'; break; + case 'appandmains': $s = " AND cat_appname='" . $this->app_name . "' AND cat_parent =0"; break; + case 'appandsubs': $s = " AND cat_appname='" . $this->app_name . "' AND cat_parent !=0"; break; + case 'noglobal': $s = " AND cat_appname != '" . $this->app_name . "'"; break; + case 'noglobalapp': $s = " AND cat_appname = '" . $this->app_name . "' AND cat_owner != " . $this->account_id; break; + default: return False; + } + return $s; + } + + /*! + @function total + @abstract returns the total number of categories for app, subs or mains + @param $for one of either 'app' 'subs' or 'mains' + @result integer count of categories + */ + function total($for = 'app') + { + switch($for) + { + case 'app': $w = " WHERE cat_appname='" . $this->app_name . "'"; break; + case 'appandmains': $w = " WHERE cat_appname='" . $this->app_name . "' AND cat_parent =0"; break; + case 'appandsubs': $w = " WHERE cat_appname='" . $this->app_name . "' AND cat_parent !=0"; break; + case 'subs': $w = ' WHERE cat_parent != 0'; break; + case 'mains': $w = ' WHERE cat_parent = 0'; break; + default: return False; + } + + $this->db->query("SELECT COUNT(cat_id) FROM phpgw_categories $w",__LINE__,__FILE__); + $this->db->next_record(); + + return $this->db->f(0); + } + + /*! + @funtion return_all_children + @abstract returns array with id's of all children from $cat_id and $cat_id itself! + @param $cat_id integer cat-id to search for + @returns array of cat-id's + */ + function return_all_children($cat_id) + { + $all_children = array($cat_id); + + $children = $this->return_array('subs',0,False,'','','',True,$cat_id,-1,'id'); + if (is_array($children) && count($children)) + { + foreach($children as $child) + { + $all_children = array_merge($all_children,$this->return_all_children($child['id'])); + } + } + //echo "

categories::return_all_children($cat_id)=(".implode(',',$all_children).")

\n"; + return $all_children; + } + + /*! + @function return_array + @abstract return an array populated with categories + @param $type string defaults to 'all' + @param $start ? + @param $limit ? + @param $query string defaults to '' + @param $sort string sort order, either defaults to 'ASC' + @param $order order by + @param $globals True or False, includes the global egroupware categories or not + @result $cats array + */ + function return_array($type,$start,$limit = True,$query = '',$sort = '',$order = '',$globals = False, $parent_id = '', $lastmod = -1, $column = '') + { + //casting and addslashes for security + $start = (int)$start; + $parent_id = (int)$parent_id; + $query = $this->db->db_addslashes($query); + $sort = $this->db->db_addslashes($sort); + $order = $this->db->db_addslashes($order); + + if ($globals) + { + $global_cats = " OR cat_appname='phpgw'"; + } + + $filter = $this->filter($type); + + if (!$sort) + { + $sort = 'ASC'; + } + + if (!empty($order) && preg_match('/^[a-zA-Z_(), ]+$/',$order) && (empty($sort) || preg_match('/^(ASC|DESC|asc|desc)$/',$sort))) + { + $ordermethod = " ORDER BY $order $sort"; + } + else + { + $ordermethod = ' ORDER BY cat_main, cat_level, cat_name ASC'; + } + + if ($this->account_id == '-1') + { + $grant_cats = ' cat_owner=-1 '; + } + else + { + if (is_array($this->grants)) + { + $grants = $this->grants; + while(list($user) = each($grants)) + { + $public_user_list[] = $user; + } + reset($public_user_list); + $grant_cats = ' (cat_owner=' . $this->account_id . " OR cat_owner=-1 OR cat_access='public' AND cat_owner in(" . implode(',',$public_user_list) . ')) '; + } + else + { + $grant_cats = ' cat_owner=' . $this->account_id . ' OR cat_owner=-1 '; + } + } + + if ($parent_id > 0) + { + $parent_filter = ' AND cat_parent=' . $parent_id; + } + + if ($query) + { + $querymethod = " AND (cat_name LIKE '%$query%' OR cat_description LIKE '%$query%') "; + } + + if($lastmod && $lastmod >= 0) + { + $querymethod .= ' AND last_mod > ' . (int)$lastmod; + } + + if($column) + { + switch($column) + { + case 'id': $table_column = ' cat_id '; break; + case 'owner': $table_column = ' cat_owner '; break; + case 'access': $table_column = ' cat_access '; break; + case 'app_name': $table_column = ' cat_appname '; break; + case 'main': $table_column = ' cat_main '; break; + case 'parent': $table_column = ' cat_parent '; break; + case 'name': $table_column = ' cat_name '; break; + case 'description': $table_column = ' cat_description '; break; + case 'data': $table_column = ' cat_data '; break; + case 'last_mod': $table_column = ' last_mod '; break; + default: $table_column = ' cat_id '; break; + } + } + else + { + $table_column = ' * '; + } + + $sql = "SELECT $table_column FROM phpgw_categories WHERE (cat_appname='" . $this->app_name . "' AND" . $grant_cats . $global_cats . ')' + . $parent_filter . $querymethod . $filter; + + $this->db2->query($sql,__LINE__,__FILE__); + $this->total_records = $this->db2->num_rows(); + + if ($limit) + { + $this->db->limit_query($sql . $ordermethod,$start,__LINE__,__FILE__); + } + else + { + $this->db->query($sql . $ordermethod,__LINE__,__FILE__); + } + + while ($this->db->next_record()) + { + if ($column) + { + $cats[] = array + ( + $column => $this->db->f(0) + ); + } + else + { + $cats[] = array + ( + 'id' => $this->db->f('cat_id'), + 'owner' => $this->db->f('cat_owner'), + 'access' => $this->db->f('cat_access'), + 'app_name' => $this->db->f('cat_appname'), + 'main' => $this->db->f('cat_main'), + 'level' => $this->db->f('cat_level'), + 'parent' => $this->db->f('cat_parent'), + 'name' => $this->db->f('cat_name'), + 'description' => $this->db->f('cat_description'), + 'data' => $this->db->f('cat_data'), + 'last_mod' => $this->db->f('last_mod') + ); + } + } + return $cats; + } + + function return_sorted_array($start,$limit = True,$query = '',$sort = '',$order = '',$globals = False, $parent_id = '') + { + //casting and slashes for security + $start = (int)$start; + $query = $this->db->db_addslashes($query); + $sort = $this->db->db_addslashes($sort); + $order = $this->db->db_addslashes($order); + $parent_id = (int)$parent_id; + + if ($globals) + { + $global_cats = " OR cat_appname='phpgw'"; + } + + if (!$sort) + { + $sort = 'ASC'; + } + + if (!empty($order) && preg_match('/^[a-zA-Z_, ]+$/',$order) && (empty($sort) || preg_match('/^(ASC|DESC|asc|desc)$/'))) + { + $ordermethod = " ORDER BY $order $sort"; + } + else + { + $ordermethod = ' ORDER BY cat_name ASC'; + } + + if ($this->account_id == '-1') + { + $grant_cats = " cat_owner='-1' "; + } + else + { + if (is_array($this->grants)) + { + $grants = $this->grants; + while(list($user) = each($grants)) + { + $public_user_list[] = $user; + } + reset($public_user_list); + $grant_cats = " (cat_owner='" . $this->account_id . "' OR cat_owner='-1' OR cat_access='public' AND cat_owner in(" . implode(',',$public_user_list) . ")) "; + } + else + { + $grant_cats = " cat_owner='" . $this->account_id . "' or cat_owner='-1' "; + } + } + + $parent_select = ' AND cat_parent=' . $parent_id; + + if ($query) + { + $querymethod = " AND (cat_name LIKE '%$query%' OR cat_description LIKE '%$query%') "; + } + + $sql = "SELECT * FROM phpgw_categories WHERE (cat_appname='" . $this->app_name . "' AND" . $grant_cats . $global_cats . ")" + . $querymethod; + + $this->db2->query($sql . $parent_select,__LINE__,__FILE__); + $total = $this->db2->num_rows(); + + if ($limit) + { + $this->db->limit_query($sql . $parent_select . $ordermethod,$start,__LINE__,__FILE__); + } + else + { + $this->db->query($sql . $parent_select . $ordermethod,__LINE__,__FILE__); + } + + $i = 0; + while ($this->db->next_record()) + { + $cats[$i]['id'] = (int)$this->db->f('cat_id'); + $cats[$i]['owner'] = (int)$this->db->f('cat_owner'); + $cats[$i]['access'] = $this->db->f('cat_access'); + $cats[$i]['app_name'] = $this->db->f('cat_appname'); + $cats[$i]['main'] = (int)$this->db->f('cat_main'); + $cats[$i]['level'] = (int)$this->db->f('cat_level'); + $cats[$i]['parent'] = (int)$this->db->f('cat_parent'); + $cats[$i]['name'] = $this->db->f('cat_name'); + $cats[$i]['description'] = $this->db->f('cat_description'); + $cats[$i]['data'] = $this->db->f('cat_data'); + $i++; + } + + $num_cats = count($cats); + for ($i=0;$i < $num_cats;$i++) + { + $sub_select = ' AND cat_parent=' . $cats[$i]['id'] . ' AND cat_level=' . ($cats[$i]['level']+1); + + /*$this->db2->query($sql . $sub_select,__LINE__,__FILE__); + $total_subs += $this->db2->num_rows(); + + if ($limit) + { + $this->db->limit_query($sql . $sub_select . $ordermethod,$start,__LINE__,__FILE__); + } + else + {*/ + $this->db->query($sql . $sub_select . $ordermethod,__LINE__,__FILE__); + $total += $this->db->num_rows(); + //} + + $subcats = array(); + $j = 0; + while ($this->db->next_record()) + { + $subcats[$j]['id'] = (int)$this->db->f('cat_id'); + $subcats[$j]['owner'] = (int)$this->db->f('cat_owner'); + $subcats[$j]['access'] = $this->db->f('cat_access'); + $subcats[$j]['app_name'] = $this->db->f('cat_appname'); + $subcats[$j]['main'] = (int)$this->db->f('cat_main'); + $subcats[$j]['level'] = (int)$this->db->f('cat_level'); + $subcats[$j]['parent'] = (int)$this->db->f('cat_parent'); + $subcats[$j]['name'] = $this->db->f('cat_name'); + $subcats[$j]['description'] = $this->db->f('cat_description'); + $subcats[$j]['data'] = $this->db->f('cat_data'); + $j++; + } + + $num_subcats = count($subcats); + if ($num_subcats != 0) + { + $newcats = array(); + for ($k = 0; $k <= $i; $k++) + { + $newcats[$k] = $cats[$k]; + } + for ($k = 0; $k < $num_subcats; $k++) + { + $newcats[$k+$i+1] = $subcats[$k]; + } + for ($k = $i+1; $k < $num_cats; $k++) + { + $newcats[$k+$num_subcats] = $cats[$k]; + } + $cats = $newcats; + $num_cats = count($cats); + } + } + $this->total_records = $total; + return $cats; + } + + /*! + @function return_single + @abstract return single + @param $id integer id of category + @result $cats array populated with + */ + function return_single($id = '') + { + $this->db->query('SELECT * FROM phpgw_categories WHERE cat_id=' . (int)$id,__LINE__,__FILE__); + + if ($this->db->next_record()) + { + $cats[0]['id'] = $this->db->f('cat_id'); + $cats[0]['owner'] = $this->db->f('cat_owner'); + $cats[0]['access'] = $this->db->f('cat_access'); + $cats[0]['app_name'] = $this->db->f('cat_appname'); + $cats[0]['main'] = $this->db->f('cat_main'); + $cats[0]['level'] = $this->db->f('cat_level'); + $cats[0]['parent'] = $this->db->f('cat_parent'); + $cats[0]['name'] = $this->db->f('cat_name'); + $cats[0]['description'] = $this->db->f('cat_description'); + $cats[0]['data'] = $this->db->f('cat_data'); + } + return $cats; + } + + /*! + @function formated_list + @abstract return into a select box, list or other formats + @param $format currently supports select (select box) or list + @param $type string - subs or mains + @param $selected - cat_id or array with cat_id values + @param $globals True or False, includes the global egroupware categories or not + @result $s array - populated with categories + */ + function formatted_list($format,$type='',$selected = '',$globals = False,$site_link = 'site') + { + return $this->formated_list($format,$type,$selected,$globals,$site_link); + } + function formated_list($format,$type='',$selected = '',$globals = False,$site_link = 'site') + { + if(is_array($format)) + { + $temp_format = $format['format']; + $type = ($format['type']?$format['type']:'all'); + $selected = (isset($format['selected'])?$format['selected']:''); + $self = (isset($format['self'])?$format['self']:''); + $globals = (isset($format['globals'])?$format['globals']:True); + $site_link = (isset($format['site_link'])?$format['site_link']:'site'); + settype($format,'string'); + $format = ($temp_format?$temp_format:'select'); + unset($temp_format); + } + + if (!is_array($selected)) + { + $selected = explode(',',$selected); + } + + if ($type != 'all') + { + $cats = $this->return_array($type,$start,False,$query,$sort,$order,$globals); + } + else + { + $cats = $this->return_sorted_array($start,False,$query,$sort,$order,$globals); + } + + if($self) + { + for ($i=0;$istrip_html($cat['name']); + if ($cat['app_name'] == 'phpgw') + { + $s .= ' <' . lang('Global') . '>'; + } + if ($cat['owner'] == '-1') + { + $s .= ' <' . lang('Global') . ' ' . lang($this->app_name) . '>'; + } + $s .= '' . "\n"; + } + return $s; + } + + if ($format == 'list') + { + $space = '  '; + + $s = '' . "\n"; + + if ($this->total_records > 0) + { + for ($i=0;$i'; + } + + if (($cats[$i]['level'] == 0) && !in_array($cats[$i]['id'],$selected)) + { + $image_set = ''; + } + + $space_set = str_repeat($space,$cats[$i]['level']); + + $s .= '' . "\n"; + $s .= '' . "\n"; + $s .= '' . "\n" + . '' . "\n"; + } + } + $s .= '
' . $image_set . '' . $space_set . '' + . $GLOBALS['phpgw']->strip_html($cats[$i]['name']) + . '
' . "\n"; + return $s; + } + } + + /*! + @function add + @abstract add categories + @param $cat_name category name + @param $cat_parent category parent + @param $cat_description category description defaults to '' + @param $cat_data category data defaults to '' + */ + function add($values) + { + $values['id'] = (int)$values['id']; + $values['parent'] = (int)$values['parent']; + + if ($values['parent'] > 0) + { + $values['level'] = $this->id2name($values['parent'],'level')+1; + $values['main'] = $this->id2name($values['parent'],'main'); + } + + $values['descr'] = $this->db->db_addslashes($values['descr']); + $values['name'] = $this->db->db_addslashes($values['name']); + + if ($values['id'] > 0) + { + $id_col = 'cat_id,'; + $id_val = $values['id'] . ','; + } + + $this->db->query('INSERT INTO phpgw_categories (' . $id_col . 'cat_parent,cat_owner,cat_access,cat_appname,cat_name,cat_description,cat_data,' + . 'cat_main,cat_level, last_mod) VALUES (' . $id_val . (int)$values['parent'] . ',' . $this->account_id . ",'" . $values['access'] + . "','" . $this->app_name . "','" . $values['name'] . "','" . $values['descr'] . "','" . $values['data'] + . "'," . (int)$values['main'] . ',' . (int)$values['level'] . ',' . time() . ')',__LINE__,__FILE__); + + if ($values['id'] > 0) + { + $max = $values['id']; + } + else + { + $max = $this->db->get_last_insert_id('phpgw_categories','cat_id'); + } + + $max = (int)$max; + if ($values['parent'] == 0) + { + $this->db->query('UPDATE phpgw_categories SET cat_main=' . $max . ' WHERE cat_id=' . $max,__LINE__,__FILE__); + } + return $max; + } + + /*! + @function delete + @abstract delete category + @param $cat_id int - category id + */ + /*function delete($cat_id,$subs = False) + { + $cat_id = (int)$cat_id; + if ($subs) + { + $subdelete = ' OR cat_parent=' . $cat_id . ' OR cat_main=' . $cat_id; + } + + $this->db->query('DELETE FROM phpgw_categories WHERE cat_id=' . $cat_id . $subdelete . " AND cat_appname='" + . $this->app_name . "'",__LINE__,__FILE__); + } */ + + function delete($cat_id, $drop_subs = False, $modify_subs = False) + { + $cat_id = (int)$cat_id; + if ($drop_subs) + { + $subdelete = ' OR cat_parent=' . $cat_id . ' OR cat_main=' . $cat_id; + } + + if ($modify_subs) + { + $cats = $this->return_sorted_array('',False,'','','',False, $cat_id); + + $new_parent = $this->id2name($cat_id,'parent'); + + for ($i=0;$idb->query('UPDATE phpgw_categories set cat_level=0, cat_parent=0, cat_main=' . (int)$cats[$i]['id'] + . ' WHERE cat_id=' . (int)$cats[$i]['id'] . " AND cat_appname='" . $this->app_name . "'",__LINE__,__FILE__); + $new_main = $cats[$i]['id']; + } + else + { + if ($new_main) + { + $update_main = ',cat_main=' . $new_main; + } + + if ($cats[$i]['parent'] == $cat_id) + { + $update_parent = ',cat_parent=' . $new_parent; + } + + $this->db->query('UPDATE phpgw_categories set cat_level=' . ($cats[$i]['level']-1) . $update_main . $update_parent + . ' WHERE cat_id=' . (int)$cats[$i]['id'] . " AND cat_appname='" . $this->app_name . "'",__LINE__,__FILE__); + } + } + } + + $this->db->query('DELETE FROM phpgw_categories WHERE cat_id=' . $cat_id . $subdelete . " AND cat_appname='" + . $this->app_name . "'",__LINE__,__FILE__); + } + + /*! + @function edit + @abstract edit a category + @param $cat_id int - category id + @param $cat_parent category parent + @param $cat_description category description defaults to '' + @param $cat_data category data defaults to '' + */ + function edit($values) + { + $values['id'] = (int)$values['id']; + $values['parent'] = (int)$values['parent']; + + if (isset($values['old_parent']) && (int)$values['old_parent'] != $values['parent']) + { + $this->delete($values['id'],False,True); + return $this->add($values); + } + else + { + if ($values['parent'] > 0) + { + $values['main'] = (int)$this->id2name($values['parent'],'main'); + $values['level'] = (int)$this->id2name($values['parent'],'level') + 1; + } + else + { + $values['main'] = $values['id']; + $values['level'] = 0; + } + } + + $values['descr'] = $this->db->db_addslashes($values['descr']); + $values['name'] = $this->db->db_addslashes($values['name']); + + $sql = "UPDATE phpgw_categories SET cat_name='" . $values['name'] . "', cat_description='" . $values['descr'] + . "', cat_data='" . $values['data'] . "', cat_parent=" . $values['parent'] . ", cat_access='" + . $values['access'] . "', cat_main=" . $values['main'] . ', cat_level=' . $values['level'] . ',last_mod=' . time() + . " WHERE cat_appname='" . $this->app_name . "' AND cat_id=" . $values['id']; + + $this->db->query($sql,__LINE__,__FILE__); + return $values['id']; + } + + function name2id($cat_name) + { + $this->db->query("SELECT cat_id FROM phpgw_categories WHERE cat_name='" . $this->db->db_addslashes($cat_name) . "' " + ."AND cat_appname='" . $this->app_name . "' AND (cat_owner=" . $this->account_id . ' OR cat_owner=-1)',__LINE__,__FILE__); + + if(!$this->db->num_rows()) + { + return 0; + } + + $this->db->next_record(); + + return $this->db->f('cat_id'); + } + + function id2name($cat_id = '', $item = 'name') + { + $cat_id = (int)$cat_id; + if($cat_id == 0) + { + return '--'; + } + switch($item) + { + case 'owner': $value = 'cat_owner'; break; + case 'main': $value = 'cat_main'; break; + case 'level': $value = 'cat_level'; break; + case 'parent': $value = 'cat_parent'; break; + case 'name': $value = 'cat_name'; break; + default: $value = 'cat_parent'; break; + } + + $this->db->query("SELECT $value FROM phpgw_categories WHERE cat_id=" . $cat_id,__LINE__,__FILE__); + $this->db->next_record(); + + if ($this->db->f($value)) + { + return $this->db->f($value); + } + else + { + if ($item == 'name') + { + return '--'; + } + } + } + + /*! + @function return_name + @abstract return category name given $cat_id + @param $cat_id + @result cat_name category name + */ + // NOTE: This is only a temp wrapper, use id2name() to keep things matching across the board. (jengo) + function return_name($cat_id) + { + return $this->id2name($cat_id); + } + + /*! + @function exists + @abstract used for checking if a category name exists + @param $type subs or mains + @param $cat_name category name + @result boolean true or false + */ + function exists($type,$cat_name = '',$cat_id = '') + { + $cat_id = (int)$cat_id; + $filter = $this->filter($type); + + if ($cat_name) + { + $cat_exists = " cat_name='" . $this->db->db_addslashes($cat_name) . "' "; + } + + if ($cat_id) + { + $cat_exists = ' cat_parent=' . $cat_id; + } + + if ($cat_name && $cat_id) + { + $cat_exists = " cat_name='" . $this->db->db_addslashes($cat_name) . "' AND cat_id != $cat_id "; + } + + $this->db->query("SELECT COUNT(cat_id) FROM phpgw_categories WHERE $cat_exists $filter",__LINE__,__FILE__); + + $this->db->next_record(); + + if ($this->db->f(0)) + { + return True; + } + else + { + return False; + } + } + } +?> diff --git a/phpgwapi/inc/class.db.inc.php b/phpgwapi/inc/class.db.inc.php index b84bbd26fb..468b54ba97 100644 --- a/phpgwapi/inc/class.db.inc.php +++ b/phpgwapi/inc/class.db.inc.php @@ -181,6 +181,12 @@ if (!$this->Link_ID) { + foreach(array('Host','Database','User','Password') as $name) + { + $$name = $this->$name; + } + $type = $this->Type; + switch($this->Type) // convert to ADO db-type-names { case 'pgsql': @@ -190,13 +196,12 @@ " user=$this->User".($this->Password ? " password='".addslashes($this->Password)."'" : ''); $User = $Password = $Database = ''; // to indicate $Host is a connection-string break; + case 'mssql': + if ($this->Port) $Host .= ','.$this->Port; + break; default: - $Host = $this->Host . ($this->Port ? ':'.$this->Port : ''); - foreach(array('Database','User','Password') as $name) - { - $$name = $this->$name; - } - $type = $this->Type; + if ($this->Port) $Host .= ':'.$this->Port; + break; } if (!is_object($GLOBALS['phpgw']->ADOdb) || // we have no connection so far @@ -228,6 +233,15 @@ return 0; // in case error-reporting = 'no' } //echo "new ADOdb connection
".print_r($GLOBALS['phpgw']->ADOdb,True)."
\n"; + + if ($this->Type == 'mssql') + { + // this is the format ADOdb expects + $this->Link_ID->Execute('SET DATEFORMAT ymd'); + // sets the limit to the maximum + ini_set('mssql.textlimit',2147483647); + ini_set('mssql.sizelimit',2147483647); + } } else { @@ -682,7 +696,7 @@ function haltmsg($msg) { printf("

Database error: %s
\n", $msg); - if ($this->Errno != "0" && $this->Error != "()") + if (($this->Errno || $this->Error) && $this->Error != "()") { printf("$this->Type Error: %s (%s)
\n",$this->Errno,$this->Error); } @@ -943,6 +957,10 @@ break; // ADOdb has no BlobEncode for mysql and returns an unquoted string !!! } return "'" . $this->Link_ID->BlobEncode($value) . "'"; + case 'date': + return $this->Link_ID->DBDate($value); + case 'timestamp': + return $this->Link_ID->DBTimeStamp($value); } return $this->Link_ID->quote($value); } diff --git a/phpgwapi/inc/class.preferences.inc.php b/phpgwapi/inc/class.preferences.inc.php new file mode 100644 index 0000000000..8b17528540 --- /dev/null +++ b/phpgwapi/inc/class.preferences.inc.php @@ -0,0 +1,1270 @@ + * + * and Mark Peters * + * Manages user preferences * + * Copyright (C) 2000, 2001 Joseph Engo * + * -------------------------------------------------------------------------* + * This library is part of the eGroupWare API * + * http://www.egroupware.org/api * + * ------------------------------------------------------------------------ * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by * + * the Free Software Foundation; either version 2.1 of the License, * + * or any later version. * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU Lesser General Public License for more details. * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + \**************************************************************************/ + + /* $Id$ */ + + /*! + @class preferences + @abstract preferences class used for setting application preferences + @discussion the prefs are read into 4 arrays: \ + $data the effective prefs used everywhere in phpgw, they are merged from the other 3 arrays \ + $user the stored user prefs, only used for manipulating and storeing the user prefs \ + $default the default preferences, always used when the user has no own preference set \ + $forced forced preferences set by the admin, they take precedence over user or default prefs + */ + class preferences + { + /*! @var account_id */ + var $account_id; + /*! @var account_type */ + var $account_type; + /*! @var data effectiv user prefs, used by all apps */ + var $data = array(); + /*! @var user set user prefs for saveing (no defaults/forced prefs merged) */ + var $user = array(); + /*! @var default default prefs */ + var $default = array(); + /*! @var forced forced prefs */ + var $forced = array(); + /*! @var session session / tempory prefs */ + var $session = array(); + /*! @var db */ + var $db; + + var $values,$vars; // standard notify substitues, will be set by standard_substitues() + + /**************************************************************************\ + * Standard constructor for setting $this->account_id * + \**************************************************************************/ + /*! + @function preferences + @abstract Standard constructor for setting $this->account_id + @discussion Author: + */ + function preferences($account_id = '') + { + $this->db = is_object($GLOBALS['phpgw']->db) ? $GLOBALS['phpgw']->db : $GLOBALS['phpgw_setup']->db; + $this->account_id = get_account_id($account_id); + } + + /**************************************************************************\ + * These are the standard $this->account_id specific functions * + \**************************************************************************/ + + /*! + @function parse_notify + @abstract parses a notify and replaces the substitutes + @syntax parse_notify($msg,$values='',$use_standard_values=True) + @param $msg message to parse / substitute + @param $values extra vars to replace in addition to $this->values, vars are in an array with \ + $key => $value pairs, $key does not include the $'s and is the *untranslated* name + @param $use_standard_values should the standard values are used + @returns the parsed notify-msg + */ + function parse_notify($msg,$values='',$use_standard_values=True) + { + $vals = $values ? $values : array(); + + if ($use_standard_values && is_array($this->values)) + { + $vals += $this->values; + } + foreach($vals as $key => $val) + { + $replace[] = '$$'.$key.'$$'; + $with[] = $val; + } + return str_replace($replace,$with,$msg); + } + + /*! + @function lang_notify + @abstract replaces the english key's with translated ones, or if $un_lang the opposite + @syntax lang_notify($msg,$values='',$un_lang=False) + @param $msg message to translate + @param $values extra vars to replace in addition to $this->values, vars are in an array with \ + $key => $value pairs, $key does not include the $'s and is the *untranslated* name + @param $un_lang if true translate back + @returns the result + */ + function lang_notify($msg,$vals=array(),$un_lang=False) + { + foreach($vals as $key => $val) + { + $lname = ($lname = lang($key)) == $key.'*' ? $key : $lname; + if ($un_lang) + { + $langs[$lname] = '$$'.$key.'$$'; + } + else + { + $langs[$key] = '$$'.$lname.'$$'; + } + } + return $this->parse_notify($msg,$langs,False); + } + + /*! + @function standard_substitues + @abstract define some standard substitues-values and use them on the prefs, if needed + */ + function standard_substitutes() + { + if (!is_array(@$GLOBALS['phpgw_info']['user']['preferences'])) + { + $GLOBALS['phpgw_info']['user']['preferences'] = $this->data; // else no lang() + } + // we cant use phpgw_info/user/fullname, as it's not set when we run + $GLOBALS['phpgw']->accounts->get_account_name($this->account_id,$lid,$fname,$lname); + + $this->values = array( // standard notify replacements + 'fullname' => $GLOBALS['phpgw']->common->display_fullname('',$fname,$lname), + 'firstname' => $fname, + 'lastname' => $lname, + 'domain' => $GLOBALS['phpgw_info']['server']['mail_suffix'], + 'email' => $this->email_address($this->account_id), + 'date' => $GLOBALS['phpgw']->common->show_date('',$GLOBALS['phpgw_info']['user']['preferences']['common']['dateformat']), + ); + // do this first, as it might be already contain some substitues + // + $this->values['email'] = $this->parse_notify($this->values['email']); + + $this->vars = array( // langs have to be in common !!! + 'fullname' => lang('name of the user, eg. "%1"',$this->values['fullname']), + 'firstname' => lang('first name of the user, eg. "%1"',$this->values['firstname']), + 'lastname' => lang('last name of the user, eg. "%1"',$this->values['lastname']), + 'domain' => lang('domain name for mail-address, eg. "%1"',$this->values['domain']), + 'email' => lang('email-address of the user, eg. "%1"',$this->values['email']), + 'date' => lang('todays date, eg. "%1"',$this->values['date']), + ); + // do the substituetion in the effective prefs (data) + // + foreach($this->data as $app => $data) + { + foreach($data as $key => $val) + { + if (!is_array($val) && strstr($val,'$$') !== False) + { + $this->data[$app][$key] = $this->parse_notify($val); + } + elseif (is_array($val)) + { + foreach($val as $k => $v) + { + if (!is_array($v) && strstr($val,'$$') !== False) + { + $this->data[$app][$key][$k] = $this->parse_notify($v); + } + } + } + } + } + } + + /*! + @function unquote + @abstract unquote (stripslashes) recursivly the whole array + @param $arr array to unquote (var-param!) + */ + function unquote(&$arr) + { + if (!is_array($arr)) + { + $arr = stripslashes($arr); + return; + } + foreach($arr as $key => $value) + { + if (is_array($value)) + { + $this->unquote($arr[$key]); + } + else + { + $arr[$key] = stripslashes($value); + } + } + } + + /*! + @function read_repository + @abstract private - read preferences from the repository + @note the function ready all 3 prefs user/default/forced and merges them to the effective ones + @discussion private function should only be called from within this class + */ + function read_repository() + { + $this->session = $GLOBALS['phpgw']->session->appsession('preferences','preferences'); + if (!is_array($this->session)) + { + $this->session = array(); + } + $this->db->query("SELECT * FROM phpgw_preferences" + . " WHERE preference_owner IN (-1,-2," . (int)$this->account_id . ')',__LINE__,__FILE__); + + $this->forced = $this->default = $this->user = array(); + while($this->db->next_record()) + { + // The following replacement is required for PostgreSQL to work + $app = str_replace(' ','',$this->db->f('preference_app')); + $value = unserialize($this->db->f('preference_value')); + $this->unquote($value); + if (!is_array($value)) + { + continue; + } + switch($this->db->f('preference_owner')) + { + case -1: // forced + $this->forced[$app] = $value; + break; + case -2: // default + $this->default[$app] = $value; + break; + default: // user + $this->user[$app] = $value; + break; + } + } + $this->data = $this->user; + + // let the (temp.) session prefs. override the user prefs. + // + foreach($this->session as $app => $values) + { + foreach($values as $var => $value) + { + $this->data[$app][$var] = $value; + } + } + + // now use defaults if needed (user-value unset or empty) + // + foreach($this->default as $app => $values) + { + foreach($values as $var => $value) + { + if (!isset($this->data[$app][$var]) || $this->data[$app][$var] === '') + { + $this->data[$app][$var] = $value; + } + } + } + // now set/force forced values + // + foreach($this->forced as $app => $values) + { + foreach($values as $var => $value) + { + $this->data[$app][$var] = $value; + } + } + // setup the standard substitutes and substitutes the data in $this->data + // + $this->standard_substitutes(); + + // This is to supress warnings during login + if (is_array($this->data)) + { + reset($this->data); + } + if (isset($this->debug) && substr($GLOBALS['phpgw_info']['flags']['currentapp'],0,3) != 'log') + { + echo 'user

';     print_r($this->user); echo "
\n"; + echo 'forced
';   print_r($this->forced); echo "
\n"; + echo 'default
';  print_r($this->default); echo "
\n"; + echo 'effectiv
'; print_r($this->data); echo "
\n"; + } + return $this->data; + } + + /*! + @function read + @abstract public - read preferences from repository and stores in an array + @discussion Syntax array read(); <> + Example1: preferences->read(); + @result $data array containing user preferences + */ + function read() + { + if (count($this->data) == 0) + { + $this->read_repository(); + } + reset ($this->data); + return $this->data; + } + + /*! + @function add + @abstract add preference to $app_name a particular app + @discussion + @param $app_name name of the app + @param $var name of preference to be stored + @param $value value of the preference + @param $type of preference to set: forced, default, user + @note the effective prefs ($this->data) are updated to reflect the change + @returns the new effective prefs (even when forced or default prefs are set !) + */ + function add($app_name,$var,$value = '##undef##',$type='user') + { + //echo "

add('$app_name','$var','$value')

\n"; + if ($value === '##undef##') + { + global $$var; + $value = $$var; + } + + switch ($type) + { + case 'session': + if (!isset($this->forced[$app_name][$var]) || $this->forced[$app_name][$var] === '') + { + $this->session[$app_name][$var] = $this->data[$app_name][$var] = $value; + $GLOBALS['phpgw']->session->appsession('preferences','preferences',$this->session); + } + break; + + case 'forced': + $this->data[$app_name][$var] = $this->forced[$app_name][$var] = $value; + break; + + case 'default': + $this->default[$app_name][$var] = $value; + if ((!isset($this->forced[$app_name][$var]) || $this->forced[$app_name][$var] === '') && + (!isset($this->user[$app_name][$var]) || $this->user[$app_name][$var] === '')) + { + $this->data[$app_name][$var] = $value; + } + break; + + case user: + default: + $this->user[$app_name][$var] = $value; + if (!isset($this->forced[$app_name][$var]) || $this->forced[$app_name][$var] === '') + { + $this->data[$app_name][$var] = $value; + } + break; + } + reset($this->data); + return $this->data; + } + + /*! + @function delete + @abstract delete preference from $app_name + @discussion + @param $app_name name of app + @param $var variable to be deleted + @param $type of preference to set: forced, default, user + @note the effektive prefs ($this->data) are updated to reflect the change + @returns the new effective prefs (even when forced or default prefs are deleted!) + */ + function delete($app_name, $var = False,$type = 'user') + { + //echo "

delete('$app_name','$var','$type')

\n"; + $set_via = array( + 'forced' => array('user','default'), + 'default' => array('forced','user'), + 'user' => array('forced','default') + ); + if (!isset($set_via[$type])) + { + $type = 'user'; + } + $pref = &$this->$type; + + if ($all = (is_string($var) && $var == '')) + { + unset($pref[$app_name]); + unset($this->data[$app_name]); + } + else + { + unset($pref[$app_name][$var]); + unset($this->data[$app_name][$var]); + } + // set the effectiv pref again if needed + // + foreach ($set_via[$type] as $set_from) + { + if ($all) + { + if (isset($this->$set_from[$app_name])) + { + $this->data[$app_name] = $this->$set_from[$app_name]; + break; + } + } + else + { + $arr = $this->$set_from; + if($var && @isset($arr[$app_name][$var]) && $arr[$app_name][$var] !== '') + { + $this->data[$app_name][$var] = $this->$set_from[$app_name][$var]; + break; + } + unset($arr); + } + } + reset ($this->data); + return $this->data; + } + + /*! + @function add_struct + @abstract add complex array data preference to $app_name a particular app + @discussion Use for sublevels of prefs, such as email app's extra accounts preferences + @param $app_name name of the app + @param $var array keys separated by '/', eg. 'ex_accounts/1' + @param $value value of the preference + @note the function works on user and data, to be able to save the pref and to have imediate effect + */ + function add_struct($app_name,$var,$value = '') + { + /* eval is slow and dangerous + $code = '$this->data[$app_name]'.$var.' = $value;'; + print_debug('class.preferences: add_struct: $code: ', $code,'api'); + eval($code); + */ + $parts = explode('/',str_replace(array('][','[',']','"',"'"),array('/','','','',''),$var)); + $data = &$this->data[$app_name]; + $user = &$this->user[$app_name]; + foreach($parts as $name) + { + $data = &$data[$name]; + $user = &$user[$name]; + } + $data = $user = $value; + print_debug('class.preferences: add_struct: $this->data[$app_name] dump:', $this->data[$app_name],'api'); + reset($this->data); + return $this->data; + } + + /*! + @function delete_struct + @abstract delete complex array data preference from $app_name + @discussion Use for sublevels of prefs, such as email app's extra accounts preferences + @param $app_name name of app + @param $var array keys separated by '/', eg. 'ex_accounts/1' + @note the function works on user and data, to be able to save the pref and to have immediate effect + */ + function delete_struct($app_name, $var = '') + { + /* eval is slow and dangerous + $code_1 = '$this->data[$app_name]'.$var.' = "";'; + print_debug('class.preferences: delete_struct: $code_1:', $code_1,'api'); + eval($code_1); + $code_2 = 'unset($this->data[$app_name]'.$var.');' ; + print_debug('class.preferences: delete_struct: $code_2: ', $code_2,'api'); + eval($code_2); + */ + $parts = explode('/',str_replace(array('][','[',']','"',"'"),array('/','','','',''),$var)); + $last = array_pop($parts); + $data = &$this->data[$app_name]; + $user = &$this->user[$app_name]; + foreach($parts as $name) + { + $data = &$data[$name]; + $user = &$user[$name]; + } + unset($data[$last]); + unset($user[$last]); + print_debug('* $this->data[$app_name] dump:', $this->data[$app_name],'api'); + reset ($this->data); + return $this->data; + } + + /*! + @function quote + @abstract quote (addslashes) recursivly the whole array + @param $arr array to unquote (var-param!) + */ + function quote(&$arr) + { + if (!is_array($arr)) + { + $arr = addslashes($arr); + return; + } + foreach($arr as $key => $value) + { + if (is_array($value)) + { + $this->quote($arr[$key]); + } + else + { + $arr[$key] = addslashes($value); + } + } + } + + /*! + @function save_repository + @abstract save the the preferences to the repository + @syntax save_repository($update_session_info = False,$type='') + @param $update_session_info old param, seems not to be used + @param $type which prefs to update: user/default/forced + @note the user prefs for saveing are in $this->user not in $this->data, which are the effectiv prefs only + */ + function save_repository($update_session_info = False,$type='user') + { + switch($type) + { + case 'forced': + $account_id = -1; + $prefs = &$this->forced; + break; + case 'default': + $account_id = -2; + $prefs = &$this->default; + break; + default: + $account_id = (int)$this->account_id; + $prefs = &$this->user; // we use the user-array as data contains default values too + break; + } + //echo "

preferences::save_repository(,$type): account_id=$account_id, prefs="; print_r($prefs); echo "

\n"; + + if (! $GLOBALS['phpgw']->acl->check('session_only_preferences',1,'preferences')) + { + $this->db->transaction_begin(); + $this->db->query("DELETE FROM phpgw_preferences WHERE preference_owner='$account_id'", + __LINE__,__FILE__ + ); + + foreach($prefs as $app => $value) + { + if (!is_array($value)) + { + continue; + } + $this->quote($value); + $value = $this->db->db_addslashes(serialize($value)); // this addslashes is for the database + $app = $this->db->db_addslashes($app); + + $this->db->query($sql = "INSERT INTO phpgw_preferences" + . " (preference_owner,preference_app,preference_value)" + . " VALUES ($account_id,'$app','$value')",__LINE__,__FILE__); + } + $this->db->transaction_commit(); + } + else + { + $GLOBALS['phpgw_info']['user']['preferences'] = $this->data; + $GLOBALS['phpgw']->session->save_repositories(); + } + + if (($type == 'user' || !$type) && $GLOBALS['phpgw_info']['server']['cache_phpgw_info'] && $this->account_id == $GLOBALS['phpgw_info']['user']['account_id']) + { + $GLOBALS['phpgw']->session->delete_cache($this->account_id); + $GLOBALS['phpgw']->session->read_repositories(False); + } + + return $this->data; + } + + /*! + @function create_defaults + @abstract insert a copy of the default preferences for use by real account_id + @discussion + @param $account_id numerical id of account for which to create the prefs + */ + function create_defaults($account_id) + { + return; // not longer needed, as the defaults are merged in on runtime + $this->db->query("select * from phpgw_preferences where preference_owner='-2'",__LINE__,__FILE__); + $this->db->next_record(); + + if($this->db->f('preference_value')) + { + $this->db->query("insert into phpgw_preferences values ('$account_id','" + . $this->db->f('preference_value') . "')",__LINE__,__FILE__); + } + + if ($GLOBALS['phpgw_info']['server']['cache_phpgw_info'] && $account_id == $GLOBALS['phpgw_info']['user']['account_id']) + { + $GLOBALS['phpgw']->session->read_repositories(False); + } + } + + /*! + @function update_data + @abstract update the preferences array + @discussion + @param $data array of preferences + */ + function update_data($data) + { + reset($data); + $this->data = Array(); + $this->data = $data; + reset($this->data); + return $this->data; + } + + /* legacy support */ + function change($app_name,$var,$value = "") + { + return $this->add($app_name,$var,$value); + } + function commit($update_session_info = True) + { + //return $this->save_repository($update_session_info); + } + + /**************************************************************************\ + * These are the non-standard $this->account_id specific functions * + \**************************************************************************/ + + /*! + @function verify_basic_settings + @abstract verify basic settings + @discussion + */ + function verify_basic_settings() + { + if (!@is_array($GLOBALS['phpgw_info']['user']['preferences'])) + { + $GLOBALS['phpgw_info']['user']['preferences'] = array(); + } + /* This takes care of new users who don't have proper default prefs setup */ + if (!isset($GLOBALS['phpgw_info']['flags']['nocommon_preferences']) || + !$GLOBALS['phpgw_info']['flags']['nocommon_preferences']) + { + $preferences_update = False; + if (!isset($GLOBALS['phpgw_info']['user']['preferences']['common']['maxmatchs']) || + !$GLOBALS['phpgw_info']['user']['preferences']['common']['maxmatchs']) + { + $this->add('common','maxmatchs',15); + $preferences_update = True; + } + if (!isset($GLOBALS['phpgw_info']['user']['preferences']['common']['theme']) || + !$GLOBALS['phpgw_info']['user']['preferences']['common']['theme']) + { + $this->add('common','theme','default'); + $preferences_update = True; + } + if (!isset($GLOBALS['phpgw_info']['user']['preferences']['common']['template_set']) || + !$GLOBALS['phpgw_info']['user']['preferences']['common']['template_set']) + { + $this->add('common','template_set','default'); + $preferences_update = True; + } + if (!isset($GLOBALS['phpgw_info']['user']['preferences']['common']['dateformat']) || + !$GLOBALS['phpgw_info']['user']['preferences']['common']['dateformat']) + { + $this->add('common','dateformat','m/d/Y'); + $preferences_update = True; + } + if (!isset($GLOBALS['phpgw_info']['user']['preferences']['common']['timeformat']) || + !$GLOBALS['phpgw_info']['user']['preferences']['common']['timeformat']) + { + $this->add('common','timeformat',12); + $preferences_update = True; + } + if (!isset($GLOBALS['phpgw_info']['user']['preferences']['common']['lang']) || + !$GLOBALS['phpgw_info']['user']['preferences']['common']['lang']) + { + $this->add('common','lang',$GLOBALS['phpgw']->common->getPreferredLanguage()); + $preferences_update = True; + } + if ($preferences_update) + { + $this->save_repository(); + } + unset($preferences_update); + } + } + + /****************************************************\ + * Email Preferences and Private Support Functions * + \****************************************************/ + + /*! + @function sub_get_mailsvr_port + @abstract Helper function for create_email_preferences, gets mail server port number. + @discussion This will generate the appropriate port number to access a + mail server of type pop3, pop3s, imap, imaps users value from + $phpgw_info['user']['preferences']['email']['mail_port']. + if that value is not set, it generates a default port for the given $server_type. + Someday, this *MAY* be + (a) a se4rver wide admin setting, or + (b)user custom preference + Until then, simply set the port number based on the mail_server_type, thereof + ONLY call this function AFTER ['email']['mail_server_type'] has been set. + @param $prefs - user preferences array based on element ['email'][] + @author Angles + @access Private + */ + function sub_get_mailsvr_port($prefs, $acctnum=0) + { + // first we try the port number supplied in preferences + if((isset($prefs['email']['accounts'][$acctnum]['mail_port'])) && + ($prefs['email']['accounts'][$acctnum]['mail_port'] != '')) + { + $port_number = $prefs['email']['accounts'][$acctnum]['mail_port']; + } + // preferences does not have a port number, generate a default value + else + { + if (!isset($prefs['email']['accounts'][$acctnum]['mail_server_type'])) + { + $prefs['email']['accounts'][$acctnum]['mail_server_type'] = $prefs['email']['mail_server_type']; + } + + switch($prefs['email']['accounts'][$acctnum]['mail_server_type']) + { + case 'pop3s': + // POP3 over SSL + $port_number = 995; + break; + case 'pop3': + // POP3 normal connection, No SSL + // ( same string as normal imap above) + $port_number = 110; + break; + case 'nntp': + // NNTP news server port + $port_number = 119; + break; + case 'imaps': + // IMAP over SSL + $port_number = 993; + break; + case 'imap': + // IMAP normal connection, No SSL + default: + // UNKNOWN SERVER in Preferences, return a + // default value that is likely to work + // probably should raise some kind of error here + $port_number = 143; + break; + } + } + return $port_number; + } + + /*! + @function sub_default_userid + @abstract Helper function for create_email_preferences, gets default userid for email + @discussion This will generate the appropriate userid for accessing an email server. + In the absence of a custom ['email']['userid'], this function should be used to set it. + @param $accountid - as determined in and/or passed to "create_email_preferences" + @access Private + */ + function sub_default_userid($account_id='') + { + if ($GLOBALS['phpgw_info']['server']['mail_login_type'] == 'vmailmgr') + { + $prefs_email_userid = $GLOBALS['phpgw']->accounts->id2name($account_id) + . '@' . $GLOBALS['phpgw_info']['server']['mail_suffix']; + } + else + { + $prefs_email_userid = $GLOBALS['phpgw']->accounts->id2name($account_id); + } + return $prefs_email_userid; + } + + /*! + @function email_address + @abstract returns the custom email-address (if set) or generates a default one + @discussion This will generate the appropriate email address used as the "From:" + email address when the user sends email, the localpert@domain part. The "personal" + part is generated elsewhere. + In the absence of a custom ['email']['address'], this function should be used to set it. + @param $accountid - as determined in and/or passed to "create_email_preferences" + @access Public now + */ + function email_address($account_id='') + { + if (isset($this->data['email']['address'])) + { + return $this->data['email']['address']; + } + // if email-address is set in the account, return it + if ($email = $GLOBALS['phpgw']->accounts->id2name($account_id,'account_email')) + { + return $email; + } + $prefs_email_address = $GLOBALS['phpgw']->accounts->id2name($account_id); + if (strstr($prefs_email_address,'@') === False) + { + $prefs_email_address .= '@' . $GLOBALS['phpgw_info']['server']['mail_suffix']; + } + return $prefs_email_address; + } + + function sub_default_address($account_id='') + { + return $this->email_address($account_id); + } + + /*! + @function create_email_preferences + @abstract create email preferences + @param $account_id -optional defaults to : get_account_id() + @discussion fills a local copy of ['email'][] prefs array which is then returned to the calling + function, which the calling function generally tacks onto the $GLOBALS['phpgw_info'] array as such: + $GLOBALS['phpgw_info']['user']['preferences'] = $GLOBALS['phpgw']->preferences->create_email_preferences(); + which fills an array based at: + $GLOBALS['phpgw_info']['user']['preferences']['email'][prefs_are_elements_here] + Reading the raw preference DB data and comparing to the email preference schema defined in + /email/class.bopreferences.inc.php (see discussion there and below) to create default preference values + for the in the ['email'][] pref data array in cases where the user has not supplied + a preference value for any particular preference item available to the user. + @access Public + */ + function create_email_preferences($accountid='', $acctnum=0) + { + print_debug('class.preferences: create_email_preferences: ENTERING
', 'messageonly','api'); + // we may need function "html_quotes_decode" from the mail_msg class + $email_base = CreateObject("email.mail_msg"); + + $account_id = get_account_id($accountid); + // If the current user is not the request user, grab the preferences + // and reset back to current user. + if($account_id != $this->account_id) + { + // Temporarily store the values to a temp, so when the + // read_repository() is called, it doesn't destory the + // current users settings. + $temp_account_id = $this->account_id; + $temp_data = $this->data; + + // Grab the new users settings, only if they are not the + // current users settings. + $this->account_id = $account_id; + $prefs = $this->read_repository(); + + // Reset the data to what it was prior to this call + $this->account_id = $temp_account_id; + $this->data = $temp_data; + } + else + { + $prefs = $this->data; + } + // are we dealing with the default email account or an extra email account? + if ($acctnum != 0) + { + // prefs are actually a sub-element of the main email prefs + // at location [email][ex_accounts][X][...pref names] => pref values + // make this look like "prefs[email] so the code below code below will do its job transparently + + // store original prefs + $orig_prefs = array(); + $orig_prefs = $prefs; + // obtain the desired sub-array of extra account prefs + $sub_prefs = array(); + $sub_prefs['email'] = $prefs['email']['ex_accounts'][$acctnum]; + // make the switch, make it seem like top level email prefs + $prefs = array(); + $prefs['email'] = $sub_prefs['email']; + // since we return just $prefs, it's up to the calling program to put the sub prefs in the right place + } + print_debug('class.preferences: create_email_preferences: $acctnum: ['.$acctnum.'] ; raw $this->data dump', $this->data,'api'); + + // = = = = NOT-SIMPLE PREFS = = = = + // Default Preferences info that is: + // (a) not controlled by email prefs itself (mostly api and/or server level stuff) + // (b) too complicated to be described in the email prefs data array instructions + + // --- [server][mail_server_type] --- + // Set API Level Server Mail Type if not defined + // if for some reason the API didnot have a mail server type set during initialization + if (empty($GLOBALS['phpgw_info']['server']['mail_server_type'])) + { + $GLOBALS['phpgw_info']['server']['mail_server_type'] = 'imap'; + } + + // --- [server][mail_folder] --- + // ==== UWash Mail Folder Location used to be "mail", now it's changeable, but keep the + // ==== default to "mail" so upgrades happen transparently + // --- TEMP MAKE DEFAULT UWASH MAIL FOLDER ~/mail (a.k.a. $HOME/mail) + $GLOBALS['phpgw_info']['server']['mail_folder'] = 'mail'; + // --- DELETE THE ABOVE WHEN THIS OPTION GETS INTO THE SYSTEM SETUP + // pick up custom "mail_folder" if it exists (used for UWash and UWash Maildir servers) + // else use the system default (which we temporarily hard coded to "mail" just above here) + + //--- [email][mail_port] --- + // These sets the mail_port server variable + // someday (not currently) this may be a site-wide property set during site setup + // additionally, someday (not currently) the user may be able to override this with + // a custom email preference. Currently, we simply use standard port numbers + // for the service in question. + $prefs['email']['mail_port'] = $this->sub_get_mailsvr_port($prefs); + + //--- [email][fullname] --- + // we pick this up from phpgw api for the default account + // the user does not directly manipulate this pref for the default email account + if ((string)$acctnum == '0') + { + $prefs['email']['fullname'] = $GLOBALS['phpgw_info']['user']['fullname']; + } + + // = = = = SIMPLER PREFS = = = = + + // Default Preferences info that is articulated in the email prefs schema array itself + // such email prefs schema array is described and established in /email/class.bopreferences + // by function "init_available_prefs", see the discussion there. + + // --- create the objectified /email/class.bopreferences.inc.php --- + $bo_mail_prefs = CreateObject('email.bopreferences'); + + // --- bo_mail_prefs->init_available_prefs() --- + // this fills object_email_bopreferences->std_prefs and ->cust_prefs + // we will initialize the users preferences according to the rules and instructions + // embodied in those prefs arrays, applying those rules to the unprocessed + // data read from the preferences DB. By taking the raw data and applying those rules, + // we will construct valid and known email preference data for this user. + $bo_mail_prefs->init_available_prefs(); + + // --- combine the two array (std and cust) for 1 pass handling --- + // when this preference DB was submitted and saved, it was hopefully so well structured + // that we can simply combine the two arrays, std_prefs and cust_prefs, and do a one + // pass analysis and preparation of this users preferences. + $avail_pref_array = $bo_mail_prefs->std_prefs; + $c_cust_prefs = count($bo_mail_prefs->cust_prefs); + for($i=0;$i<$c_cust_prefs;$i++) + { + // add each custom prefs to the std prefs array + $next_idx = count($avail_pref_array); + $avail_pref_array[$next_idx] = $bo_mail_prefs->cust_prefs[$i]; + } + print_debug('class.preferences: create_email_preferences: std AND cust arrays combined:', $avail_pref_array,'api'); + + // --- make the schema-based pref data for this user --- + // user defined values and/or user specified custom email prefs are read from the + // prefs DB with mininal manipulation of the data. Currently the only change to + // users raw data is related to reversing the encoding of "database un-friendly" chars + // which itself may become unnecessary if and when the database handlers can reliably + // take care of this for us. Of course, password data requires special decoding, + // but the password in the array [email][paswd] should be left in encrypted form + // and only decrypted seperately when used to login in to an email server. + + // --- generating a default value if necessary --- + // in the absence of a user defined custom email preference for a particular item, we can + // determine the desired default value for that pref as such: + // $this_avail_pref['init_default'] is a comma seperated seperated string which should + // be exploded into an array containing 2 elements that are: + // exploded[0] : an description of how to handle the next string element to get a default value. + // Possible "instructional tokens" for exploded[0] (called $set_proc[0] below) are: + // string + // set_or_not + // function + // init_no_fill + // varEVAL + // tells you how to handle the string in exploded[1] (called $set_proc[1] below) to get a valid + // default value for a particular preference if one is needed (i.e. if no user custom + // email preference exists that should override that default value, in which case we + // do not even need to obtain such a default value as described in ['init_default'] anyway). + + // --- loop thru $avail_pref_array and process each pref item --- + $c_prefs = count($avail_pref_array); + for($i=0;$i<$c_prefs;$i++) + { + $this_avail_pref = $avail_pref_array[$i]; + print_debug('class.preferences: create_email_preferences: value from DB for $prefs[email]['.$this_avail_pref['id'].'] = ['.$prefs['email'][$this_avail_pref['id']].']', 'messageonly','api'); + print_debug('class.preferences: create_email_preferences: std/cust_prefs $this_avail_pref['.$i.'] dump:', $this_avail_pref,'api'); + + // --- is there a value in the DB for this preference item --- + // if the prefs DB has no value for this defined available preference, we must make one. + // This occurs if (a) this is user's first login, or (b) this is a custom pref which the user + // has not overriden, do a default (non-custom) value is needed. + if (!isset($prefs['email'][$this_avail_pref['id']])) + { + // now we are analizing an individual pref that is available to the user + // AND the user had no existing value in the prefs DB for this. + + // --- get instructions on how to generate a default value --- + $set_proc = explode(',', $this_avail_pref['init_default']); + print_debug(' * set_proc=['.serialize($set_proc).']', 'messageonly','api'); + + // --- use "instructional token" in $set_proc[0] to take appropriate action --- + // STRING + if ($set_proc[0] == 'string') + { + // means this pref item's value type is string + // which defined string default value is in $set_proc[1] + print_debug('* handle "string" set_proc: ', serialize($set_proc),'api'); + if (trim($set_proc[1]) == '') + { + // this happens when $this_avail_pref['init_default'] = "string, " + $this_string = ''; + } + else + { + $this_string = $set_proc[1]; + } + $prefs['email'][$this_avail_pref['id']] = $this_string; + } + // SET_OR_NOT + elseif ($set_proc[0] == 'set_or_not') + { + // typical with boolean options, True = "set/exists" and False = unset + print_debug('* handle "set_or_not" set_proc: ', serialize($set_proc),'api'); + if ($set_proc[1] == 'not_set') + { + // leave it NOT SET + } + else + { + // opposite of boolean not_set = string "True" which simply sets a + // value it exists in the users session [email][] preference array + $prefs['email'][$this_avail_pref['id']] = 'True'; + } + } + // FUNCTION + elseif ($set_proc[0] == 'function') + { + // string in $set_proc[1] should be "eval"uated as code, calling a function + // which will give us a default value to put in users session [email][] prefs array + print_debug(' * handle "function" set_proc: ', serialize($set_proc),'api'); + $evaled = ''; + //eval('$evaled = $this->'.$set_proc[1].'('.$account_id.');'); + + $code = '$evaled = $this->'.$set_proc[1].'('.$account_id.');'; + print_debug(' * $code: ', $code,'api'); + eval($code); + + print_debug('* $evaled:', $evaled,'api'); + $prefs['email'][$this_avail_pref['id']] = $evaled; + } + // INIT_NO_FILL + elseif ($set_proc[0] == 'init_no_fill') + { + // we have an available preference item that we may NOT fill with a default + // value. Only the user may supply a value for this pref item. + print_debug('* handle "init_no_fill" set_proc:', serialize($set_proc),'api'); + // we are FORBADE from filling this at this time! + } + // varEVAL + elseif ($set_proc[0] == 'varEVAL') + { + // similar to "function" but used for array references, the string in $set_proc[1] + // represents code which typically is an array referencing a system/api property + print_debug('* handle "GLOBALS" set_proc:', serialize($set_proc),'api'); + $evaled = ''; + $code = '$evaled = '.$set_proc[1]; + print_debug(' * $code:', $code,'api'); + eval($code); + print_debug('* $evaled:', $evaled,'api'); + $prefs['email'][$this_avail_pref['id']] = $evaled; + } + else + { + // error, no instructions on how to handle this element's default value creation + echo 'class.preferences: create_email_preferences: set_proc ERROR: '.serialize($set_proc).'
'; + } + } + else + { + // we have a value in the database, do we need to prepare it in any way? + // (the following discussion is unconfirmed:) + // DO NOT ALTER the data in the prefs array!!!! or the next time we call + // save_repository withOUT undoing what we might do here, the + // prefs will permenantly LOOSE the very thing(s) we are un-doing + /// here until the next OFFICIAL submit email prefs function, where it + // will again get this preparation before being written to the database. + + // NOTE: if database de-fanging is eventually handled deeper in the + // preferences class, then the following code would become depreciated + // and should be removed in that case. + if (($this_avail_pref['type'] == 'user_string') && + (stristr($this_avail_pref['write_props'], 'no_db_defang') == False)) + { + // this value was "de-fanged" before putting it in the database + // undo that defanging now + $db_unfriendly = $email_base->html_quotes_decode($prefs['email'][$this_avail_pref['id']]); + $prefs['email'][$this_avail_pref['id']] = $db_unfriendly; + } + } + } + // users preferences are now established to known structured values... + + // SANITY CHECK + // --- [email][use_trash_folder] --- + // --- [email][use_sent_folder] --- + // is it possible to use Trash and Sent folders - i.e. using IMAP server + // if not - force settings to false + if (stristr($prefs['email']['mail_server_type'], 'imap') == False) + { + if (isset($prefs['email']['use_trash_folder'])) + { + unset($prefs['email']['use_trash_folder']); + } + + if (isset($prefs['email']['use_sent_folder'])) + { + unset($prefs['email']['use_sent_folder']); + } + } + + // DEBUG : force some settings to test stuff + //$prefs['email']['p_persistent'] = 'True'; + + print_debug('class.preferences: $acctnum: ['.$acctnum.'] ; create_email_preferences: $prefs[email]', $prefs['email'],'api'); + print_debug('class.preferences: create_email_preferences: LEAVING', 'messageonly','api'); + return $prefs; + } + + /* + // ==== DEPRECATED - ARCHIVAL CODE ==== + // used to be part of function this->create_email_preferences() + // = = = = SIMPLER PREFS = = = = + // Default Preferences info that is: + // described in the email prefs array itself + + $default_trash_folder = 'Trash'; + $default_sent_folder = 'Sent'; + + // --- userid --- + if (!isset($prefs['email']['userid'])) + { + $prefs['email']['userid'] = $this->sub_default_userid($accountid); + } + // --- address --- + if (!isset($prefs['email']['address'])) + { + $prefs['email']['address'] = $this->email_address($accountid); + } + // --- mail_server --- + if (!isset($prefs['email']['mail_server'])) + { + $prefs['email']['mail_server'] = $GLOBALS['phpgw_info']['server']['mail_server']; + } + // --- mail_server_type --- + if (!isset($prefs['email']['mail_server_type'])) + { + $prefs['email']['mail_server_type'] = $GLOBALS['phpgw_info']['server']['mail_server_type']; + } + // --- imap_server_type --- + if (!isset($prefs['email']['imap_server_type'])) + { + $prefs['email']['imap_server_type'] = $GLOBALS['phpgw_info']['server']['imap_server_type']; + } + // --- mail_folder --- + // because of the way this option works, an empty string IS ACTUALLY a valid value + // which represents the $HOME/* as the UWash mail files location + // THERFOR we must check the "Use_custom_setting" option to help us figure out what to do + if (!isset($prefs['email']['use_custom_settings'])) + { + // we are NOT using custom settings so this MUST be the server default + $prefs['email']['mail_folder'] = $GLOBALS['phpgw_info']['server']['mail_folder']; + } + else + { + // we ARE using custom settings AND a BLANK STRING is a valid option, so... + if ((isset($prefs['email']['mail_folder'])) + && ($prefs['email']['mail_folder'] != '')) + { + // using custom AND a string exists, so "mail_folder" is that string stored in the custom prefs by the user + // DO NOTING - VALID OPTION VALUE for $prefs['email']['mail_folder'] + } + else + { + // using Custom Prefs BUT this text box was left empty by the user on submit, so no value stored + // BUT since we are using custom prefs, "mail_folder" MUST BE AN EMPTY STRING + // which is an acceptable, valid preference, overriding any value which + // may have been set in ["server"]["mail_folder"] + // This is one of the few instances in the preference class where an empty, unspecified value + // actually does NOT get deleted from the repository. + $prefs['email']['mail_folder'] = ''; + } + } + + // --- use_trash_folder --- + // --- trash_folder_name --- + // if the option to use the Trash folder is ON, make sure a proper name is specified + if (isset($prefs['email']['use_trash_folder'])) + { + if ((!isset($prefs['email']['trash_folder_name'])) + || ($prefs['email']['trash_folder_name'] == '')) + { + $prefs['email']['trash_folder_name'] = $default_trash_folder; + } + } + + // --- use_sent_folder --- + // --- sent_folder_name --- + // if the option to use the sent folder is ON, make sure a proper name is specified + if (isset($prefs['email']['use_sent_folder'])) + { + if ((!isset($prefs['email']['sent_folder_name'])) + || ($prefs['email']['sent_folder_name'] == '')) + { + $prefs['email']['sent_folder_name'] = $default_sent_folder; + } + } + + // --- layout --- + // Layout Template Preference + // layout 1 = default ; others are prefs + if (!isset($prefs['email']['layout'])) + { + $prefs['email']['layout'] = 1; + } + + //// --- font_size_offset --- + //// Email Index Page Font Size Preference + //// layout 1 = default ; others are prefs + //if (!isset($prefs['email']['font_size_offset'])) + //{ + // $prefs['email']['font_size_offset'] = 'normal'; + //} + + // SANITY CHECK + // --- use_trash_folder --- + // --- use_sent_folder --- + // is it possible to use Trash and Sent folders - i.e. using IMAP server + // if not - force settings to false + if (stristr($prefs['email']['mail_server_type'], 'imap') == False) + { + if (isset($prefs['email']['use_trash_folder'])) + { + unset($prefs['email']['use_trash_folder']); + } + + if (isset($prefs['email']['use_sent_folder'])) + { + unset($prefs['email']['use_sent_folder']); + } + } + + // DEBUG : force some settings to test stuff + //$prefs['email']['layout'] = 1; + //$prefs['email']['layout'] = 2; + //$prefs['email']['font_size_offset'] = (-1); + + // DEBUG + //echo "
prefs['email']:
" + // .'
'.serialize($prefs['email']) .'

'; + return $prefs; + */ + } /* end of preferences class */ +?> diff --git a/phpgwapi/inc/class.schema_proc.inc.php b/phpgwapi/inc/class.schema_proc.inc.php new file mode 100644 index 0000000000..964832a690 --- /dev/null +++ b/phpgwapi/inc/class.schema_proc.inc.php @@ -0,0 +1,590 @@ + * + * and Miles Lott * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + \**************************************************************************/ + + /* $Id$ */ + + class schema_proc + { + var $m_oTranslator; + var $m_oDeltaProc; + var $m_odb; + var $m_aTables; + var $m_bDeltaOnly; + + function schema_proc($dbms) + { + $this->sType = $dbms; + $this->m_oTranslator = CreateObject('phpgwapi.schema_proc_' . $dbms); + $this->m_oDeltaProc = CreateObject('phpgwapi.schema_proc_array'); + $this->m_aTables = array(); + $this->m_bDeltaOnly = False; // Default to false here in case it's just a CreateTable script + } + + function GenerateScripts($aTables, $bOutputHTML=False) + { + if (!is_array($aTables)) + { + return False; + } + $this->m_aTables = $aTables; + + $sAllTableSQL = ''; + foreach ($this->m_aTables as $sTableName => $aTableDef) + { + $sSequenceSQL = ''; + $append_ix = False; + if($this->_GetTableSQL($sTableName, $aTableDef, $sTableSQL, $sSequenceSQL,$append_ix)) + { + if($append_ix) + { + $sTableSQL = "CREATE TABLE $sTableName (\n$sTableSQL\n" + . $this->m_oTranslator->m_sStatementTerminator; + } + else + { + $sTableSQL = "CREATE TABLE $sTableName (\n$sTableSQL\n)" + . $this->m_oTranslator->m_sStatementTerminator; + } + if($sSequenceSQL != '') + { + $sAllTableSQL .= $sSequenceSQL . "\n"; + } + $sAllTableSQL .= $sTableSQL . "\n\n"; + } + else + { + if($bOutputHTML) + { + print('
Failed generating script for ' . $sTableName . '
'); + echo '
'.$sTableName.' = '; print_r($aTableDef); echo "
\n"; + } + + return false; + } + } + + if($bOutputHTML) + { + print('
' . $sAllTableSQL . '


'); + } + + return True; + } + + function ExecuteScripts($aTables, $bOutputHTML=False) + { + if(!is_array($aTables) || !IsSet($this->m_odb)) + { + return False; + } + + reset($aTables); + $this->m_aTables = $aTables; + + while(list($sTableName, $aTableDef) = each($aTables)) + { + if($this->CreateTable($sTableName, $aTableDef)) + { + if($bOutputHTML) + { + echo '
Create Table ' . $sTableName . ''; + } + } + else + { + if($bOutputHTML) + { + echo '
Create Table Failed For ' . $sTableName . ''; + } + + return False; + } + } + + return True; + } + + function DropAllTables($aTables, $bOutputHTML=False) + { + if(!is_array($aTables) || !isset($this->m_odb)) + { + return False; + } + + $this->m_aTables = $aTables; + + reset($this->m_aTables); + while(list($sTableName, $aTableDef) = each($this->m_aTables)) + { + if($this->DropTable($sTableName)) + { + if($bOutputHTML) + { + echo '
Drop Table ' . $sTableSQL . ''; + } + } + else + { + return False; + } + } + + return True; + } + + function DropTable($sTableName) + { + $retVal = $this->m_oDeltaProc->DropTable($this, $this->m_aTables, $sTableName); + if($this->m_bDeltaOnly) + { + return $retVal; + } + + return $retVal && $this->m_oTranslator->DropTable($this, $this->m_aTables, $sTableName); + } + + function DropColumn($sTableName, $aTableDef, $sColumnName, $bCopyData = true) + { + $retVal = $this->m_oDeltaProc->DropColumn($this, $this->m_aTables, $sTableName, $aTableDef, $sColumnName, $bCopyData); + if($this->m_bDeltaOnly) + { + return $retVal; + } + + return $retVal && $this->m_oTranslator->DropColumn($this, $this->m_aTables, $sTableName, $aTableDef, $sColumnName, $bCopyData); + } + + function RenameTable($sOldTableName, $sNewTableName) + { + $retVal = $this->m_oDeltaProc->RenameTable($this, $this->m_aTables, $sOldTableName, $sNewTableName); + if($this->m_bDeltaOnly) + { + return $retVal; + } + + return $retVal && $this->m_oTranslator->RenameTable($this, $this->m_aTables, $sOldTableName, $sNewTableName); + } + + function RenameColumn($sTableName, $sOldColumnName, $sNewColumnName, $bCopyData=True) + { + $retVal = $this->m_oDeltaProc->RenameColumn($this, $this->m_aTables, $sTableName, $sOldColumnName, $sNewColumnName, $bCopyData); + if($this->m_bDeltaOnly) + { + return $retVal; + } + + return $retVal && $this->m_oTranslator->RenameColumn($this, $this->m_aTables, $sTableName, $sOldColumnName, $sNewColumnName, $bCopyData); + } + + function AlterColumn($sTableName, $sColumnName, $aColumnDef, $bCopyData=True) + { + $retVal = $this->m_oDeltaProc->AlterColumn($this, $this->m_aTables, $sTableName, $sColumnName, $aColumnDef, $bCopyData); + if($this->m_bDeltaOnly) + { + return $retVal; + } + + return $retVal && $this->m_oTranslator->AlterColumn($this, $this->m_aTables, $sTableName, $sColumnName, $aColumnDef, $bCopyData); + } + + function AddColumn($sTableName, $sColumnName, $aColumnDef) + { + $retVal = $this->m_oDeltaProc->AddColumn($this, $this->m_aTables, $sTableName, $sColumnName, $aColumnDef); + if($this->m_bDeltaOnly) + { + return $retVal; + } + + return $retVal && $this->m_oTranslator->AddColumn($this, $this->m_aTables, $sTableName, $sColumnName, $aColumnDef); + } + + function CreateTable($sTableName, $aTableDef) + { + $retVal = $this->m_oDeltaProc->CreateTable($this, $this->m_aTables, $sTableName, $aTableDef); + if($this->m_bDeltaOnly) + { + return $retVal; + } + + return $retVal && $this->m_oTranslator->CreateTable($this, $this->m_aTables, $sTableName, $aTableDef); + } + + function UpdateSequence($sTableName,$sColumnName) + { + if (method_exists($this->m_oTranslator,'UpdateSequence')) + { + return $this->m_oTranslator->UpdateSequence($this->m_odb,$sTableName,$sColumnName); + } + return True; + } + + + // This function manually re-created the table incl. primary key and all other indices + // It is meant to use if the primary key, existing indices or column-order changes or + // columns are not longer used or new columns need to be created (with there default value or NULL) + // Beside the default-value in the schema, one can give extra defaults via $aDefaults to eg. use an + // other colum or function to set the value of a new or changed column + function RefreshTable($sTableName, $aTableDef, $aDefaults=False) + { + if($GLOBALS['DEBUG']) { echo "

schema_proc::RefreshTable('$sTableName',"._debug_array($aTableDef,False).")

m_aTables[$sTableName]="._debug_array($this->m_aTables[$sTableName],False)."\n"; } + $old_fd = $this->m_aTables[$sTableName]['fd']; + + $Ok = $this->m_oDeltaProc->RefreshTable($this, $this->m_aTables, $sTableName, $aTableDef); + if(!$Ok || $this->m_bDeltaOnly) + { + return $Ok; // nothing else to do + } + $tmp_name = 'tmp_'.$sTableName; + $this->m_odb->transaction_begin(); + + $select = array(); + foreach($aTableDef['fd'] as $name => $data) + { + if ($aDefaults && isset($aDefaults[$name])) // use given default + { + $value = $aDefaults[$name]; + } + elseif (isset($old_fd[$name])) // existing column, use its value => column-name in query + { + $value = $name; + } + else // new column => use default value or NULL + { + if (!isset($data['default']) && (!isset($data['nullable']) || $data['nullable'])) + { + $value = 'NULL'; + } + else + { + $value = $this->m_odb->quote(isset($data['default']) ? $data['default'] : '',$data['type']); + // fix for postgres error "no '<' operator for type 'unknown'" + if ($this->sType == 'pgsql') + { + $type_translated = $this->m_oTranslator->TranslateType($data['type']); + $value = "CAST($value AS $type_translated)"; + } + } + } + $select[] = $value; + } + $select = implode(',',$select); + + $Ok = $this->RenameTable($sTableName,$tmp_name) && + $this->CreateTable($sTableName,$aTableDef) && + $this->m_odb->query("INSERT INTO $sTableName SELECT DISTINCT $select FROM $tmp_name",__LINE__,__FILE__); + + if (!$Ok) + { + $this->m_odb->transaction_abort(); + return False; + } + // do we need to update the new sequences value ? + if (count($aTableDef['pk']) == 1 && $aTableDef['fd'][$aTableDef['pk'][0]]['type'] == 'auto') + { + $this->UpdateSequence($sTableName,$aTableDef['pk'][0]); + } + $this->DropTable($tmp_name); + $this->m_odb->transaction_commit(); + + return True; + } + + function f($value) + { + if($this->m_bDeltaOnly) + { + // Don't care, since we are processing deltas only + return False; + } + + return $this->m_odb->f($value); + } + + function num_rows() + { + if($this->m_bDeltaOnly) + { + // If not False, we will cause while loops calling us to hang + return False; + } + + return $this->m_odb->num_rows(); + } + + function next_record() + { + if($this->m_bDeltaOnly) + { + // If not False, we will cause while loops calling us to hang + return False; + } + + return $this->m_odb->next_record(); + } + + function query($sQuery, $line='', $file='') + { + if($this->m_bDeltaOnly) + { + // Don't run this query, since we are processing deltas only + return True; + } + + return $this->m_odb->query($sQuery, $line, $file); + } + + function _GetTableSQL($sTableName, $aTableDef, &$sTableSQL, &$sSequenceSQL,&$append_ix) + { + if(!is_array($aTableDef)) + { + return False; + } + + $sTableSQL = ''; + reset($aTableDef['fd']); + while(list($sFieldName, $aFieldAttr) = each($aTableDef['fd'])) + { + $sFieldSQL = ''; + if($this->_GetFieldSQL($aFieldAttr, $sFieldSQL)) + { + if($sTableSQL != '') + { + $sTableSQL .= ",\n"; + } + + $sTableSQL .= "$sFieldName $sFieldSQL"; + + if($aFieldAttr['type'] == 'auto') + { + $this->m_oTranslator->GetSequenceSQL($sTableName, $sSequenceSQL); + if($sSequenceSQL != '') + { + $sTableSQL .= sprintf(" DEFAULT nextval('seq_%s')", $sTableName); + } + } + } + else + { + if($GLOBALS['DEBUG']) { echo 'GetFieldSQL failed for ' . $sFieldName; } + return False; + } + } + + $sUCSQL = ''; + $sPKSQL = ''; + $sIXSQL = ''; + + if(count($aTableDef['pk']) > 0) + { + if(!$this->_GetPK($aTableDef['pk'], $sPKSQL)) + { + if($bOutputHTML) + { + print('
Failed getting primary key
'); + } + + return False; + } + } + + if(count($aTableDef['uc']) > 0) + { + if(!$this->_GetUC($aTableDef['uc'], $sUCSQL)) + { + if($bOutputHTML) + { + print('
Failed getting unique constraint
'); + } + + return False; + } + } + + if(count($aTableDef['ix']) > 0) + { + $append_ix = False; + if(!$this->_GetIX($aTableDef['ix'], $sIXSQL,$append_ix,$sTableName)) + { + if($bOutputHTML) + { + print('
Failed getting index
'); + } + + return False; + } +// print('
HELLO!: ' . $sIXSQL); + } + + if($sPKSQL != '') + { + $sTableSQL .= ",\n" . $sPKSQL; + } + + if($sUCSQL != '') + { + $sTableSQL .= ",\n" . $sUCSQL; + } + + if($sIXSQL != '') + { + if($append_ix) + { + $sTableSQL .= ");\n" . $sIXSQL; + //pg: CREATE INDEX test1_id_index ON test1 (id); + } + else + { + $sTableSQL .= ",\n" . $sIXSQL; + } + } + + return True; + } + + // Get field DDL + function _GetFieldSQL($aField, &$sFieldSQL) + { + if($GLOBALS['DEBUG']) { echo'
_GetFieldSQL(): Incoming ARRAY: '; var_dump($aField); } + if(!is_array($aField)) + { + return false; + } + + $sType = ''; + $iPrecision = 0; + $iScale = 0; + $bNullable = true; + + reset($aField); + while(list($sAttr, $vAttrVal) = each($aField)) + { + switch ($sAttr) + { + case 'type': + $sType = $vAttrVal; + break; + case 'precision': + $iPrecision = (int)$vAttrVal; + break; + case 'scale': + $iScale = (int)$vAttrVal; + break; + case 'nullable': + $bNullable = $vAttrVal; + break; + default: + break; + } + } + + // Translate the type for the DBMS + if($sFieldSQL = $this->m_oTranslator->TranslateType($sType, $iPrecision, $iScale)) + { + if(strpos(strtolower($sFieldSQL),'null')===false) + { + if(!$bNullable) + { + $sFieldSQL .= ' NOT NULL'; + } + elseif ($this->m_oTranslator->b_needExplicitNULL) + { + $sFieldSQL .= ' NULL'; + } + } + if(isset($aField['default'])) + { + if($GLOBALS['DEBUG']) { echo'
_GetFieldSQL(): Calling TranslateDefault for "' . $aField['default'] . '"'; } + // Get default DDL - useful for differences in date defaults (eg, now() vs. getdate()) + + $sFieldSQL .= ' DEFAULT ' . (is_numeric($aField['default']) ? $aField['default'] : + $this->m_oTranslator->TranslateDefault($aField['default'])); + } + if($GLOBALS['DEBUG']) { echo'
_GetFieldSQL(): Outgoing SQL: ' . $sFieldSQL; } + return true; + } + + if($GLOBALS['DEBUG']) { echo '
Failed to translate field: type[' . $sType . '] precision[' . $iPrecision . '] scale[' . $iScale . ']
'; } + + return False; + } + + function _GetPK($aFields, &$sPKSQL) + { + $sPKSQL = ''; + if(count($aFields) < 1) + { + return True; + } + + $sPKSQL = $this->m_oTranslator->GetPKSQL(implode(',',$aFields)); + + return True; + } + + function _GetUC($aFields, &$sUCSQL) + { + $sUCSQL = ''; + if(count($aFields) < 1) + { + return True; + } + foreach($aFields as $mFields) + { + $aUCSQL[] = $this->m_oTranslator->GetUCSQL( + is_array($mFields) ? implode(',',$mFields) : $mFields); + } + $sUCSQL = implode(",\n",$aUCSQL); + + return True; + } + + function _GetIX($aFields, &$sIXSQL, &$append, $sTableName) + { + $sUCSQL = ''; + if(count($aFields) < 1) + { + return True; + } + $aIXSQL = array(); + foreach($aFields as $mFields) + { + $options = False; + if (is_array($mFields)) + { + if (isset($mFields['options'])) // array sets additional options + { + $options = @$mFields['options'][$this->sType]; // db-specific options, eg. index-type + unset($mFields['options']); + } + if ($options === false) + { + continue; // dont create index for that db, eg. cant index text + } + $mFields = implode(',',$mFields); + } + $aIXSQL[] = $this->m_oTranslator->GetIXSQL($mFields,$append,$options,$sTableName); + } + if($append) + { + $sIXSQL = implode("\n",$aIXSQL); + } + else + { + $sIXSQL = implode(",\n",$aIXSQL); + } + + return True; + } + } +?> diff --git a/phpgwapi/inc/class.schema_proc_mssql.inc.php b/phpgwapi/inc/class.schema_proc_mssql.inc.php new file mode 100644 index 0000000000..08a69c08dc --- /dev/null +++ b/phpgwapi/inc/class.schema_proc_mssql.inc.php @@ -0,0 +1,364 @@ + * + * and Miles Lott * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + \**************************************************************************/ + + /* $Id$ */ + + class schema_proc_mssql + { + var $m_sStatementTerminator; + /* Following added to convert sql to array */ + var $sCol = array(); + var $pk = array(); + var $fk = array(); + var $ix = array(); + var $uc = array(); + var $b_needExplicitNULL = true; // no definition means NOT NULL for mssql + + function schema_proc_mssql() + { + $this->m_sStatementTerminator = ';'; + } + + /* Return a type suitable for DDL */ + function TranslateType($sType, $iPrecision = 0, $iScale = 0) + { + $sTranslated = ''; + switch($sType) + { + case 'auto': + $sTranslated = 'int identity(1,1) NOT NULL'; + break; + case 'blob': + $sTranslated = 'image'; /* wonder how well PHP will support this??? */ + break; + case 'char': + if ($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = sprintf("char(%d)", $iPrecision); + } + if ($iPrecision > 255) + { + $sTranslated = 'text'; + } + break; + case 'date': + $sTranslated = 'smalldatetime'; + break; + case 'decimal': + $sTranslated = sprintf("decimal(%d,%d)", $iPrecision, $iScale); + break; + case 'float': + switch ($iPrecision) + { + case 4: + $sTranslated = 'float'; + break; + case 8: + $sTranslated = 'real'; + break; + } + break; + case 'int': + switch ($iPrecision) + { + case 2: + $sTranslated = 'smallint'; + break; + case 4: + case 8: + $sTranslated = 'int'; + break; + } + break; + case 'longtext': + case 'text': + $sTranslated = 'text'; + break; + case 'timestamp': + $sTranslated = 'datetime'; + break; + case 'bool': + $sTranslated = 'bit'; + break; + case 'varchar': + if ($iPrecision > 0 && $iPrecision <= 256) + { + $sTranslated = sprintf("varchar(%d)", $iPrecision); + } + if ($iPrecision > 256) + { + $sTranslated = 'text'; + } + break; + } + return $sTranslated; + } + + function TranslateDefault($sDefault) + { + switch ($sDefault) + { + case 'current_date': + case 'current_timestamp': + return 'GetDate()'; + } + + return "'$sDefault'"; + } + + // Inverse of above, convert sql column types to array info + function rTranslateType($sType, $iPrecision = 0, $iScale = 0) + { + $sTranslated = ''; + if ($sType == 'int' || $sType == 'tinyint' || $sType == 'smallint') + { + if ($iPrecision > 8) + { + $iPrecision = 8; + } + elseif($iPrecision > 4) + { + $iPrecision = 4; + } + else + { + $iPrecision = 2; + } + } + switch($sType) + { + case 'tinyint': + case 'smallint': + $sTranslated = "'type' => 'int', 'precision' => 2"; + break; + case 'int': + $sTranslated = "'type' => 'int', 'precision' => 4"; + break; + case 'char': + if ($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = "'type' => 'char', 'precision' => $iPrecision"; + } + if ($iPrecision > 255) + { + $sTranslated = "'type' => 'text'"; + } + break; + case 'decimal': + $sTranslated = "'type' => 'decimal', 'precision' => $iPrecision, 'scale' => $iScale"; + break; + case 'float': + case 'double': + $sTranslated = "'type' => 'float', 'precision' => $iPrecision"; + break; + case 'smalldatetime': + $sTranslated = "'type' => 'date'"; + break; + case 'datetime': + $sTranslated = "'type' => 'timestamp'"; + break; + case 'varchar': + if ($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = "'type' => 'varchar', 'precision' => $iPrecision"; + } + if ($iPrecision > 255) + { + $sTranslated = "'type' => 'text'"; + } + break; + case 'image': + $sTranslated = "'type' => 'blob'"; + break; + case 'text': + $sTranslated = "'type' => '$sType'"; + break; + case 'bit': + $sTranslated = "'type' => 'bool'"; + break; + } + return $sTranslated; + } + + function GetPKSQL($sFields) + { + return "PRIMARY KEY NONCLUSTERED ($sFields)"; + } + + function GetUCSQL($sFields) + { + return "UNIQUE($sFields)"; + } + + function GetIXSQL($sFields,&$append,$options,$sTableName) + { + $append = True; + $ixFields = str_replace(',','_',$sFields); + $index = $sTableName . '_' . $ixFields . '_idx'; + return "CREATE INDEX $index ON $sTableName ($sFields);\n"; + } + + function _GetColumns($oProc, $sTableName, &$sColumns, $sDropColumn = '') + { + $sColumns = ''; + $this->pk = array(); + $this->fk = array(); + $this->ix = array(); + $this->uc = array(); + + // Field, Type, Null, Key, Default, Extra + $oProc->m_odb->query("exec sp_columns '$sTableName'"); + while ($oProc->m_odb->next_record()) + { + $type = $default = $null = $nullcomma = $prec = $scale = $ret = $colinfo = $scales = ''; + if ($sColumns != '') + { + $sColumns .= ','; + } + $sColumns .= $oProc->m_odb->f(0); + + // The rest of this is used only for SQL->array + $colinfo = explode('(',$oProc->m_odb->f(1)); + $prec = ereg_replace(')','',$colinfo[1]); + $scales = explode(',',$prec); + if ($scales[1]) + { + $prec = $scales[0]; + $scale = $scales[1]; + } + $type = $this->rTranslateType($colinfo[0], $prec, $scale); + + if ($oProc->m_odb->f(2) == 'YES') + { + $null = "'nullable' => True"; + } + else + { + $null = "'nullable' => False"; + } + if ($oProc->m_odb->f(4)) + { + $default = "'default' => '".$oProc->m_odb->f(4)."'"; + $nullcomma = ','; + } + else + { + $default = ''; + $nullcomma = ''; + } + if ($oProc->m_odb->f(5)) + { + $type = "'type' => 'auto'"; + } + $this->sCol[] = "\t\t\t\t'" . $oProc->m_odb->f(0)."' => array(" . $type . ',' . $null . $nullcomma . $default . '),' . "\n"; + if ($oProc->m_odb->f(3) == 'PRI') + { + $this->pk[] = $oProc->m_odb->f(0); + } + if ($oProc->m_odb->f(3) == 'UNI') + { + $this->uc[] = $oProc->m_odb->f(0); + } + /* Hmmm, MUL could also mean unique, or not... */ + if ($oProc->m_odb->f(3) == 'MUL') + { + $this->ix[] = $oProc->m_odb->f(0); + } + } + /* ugly as heck, but is here to chop the trailing comma on the last element (for php3) */ + $this->sCol[count($this->sCol) - 1] = substr($this->sCol[count($this->sCol) - 1],0,-2) . "\n"; + + return false; + } + + function DropTable($oProc, &$aTables, $sTableName) + { + return !!($oProc->m_odb->query("DROP TABLE " . $sTableName)); + } + + function DropColumn($oProc, &$aTables, $sTableName, $aNewTableDef, $sColumnName, $bCopyData = true) + { + return !!($oProc->m_odb->query("ALTER TABLE $sTableName DROP COLUMN $sColumnName")); + } + + function RenameTable($oProc, &$aTables, $sOldTableName, $sNewTableName) + { + return !!($oProc->m_odb->query("EXEC sp_rename '$sOldTableName', '$sNewTableName'")); + } + + function RenameColumn($oProc, &$aTables, $sTableName, $sOldColumnName, $sNewColumnName, $bCopyData = true) + { + // This really needs testing - it can affect primary keys, and other table-related objects + // like sequences and such + global $DEBUG; + if ($DEBUG) { echo '
RenameColumn: calling _GetFieldSQL for ' . $sNewColumnName; } + if ($oProc->_GetFieldSQL($aTables[$sTableName]["fd"][$sNewColumnName], $sNewColumnSQL)) + { + return !!($oProc->m_odb->query("EXEC sp_rename '$sTableName.$sOldColumnName', '$sNewColumnName'")); + } + return false; + } + + function AlterColumn($oProc, &$aTables, $sTableName, $sColumnName, &$aColumnDef, $bCopyData = true) + { + global $DEBUG; + if ($DEBUG) { echo '
AlterColumn: calling _GetFieldSQL for ' . $sNewColumnName; } + if ($oProc->_GetFieldSQL($aTables[$sTableName]["fd"][$sColumnName], $sNewColumnSQL)) + { + return !!($oProc->m_odb->query("ALTER TABLE $sTableName ALTER COLUMN $sColumnName " . $sNewColumnSQL)); + } + + return false; + } + + function AddColumn($oProc, &$aTables, $sTableName, $sColumnName, &$aColumnDef) + { + $oProc->_GetFieldSQL($aColumnDef, $sFieldSQL); + $query = "ALTER TABLE $sTableName ADD $sColumnName $sFieldSQL"; + + return !!($oProc->m_odb->query($query)); + } + + function GetSequenceSQL($sTableName, &$sSequenceSQL) + { + $sSequenceSQL = ''; + return true; + } + + function CreateTable($oProc, &$aTables, $sTableName, $aTableDef) + { + if ($oProc->_GetTableSQL($sTableName, $aTableDef, $sTableSQL, $sSequenceSQL,$append_ix)) + { + // create sequence first since it will be needed for default + if ($sSequenceSQL != '') + { + $oProc->m_odb->query($sSequenceSQL); + } + + if($append_ix) + { + $query = "CREATE TABLE $sTableName ($sTableSQL"; + } + else + { + $query = "CREATE TABLE $sTableName ($sTableSQL)"; + } + + return !!($oProc->m_odb->query($query)); + } + + return false; + } + } +?> diff --git a/phpgwapi/inc/class.schema_proc_mysql.inc.php b/phpgwapi/inc/class.schema_proc_mysql.inc.php new file mode 100644 index 0000000000..a63ac1c847 --- /dev/null +++ b/phpgwapi/inc/class.schema_proc_mysql.inc.php @@ -0,0 +1,426 @@ + * + * and Miles Lott * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + \**************************************************************************/ + + /* $Id$ */ + + class schema_proc_mysql + { + var $m_sStatementTerminator; + /* Following added to convert sql to array */ + var $sCol = array(); + var $pk = array(); + var $fk = array(); + var $ix = array(); + var $uc = array(); + + function schema_proc_mysql() + { + $this->m_sStatementTerminator = ';'; + } + + /* Return a type suitable for DDL */ + function TranslateType($sType, $iPrecision = 0, $iScale = 0) + { + $sTranslated = ''; + switch($sType) + { + case 'auto': + $sTranslated = 'int(11) auto_increment not null'; + break; + case 'blob': + $sTranslated = 'blob'; + break; + case 'bool': + $sTranslated = 'tinyint(1)'; + break; + case 'char': + if($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = sprintf("char(%d)", $iPrecision); + } + if($iPrecision > 255) + { + $sTranslated = 'text'; + } + break; + case 'date': + $sTranslated = 'date'; + break; + case 'decimal': + $sTranslated = sprintf("decimal(%d,%d)", $iPrecision, $iScale); + break; + case 'float': + switch($iPrecision) + { + case 4: + $sTranslated = 'float'; + break; + case 8: + $sTranslated = 'double'; + break; + } + break; + case 'int': + switch($iPrecision) + { + case 2: + $sTranslated = 'smallint'; + break; + case 4: + $sTranslated = 'int'; + break; + case 8: + $sTranslated = 'bigint'; + break; + } + break; + case 'longtext': + $sTranslated = 'longtext'; + break; + case 'text': + $sTranslated = 'text'; + break; + case 'timestamp': + $sTranslated = 'datetime'; + break; + case 'varchar': + if($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = sprintf("varchar(%d)", $iPrecision); + } + if($iPrecision > 255) + { + $sTranslated = 'text'; + } + break; + } + return $sTranslated; + } + + function TranslateDefault($sDefault) + { + switch($sDefault) + { + case 'current_date': + case 'current_timestamp': + $sDefault = 'now'; + } + return "'$sDefault'"; + } + + /* Inverse of above, convert sql column types to array info */ + function rTranslateType($sType, $iPrecision = 0, $iScale = 0) + { + $sTranslated = ''; + if($sType == 'int' || $sType == 'tinyint' || $sType == 'smallint' || $sType == 'bigint') + { + if($iPrecision > 8) + { + $iPrecision = 8; + } + elseif($iPrecision > 4) + { + $iPrecision = 4; + } + else + { + $iPrecision = 2; + } + } + switch($sType) + { + case 'tinyint': + case 'smallint': + $sTranslated = "'type' => 'int', 'precision' => 2"; + break; + case 'int': + $sTranslated = "'type' => 'int', 'precision' => 4"; + break; + case 'bigint': + $sTranslated = "'type' => 'int', 'precision' => 8"; + break; + case 'char': + if($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = "'type' => 'char', 'precision' => $iPrecision"; + } + if($iPrecision > 255) + { + $sTranslated = "'type' => 'text'"; + } + break; + case 'decimal': + $sTranslated = "'type' => 'decimal', 'precision' => $iPrecision, 'scale' => $iScale"; + break; + case 'float': + case 'double': + $sTranslated = "'type' => 'float', 'precision' => $iPrecision"; + break; + case 'datetime': + $sTranslated = "'type' => 'timestamp'"; + break; + case 'enum': + /* Here comes a nasty assumption */ + /* $sTranslated = "'type' => 'varchar', 'precision' => 255"; */ + $sTranslated = "'type' => 'varchar', 'precision' => $iPrecision"; + break; + case 'varchar': + if($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = "'type' => 'varchar', 'precision' => $iPrecision"; + } + if($iPrecision > 255) + { + $sTranslated = "'type' => 'text'"; + } + break; + case 'longtext': + case 'text': + case 'blob': + case 'date': + $sTranslated = "'type' => '$sType'"; + break; + } + return $sTranslated; + } + + function GetPKSQL($sFields) + { + return "PRIMARY KEY($sFields)"; + } + + function GetUCSQL($sFields) + { + return "UNIQUE($sFields)"; + } + + function GetIXSQL($sFields,&$append,$options=False) + { + $append = False; + $type = 'INDEX'; + $length = ''; + if(strtoupper($options) == 'FULLTEXT') + { + $type = 'FULLTEXT'; + } + if(is_numeric($options)) + { + $length = "($options)"; + } + return "$type($sFields $length)"; + } + + function _GetColumns($oProc, $sTableName, &$sColumns, $sDropColumn = '') + { + $sColumns = ''; + $this->pk = array(); + $this->fk = array(); + $this->ix = array(); + $this->uc = array(); + + /* Field, Type, Null, Key, Default, Extra */ + $oProc->m_odb->query("describe $sTableName"); + while($oProc->m_odb->next_record()) + { + $type = $default = $null = $nullcomma = $prec = $scale = $ret = $colinfo = $scales = ''; + if($sColumns != '') + { + $sColumns .= ','; + } + $sColumns .= $oProc->m_odb->f(0); + + /* The rest of this is used only for SQL->array */ + $colinfo = explode('(',$oProc->m_odb->f(1)); + $prec = ereg_replace('\).*','',$colinfo[1]); + $scales = explode(',',$prec); + + if($colinfo[0] == 'enum') + { + /* set prec to length of longest enum-value */ + for($prec=0; list($nul,$name) = @each($scales);) + { + if($prec < (strlen($name) - 2)) + { + /* -2 as name is like "'name'" */ + $prec = (strlen($name) - 2); + } + } + } + elseif($scales[1]) + { + $prec = $scales[0]; + $scale = $scales[1]; + } + $type = $this->rTranslateType($colinfo[0], $prec, $scale); + + if($oProc->m_odb->f(2) == 'YES') + { + $null = "'nullable' => True"; + } + else + { + $null = "'nullable' => False"; + } + if($oProc->m_odb->f(4) != '') + { + $default = "'default' => '".$oProc->m_odb->f(4)."'"; + $nullcomma = ','; + } + else + { + $default = ''; + $nullcomma = ''; + } + if($oProc->m_odb->f(5)) + { + $type = "'type' => 'auto'"; + } + $this->sCol[] = "\t\t\t\t'" . $oProc->m_odb->f(0)."' => array(" . $type . ',' . $null . $nullcomma . $default . '),' . "\n"; +/* + if($oProc->m_odb->f(3) == 'PRI') + { + $this->pk[] = $oProc->m_odb->f(0); + } + if($oProc->m_odb->f(3) == 'UNI') + { + $this->uc[] = $oProc->m_odb->f(0); + } + // index over multiple columns + if($oProc->m_odb->f(3) == 'MUL') + { + $this->ix[] = $oProc->m_odb->f(0); + } +*/ + } + $this->_GetIndices($oProc,$sTableName,$this->pk,$this->ix,$this->uc,$this->fk); + + /* ugly as heck, but is here to chop the trailing comma on the last element (for php3) */ + $this->sCol[count($this->sCol) - 1] = substr($this->sCol[count($this->sCol) - 1],0,-2) . "\n"; + + return false; + } + + function _GetIndices($oProc,$sTableName,&$aPk,&$aIx,&$aUc,&$aFk) + { + $aPk = $aIx = $aUc = $aFk = array(); + $seq = $ix = $uc = 0; + + $oProc->m_odb->query("show index from $sTableName"); + while($oProc->m_odb->next_record()) + { + if($seq >= $oProc->m_odb->f('Seq_in_index')) // new index started + { + $$type += 1; + } + if($oProc->m_odb->f('Key_name') == 'PRIMARY') // pk + { + $aPk[] = $oProc->m_odb->f('Column_name'); + $type = 'pk'; + } + elseif($oProc->m_odb->f('Non_unique')) // ix + { + $aIx[$ix][] = $oProc->m_odb->f('Column_name'); + $type = 'ix'; + if($oProc->m_odb->f('Comment') == 'FULLTEXT') + { + $aIx[$ix]['options'] = array('mysql' => 'FULLTEXT'); + } + elseif((int)$oProc->m_odb->f('Sub_part')) + { + $aIx[$ix]['options'] = array('mysql' => (int)$oProc->m_odb->f('Sub_part')); + } + } + else // uc + { + $aUc[$uc][] = $oProc->m_odb->f('Column_name'); + $type = 'uc'; + } + $seq = $oProc->m_odb->f('Seq_in_index'); + } + //echo "Indices from $sTableName

pk=".print_r($aPk,True)."\nix=".print_r($aIx,True)."\nuc=".print_r($aUc,True)."
\n"; + } + + function DropTable($oProc, &$aTables, $sTableName) + { + return !!($oProc->m_odb->query("DROP TABLE " . $sTableName)); + } + + function DropColumn($oProc, &$aTables, $sTableName, $aNewTableDef, $sColumnName, $bCopyData = true) + { + return !!($oProc->m_odb->query("ALTER TABLE $sTableName DROP COLUMN $sColumnName")); + } + + function RenameTable($oProc, &$aTables, $sOldTableName, $sNewTableName) + { + return !!($oProc->m_odb->query("ALTER TABLE $sOldTableName RENAME $sNewTableName")); + } + + function RenameColumn($oProc, &$aTables, $sTableName, $sOldColumnName, $sNewColumnName, $bCopyData = true) + { + /* + TODO: This really needs testing - it can affect primary keys, and other table-related objects + like sequences and such + */ + if($GLOBALS['DEBUG']) { echo '
RenameColumn: calling _GetFieldSQL for ' . $sNewColumnName; } + if($oProc->_GetFieldSQL($aTables[$sTableName]["fd"][$sNewColumnName], $sNewColumnSQL)) + { + return !!($oProc->m_odb->query("ALTER TABLE $sTableName CHANGE $sOldColumnName $sNewColumnName " . $sNewColumnSQL)); + } + return false; + } + + function AlterColumn($oProc, &$aTables, $sTableName, $sColumnName, &$aColumnDef, $bCopyData = true) + { + if($GLOBALS['DEBUG']) { echo '
AlterColumn: calling _GetFieldSQL for ' . $sNewColumnName; } + if($oProc->_GetFieldSQL($aTables[$sTableName]["fd"][$sColumnName], $sNewColumnSQL)) + { + return !!($oProc->m_odb->query("ALTER TABLE $sTableName MODIFY $sColumnName " . $sNewColumnSQL)); + /* return !!($oProc->m_odb->query("ALTER TABLE $sTableName CHANGE $sColumnName $sColumnName " . $sNewColumnSQL)); */ + } + + return false; + } + + function AddColumn($oProc, &$aTables, $sTableName, $sColumnName, &$aColumnDef) + { + $oProc->_GetFieldSQL($aColumnDef, $sFieldSQL); + $query = "ALTER TABLE $sTableName ADD COLUMN $sColumnName $sFieldSQL"; + + return !!($oProc->m_odb->query($query)); + } + + function GetSequenceSQL($sTableName, &$sSequenceSQL) + { + $sSequenceSQL = ''; + return true; + } + + function CreateTable($oProc, &$aTables, $sTableName, $aTableDef) + { + if($oProc->_GetTableSQL($sTableName, $aTableDef, $sTableSQL, $sSequenceSQL,$append_ix)) + { + /* create sequence first since it will be needed for default */ + if($sSequenceSQL != '') + { + $oProc->m_odb->query($sSequenceSQL); + } + + $query = "CREATE TABLE $sTableName ($sTableSQL)"; + return !!($oProc->m_odb->query($query)); + } + + return false; + } + } +?> diff --git a/phpgwapi/inc/class.schema_proc_pgsql.inc.php b/phpgwapi/inc/class.schema_proc_pgsql.inc.php new file mode 100644 index 0000000000..1c51ec34f1 --- /dev/null +++ b/phpgwapi/inc/class.schema_proc_pgsql.inc.php @@ -0,0 +1,748 @@ + * + * Copyright (C) 1998-1999 Tobias Ratschiller * + * -------------------------------------------- * + * This file written by Michael Dean * + * and Miles Lott * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + \**************************************************************************/ + + /* $Id$ */ + + class schema_proc_pgsql + { + var $m_sStatementTerminator; + /* Following added to convert sql to array */ + var $sCol = array(); + var $pk = array(); + var $fk = array(); + var $ix = array(); + var $uc = array(); + + function schema_proc_pgsql() + { + $this->m_sStatementTerminator = ';'; + } + + /* Return a type suitable for DDL */ + function TranslateType($sType, $iPrecision = 0, $iScale = 0) + { + $sTranslated = $sType; + switch($sType) + { + case 'auto': + $sTranslated = 'int4'; + break; + case 'blob': + $sTranslated = 'bytea'; + break; + case 'char': + if($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = sprintf("char(%d)", $iPrecision); + } + if($iPrecision > 255) + { + $sTranslated = 'text'; + } + break; + case 'decimal': + $sTranslated = sprintf("decimal(%d,%d)", $iPrecision, $iScale); + break; + case 'float': + if($iPrecision == 4 || $iPrecision == 8) + { + $sTranslated = sprintf("float%d", $iPrecision); + } + break; + case 'int': + if($iPrecision == 2 || $iPrecision == 4 || $iPrecision == 8) + { + $sTranslated = sprintf("int%d", $iPrecision); + } + break; + case 'longtext': + $sTranslated = 'text'; + break; + case 'varchar': + if($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = sprintf("varchar(%d)", $iPrecision); + } + if($iPrecision > 255) + { + $sTranslated = 'text'; + } + break; + } + return $sTranslated; + } + + function TranslateDefault($sDefault) + { + switch($sDefault) + { + case 'current_date': + case 'current_timestamp': + $sDefault = 'now'; + } + return "'$sDefault'"; + } + + /* Inverse of above, convert sql column types to array info */ + function rTranslateType($sType, $iPrecision = 0, $iScale = 0) + { + $sTranslated = ''; + switch($sType) + { + case 'serial': + $sTranslated = "'type' => 'auto'"; + break; + case 'int2': + $sTranslated = "'type' => 'int', 'precision' => 2"; + break; + case 'int4': + $sTranslated = "'type' => 'int', 'precision' => 4"; + break; + case 'int8': + $sTranslated = "'type' => 'int', 'precision' => 8"; + break; + case 'bpchar': + case 'char': + if($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = "'type' => 'char', 'precision' => $iPrecision"; + } + if($iPrecision > 255) + { + $sTranslated = "'type' => 'text'"; + } + break; + case 'numeric': + /* Borrowed from phpPgAdmin */ + $iPrecision = ($iScale >> 16) & 0xffff; + $iScale = ($iScale - 4) & 0xffff; + $sTranslated = "'type' => 'decimal', 'precision' => $iPrecision, 'scale' => $iScale"; + break; + case 'float': + case 'float4': + case 'float8': + case 'double': + $sTranslated = "'type' => 'float', 'precision' => $iPrecision"; + break; + case 'datetime': + case 'timestamp': + $sTranslated = "'type' => 'timestamp'"; + break; + case 'varchar': + if($iPrecision > 0 && $iPrecision < 256) + { + $sTranslated = "'type' => 'varchar', 'precision' => $iPrecision"; + } + if($iPrecision > 255) + { + $sTranslated = "'type' => 'text'"; + } + break; + case 'text': + case 'blob': + case 'date': + case 'bool'; + $sTranslated = "'type' => '$sType'"; + break; + } + return $sTranslated; + } + + function GetPKSQL($sFields) + { + return "PRIMARY KEY($sFields)"; + } + + function GetUCSQL($sFields) + { + return "UNIQUE($sFields)"; + } + + function GetIXSQL($sFields,&$append,$options,$sTableName) + { + $append = True; + $ixsql = ''; + $ixFields = str_replace(',','_',$sFields); + $index = $sTableName . '_' . $ixFields . '_idx'; + return "CREATE INDEX $index ON $sTableName ($sFields);\n"; + } + + function _GetColumns($oProc, $sTableName, &$sColumns, $sDropColumn='', $sAlteredColumn='', $sAlteredColumnType='') + { + $sdb = $oProc->m_odb; + $sdc = $oProc->m_odb; + + $sColumns = ''; + $this->pk = array(); + $this->fk = array(); + $this->ix = array(); + $this->uc = array(); + + $query = "SELECT a.attname,a.attnum FROM pg_attribute a,pg_class b WHERE "; + $query .= "b.oid=a.attrelid AND a.attnum>0 and b.relname='$sTableName'"; + if($sDropColumn != '') + { + $query .= " AND a.attname != '$sDropColumn'"; + } + $query .= ' ORDER BY a.attnum'; + +// echo '_GetColumns: ' . $query; + + $oProc->m_odb->query($query); + while($oProc->m_odb->next_record()) + { + if($sColumns != '') + { + $sColumns .= ','; + } + + $sFieldName = $oProc->m_odb->f(0); + /* Failsafe in case the query still includes the column to be dropped */ + if($sFieldName != $sDropColumn) + { + $sColumns .= $sFieldName; + } + if($sAlteredColumn == $sFieldName && $sAlteredColumnType != '') + { + $sColumns .= '::' . $sAlteredColumnType; + } + } + //$qdefault = "SELECT substring(d.adsrc for 128) FROM pg_attrdef d, pg_class c " + // . "WHERE c.relname = $sTableName AND c.oid = d.adrelid AND d.adnum =" . $oProc->m_odb->f(1); + $sql_get_fields = " + SELECT + a.attnum, + a.attname AS field, + t.typname AS type, + a.attlen AS length, + a.atttypmod AS lengthvar, + a.attnotnull AS notnull + FROM + pg_class c, + pg_attribute a, + pg_type t + WHERE + c.relname = '$sTableName' + and a.attnum > 0 + and a.attrelid = c.oid + and a.atttypid = t.oid + ORDER BY a.attnum"; + /* attnum field type length lengthvar notnull(Yes/No) */ + $sdb->query($sql_get_fields); + while($sdb->next_record()) + { + $colnum = $sdb->f(0); + $colname = $sdb->f(1); + + if($sdb->f(5) == 'Yes') + { + $null = "'nullable' => True"; + } + else + { + $null = "'nullable' => False"; + } + + if($sdb->f(2) == 'numeric') + { + $prec = $sdb->f(3); + $scale = $sdb->f(4); + } + elseif($sdb->f(3) > 0) + { + $prec = $sdb->f(3); + $scale = 0; + } + elseif($sdb->f(4) > 0) + { + $prec = $sdb->f(4) - 4; + $scale = 0; + } + else + { + $prec = 0; + $scale = 0; + } + + $type = $this->rTranslateType($sdb->f(2), $prec, $scale); + + $sql_get_default = " + SELECT d.adsrc AS rowdefault + FROM pg_attrdef d, pg_class c + WHERE + c.relname = '$sTableName' AND + c.oid = d.adrelid AND + d.adnum = $colnum + "; + $sdc->query($sql_get_default); + $sdc->next_record(); + if($sdc->f(0)) + { + if(strstr($sdc->f(0),'nextval')) + { + $default = ''; + $nullcomma = ''; + } + else + { + $default = "'default' => '".$sdc->f(0)."'"; + $nullcomma = ','; + } + } + else + { + $default = ''; + $nullcomma = ''; + } + $default = str_replace("''","'",$default); + + $this->sCol[] = "\t\t\t\t'" . $colname . "' => array(" . $type . ',' . $null . $nullcomma . $default . '),' . "\n"; + } + $sql_pri_keys = " + SELECT + ic.relname AS index_name, + bc.relname AS tab_name, + ta.attname AS column_name, + i.indisunique AS unique_key, + i.indisprimary AS primary_key + FROM + pg_class bc, + pg_class ic, + pg_index i, + pg_attribute ta, + pg_attribute ia + WHERE + bc.oid = i.indrelid + AND ic.oid = i.indexrelid + AND ia.attrelid = i.indexrelid + AND ta.attrelid = bc.oid + AND bc.relname = '$sTableName' + AND ta.attrelid = i.indrelid + AND ta.attnum = i.indkey[ia.attnum-1] + ORDER BY + index_name, tab_name, column_name"; + $sdc->query($sql_pri_keys); + while($sdc->next_record()) + { + //echo '
checking: ' . $sdc->f(4); + if($sdc->f(4) == 't') + { + $this->pk[] = $sdc->f(2); + } + if($sdc->f(3) == 't') + { + $this->uc[] = $sdc->f(2); + } + } + $this->_GetIndices($oProc,$sTableName,$this->pk,$this->ix,$this->uc,$this->fk); + + /* ugly as heck, but is here to chop the trailing comma on the last element (for php3) */ + $this->sCol[count($this->sCol) - 1] = substr($this->sCol[count($this->sCol) - 1],0,-2) . "\n"; + + return False; + } + + function _GetIndices($oProc,$sTableName,&$aPk,&$aIx,&$aUc,&$aFk) + { + /* Try not to die on errors with the query */ + $tmp = $oProc->Halt_On_Error; + $oProc->Halt_On_Error = 'no'; + $aIx = array(); + /* This select excludes any indexes that are just base indexes for constraints. */ + + if(@$oProc->m_odb->db_version >= 7.3) + { + $sql = "SELECT pg_catalog.pg_get_indexdef(i.indexrelid) as pg_get_indexdef FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i WHERE c.relname = '$sTableName' AND pg_catalog.pg_table_is_visible(c.oid) AND c.oid = i.indrelid AND i.indexrelid = c2.oid AND NOT EXISTS ( SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) WHERE d.classid = c2.tableoid AND d.objid = c2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p') ) ORDER BY c2.relname"; + $num = 0; + } + else + { + $sql = "SELECT c2.relname, i.indisprimary, i.indisunique, pg_get_indexdef(i.indexrelid) FROM pg_class c, pg_class c2, pg_index i WHERE c.relname = '$sTableName' AND c.oid = i.indrelid AND i.indexrelid = c2.oid AND NOT i.indisprimary AND NOT i.indisunique ORDER BY c2.relname"; + $num = 3; + } + + @$oProc->m_odb->query($sql); + $oProc->m_odb->next_record(); + $indexfields = ereg_replace("^CREATE.+\(",'',$oProc->m_odb->f($num)); + $indexfields = ereg_replace("\)$",'',$indexfields); + $aIx = explode(',',$indexfields); + $i = 0; + foreach($aIx as $ix) + { + $aIx[$i] = trim($ix); + $i++; + } + /* Restore original value */ + $oProc->Halt_On_Error = $tmp; + #echo "Indices from $sTableName
pk=".print_r($aPk,True)."\nix=".print_r($aIx,True)."\nuc=".print_r($aUc,True)."
\n"; + } + + function _CopyAlteredTable($oProc, &$aTables, $sSource, $sDest) + { + $oDB = $oProc->m_odb; + $oProc->m_odb->query("SELECT * FROM $sSource"); + while($oProc->m_odb->next_record()) + { + $sSQL = "INSERT INTO $sDest ("; + $i=0; + @reset($aTables[$sDest]['fd']); + while(list($name,$arraydef) = each($aTables[$sDest]['fd'])) + { + if($i > 0) + { + $sSQL .= ','; + } + + $sSQL .= $name; + $i++; + } + + $sSQL .= ') VALUES ('; + @reset($aTables[$sDest]['fd']); + $i = 0; + while(list($name,$arraydef) = each($aTables[$sDest]['fd'])) + { + if($i > 0) + { + $sSQL .= ','; + } + + // !isset($arraydef['nullable']) means nullable !!! + if($oProc->m_odb->f($name) == NULL && (!isset($arraydef['nullable']) || $arraydef['nullable'])) + { + $sSQL .= 'NULL'; + } + else + { + $value = $oProc->m_odb->f($name) != NULL ? $oProc->m_odb->f($name) : @$arraydef['default']; + switch($arraydef['type']) + { + case 'blob': + case 'char': + case 'date': + case 'text': + case 'timestamp': + case 'varchar': + $sSQL .= "'" . $oProc->m_odb->db_addslashes($oProc->m_odb->f($name)) . "'"; + break; + default: + $sSQL .= (int)$oProc->m_odb->f($name); + } + } + $i++; + } + $sSQL .= ')'; + + $oDB->query($sSQL); + } + + return true; + } + + function GetSequenceForTable($oProc,$table,&$sSequenceName) + { + if($GLOBALS['DEBUG']) { echo '
GetSequenceForTable: ' . $table; } + + $oProc->m_odb->query("SELECT relname FROM pg_class WHERE NOT relname ~ 'pg_.*' AND relname LIKE 'seq_$table' AND relkind='S' ORDER BY relname",__LINE__,__FILE__); + $oProc->m_odb->next_record(); + if($oProc->m_odb->f('relname')) + { + $sSequenceName = $oProc->m_odb->f('relname'); + } + return True; + } + + function GetSequenceFieldForTable($oProc,$table,&$sField) + { + if($GLOBALS['DEBUG']) { echo '
GetSequenceFieldForTable: You rang?'; } + + $oProc->m_odb->query("SELECT a.attname FROM pg_attribute a, pg_class c, pg_attrdef d WHERE c.relname='$table' AND c.oid=d.adrelid AND d.adsrc LIKE '%seq_$table%' AND a.attrelid=c.oid AND d.adnum=a.attnum"); + $oProc->m_odb->next_record(); + if($oProc->m_odb->f('attname')) + { + $sField = $oProc->m_odb->f('attname'); + } + return True; + } + + function GetIndexesForTable($oProc,$table,&$sIndexNames) + { + $oProc->m_odb->query("SELECT a.attname FROM pg_attribute a, pg_class c, pg_attrdef d WHERE c.relname='$table' AND c.oid=d.adrelid AND d.adsrc LIKE '%$table%idx' AND a.attrelid=c.oid AND d.adnum=a.attnum"); + while($oProc->m_odb->next_record()) + { + $sIndexNames[] = $oProc->m_odb->f('attname'); + } + return True; + } + + function DropSequenceForTable($oProc,$table) + { + if($GLOBALS['DEBUG']) { echo '
DropSequenceForTable: ' . $table; } + + $this->GetSequenceForTable($oProc,$table,$sSequenceName); + if($sSequenceName) + { + $oProc->m_odb->query("DROP SEQUENCE " . $sSequenceName,__LINE__,__FILE__); + } + return True; + } + + function DropIndexesForTable($oProc,$table) + { + if($GLOBALS['DEBUG']) { echo '
DropSequenceForTable: ' . $table; } + + $this->GetIndexesForTable($oProc,$table,$sIndexNames); + if(@is_array($sIndexNames)) + { + foreach($sIndexNames as $null => $index) + { + $oProc->m_odb->query("DROP INDEX $index",__LINE__,__FILE__); + } + } + return True; + } + + function DropTable($oProc, &$aTables, $sTableName) + { + $this->DropSequenceForTable($oProc,$sTableName); + + return $oProc->m_odb->query("DROP TABLE " . $sTableName) && + $this->DropSequenceForTable($oProc, $sTableName) && + $this->DropIndexesForTable($oProc, $sTableName); + } + + function DropColumn($oProc, &$aTables, $sTableName, $aNewTableDef, $sColumnName, $bCopyData = true) + { + if($GLOBALS['DEBUG']) + { + echo '
DropColumn: table=' . $sTableName . ', column=' . $sColumnName; + } + + if($bCopyData) + { + $oProc->m_odb->query("SELECT * INTO $sTableName" . "_tmp FROM $sTableName"); + } + + $this->DropTable($oProc, $aTables, $sTableName); + + $oProc->_GetTableSQL($sTableName, $aNewTableDef, $sTableSQL, $sSequenceSQL,$append_ix); + if($sSequenceSQL) + { + $oProc->m_odb->query($sSequenceSQL); + } + if($append_ix) + { + $query = "CREATE TABLE $sTableName ($sTableSQL"; + } + else + { + $query = "CREATE TABLE $sTableName ($sTableSQL)"; + } + if(!$bCopyData) + { + return !!($oProc->m_odb->query($query)); + } + + $oProc->m_odb->query($query); + $this->_GetColumns($oProc, $sTableName . '_tmp', $sColumns, $sColumnName); + $query = "INSERT INTO $sTableName($sColumns) SELECT $sColumns FROM $sTableName" . '_tmp'; + $bRet = !!($oProc->m_odb->query($query)); + return ($bRet && $this->DropTable($oProc, $aTables, $sTableName . '_tmp')); + } + + function RenameTable($oProc, &$aTables, $sOldTableName, $sNewTableName) + { + if($GLOBALS['DEBUG']) { echo '
RenameTable(): Fetching old sequence for: ' . $sOldTableName; } + $this->GetSequenceForTable($oProc,$sOldTableName,$sSequenceName); + + if($GLOBALS['DEBUG']) { echo ' - ' . $sSequenceName; } + + if($GLOBALS['DEBUG']) { echo '
RenameTable(): Fetching sequence field for: ' . $sOldTableName; } + $this->GetSequenceFieldForTable($oProc,$sOldTableName,$sField); + + if($GLOBALS['DEBUG']) { echo ' - ' . $sField; } + + if($sSequenceName) + { + $oProc->m_odb->query("SELECT last_value FROM seq_$sOldTableName",__LINE__,__FILE__); + $oProc->m_odb->next_record(); + $lastval = $oProc->m_odb->f(0); + + if($GLOBALS['DEBUG']) { echo '
RenameTable(): dropping old sequence: ' . $sSequenceName . ' used on field: ' . $sField; } + $this->DropSequenceForTable($oProc,$sOldTableName); + + if($lastval) + { + $lastval = ' start ' . $lastval; + } + $this->GetSequenceSQL($sNewTableName,$sSequenceSQL); + if($GLOBALS['DEBUG']) { echo '
RenameTable(): Making new sequence using: ' . $sSequenceSQL . $lastval; } + $oProc->m_odb->query($sSequenceSQL . $lastval,__LINE__,__FILE__); + if($GLOBALS['DEBUG']) { echo '
RenameTable(): Altering column default for: ' . $sField; } + $oProc->m_odb->query("ALTER TABLE $sOldTableName ALTER $sField SET DEFAULT nextval('seq_" . $sNewTableName . "')",__LINE__,__FILE__); + } + // renameing existing indexes and primary keys + $indexes = $oProc->m_odb->Link_ID->MetaIndexes($sOldTableName,True); + if($GLOBALS['DEBUG']) { echo '
RenameTable(): Fetching indexes: '; _debug_array($indexes); } + foreach($indexes as $name => $data) + { + $new_name = str_replace($sOldTableName,$sNewTableName,$name); + $sql = "ALTER TABLE $name RENAME TO $new_name"; + if($GLOBALS['DEBUG']) { echo "
RenameTable(): Renaming the index '$name': $sql"; } + $oProc->m_odb->query($sql); + } + return !!($oProc->m_odb->query("ALTER TABLE $sOldTableName RENAME TO $sNewTableName")); + } + + function RenameColumn($oProc, &$aTables, $sTableName, $sOldColumnName, $sNewColumnName, $bCopyData = true) + { + /* + This really needs testing - it can affect primary keys, and other table-related objects + like sequences and such + */ + if($bCopyData) + { + $oProc->m_odb->query("SELECT * INTO $sTableName" . "_tmp FROM $sTableName"); + } + + $this->DropTable($oProc, $aTables, $sTableName); + + if(!$bCopyData) + { + return $this->CreateTable($oProc, $aTables, $sTableName, $oProc->m_aTables[$sTableName], false); + } + + $this->CreateTable($oProc, $aTables, $sTableName, $aTables[$sTableName], True); + $this->_GetColumns($oProc, $sTableName . "_tmp", $sColumns); + $query = "INSERT INTO $sTableName SELECT $sColumns FROM $sTableName" . "_tmp"; + + $bRet = !!($oProc->m_odb->query($query)); + return ($bRet && $this->DropTable($oProc, $aTables, $sTableName . "_tmp")); + } + + function AlterColumn($oProc, &$aTables, $sTableName, $sColumnName, &$aColumnDef, $bCopyData = true) + { + if($bCopyData) + { + $oProc->m_odb->query("SELECT * INTO $sTableName" . "_tmp FROM $sTableName"); + } + + $this->DropTable($oProc, $aTables, $sTableName); + + if(!$bCopyData) + { + return $this->CreateTable($oProc, $aTables, $sTableName, $aTables[$sTableName], True); + } + + $this->CreateTable($oProc, $aTables, $sTableName, $aTables[$sTableName], True); + $this->_GetColumns($oProc, $sTableName . "_tmp", $sColumns, '', $sColumnName, $aColumnDef['type'] == 'auto' ? 'int4' : $aColumnDef['type']); + + /* + TODO: analyze the type of change and determine if this is used or _CopyAlteredTable + this is a performance consideration only, _CopyAlteredTable should be safe + $query = "INSERT INTO $sTableName SELECT $sColumns FROM $sTableName" . "_tmp"; + $bRet = !!($oProc->m_odb->query($query)); + */ + + $bRet = $this->_CopyAlteredTable($oProc, $aTables, $sTableName . '_tmp', $sTableName); + + return ($bRet && $this->DropTable($oProc, $aTables, $sTableName . "_tmp")); + } + + function AddColumn($oProc, &$aTables, $sTableName, $sColumnName, &$aColumnDef) + { + if(isset($aColumnDef['default'])) // pgsql cant add a colum with a default + { + $default = $aColumnDef['default']; + unset($aColumnDef['default']); + } + if(isset($aColumnDef['nullable']) && !$aColumnDef['nullable']) // pgsql cant add a column not nullable + { + $notnull = !$aColumnDef['nullable']; + unset($aColumnDef['nullable']); + } + $oProc->_GetFieldSQL($aColumnDef, $sFieldSQL); + $query = "ALTER TABLE $sTableName ADD COLUMN $sColumnName $sFieldSQL"; + + if(($Ok = !!($oProc->m_odb->query($query))) && isset($default)) + { + $query = "ALTER TABLE $sTableName ALTER COLUMN $sColumnName SET DEFAULT '$default';\n"; + + $query .= "UPDATE $sTableName SET $sColumnName='$default';\n"; + + $Ok = !!($oProc->m_odb->query($query)); + + if($OK && $notnull) + { + // unfortunally this is pgSQL >= 7.3 + //$query .= "ALTER TABLE $sTableName ALTER COLUMN $sColumnName SET NOT NULL;\n"; + //$Ok = !!($oProc->m_odb->query($query)); + // so we do it the slow way + AlterColumn($oProc, $aTables, $sTableName, $sColumnName, $aColumnDef); + } + } + return $Ok; + } + + function UpdateSequence($oDb,$sTableName,$sColName) + { + $sql = "SELECT MAX($sColName) FROM $sTableName"; + + $oDb->query($sql,__LINE__,__FILE__); + if ($oDb->next_record() && $oDb->f(0)) + { + $sql = "SELECT setval('seq_$sTableName',".(1 + $oDb->f(0)).")"; + if($GLOBALS['DEBUG']) { echo "
Updating sequence 'seq_$sTableName' using: $sql"; } + return $oDb->query($sql,__LINE__,__FILE__); + } + return True; + } + + function GetSequenceSQL($sTableName, &$sSequenceSQL) + { + $sSequenceSQL = sprintf("CREATE SEQUENCE seq_%s", $sTableName); + return true; + } + + function CreateTable($oProc, $aTables, $sTableName, $aTableDef, $bCreateSequence = true) + { + if($oProc->_GetTableSQL($sTableName, $aTableDef, $sTableSQL, $sSequenceSQL,$append_ix)) + { + /* create sequence first since it will be needed for default */ + if($bCreateSequence && $sSequenceSQL != '') + { + if($GLOBALS['DEBUG']) { echo '
Making sequence using: ' . $sSequenceSQL; } + $oProc->m_odb->query($sSequenceSQL); + } + + if($append_ix) + { + $query = "CREATE TABLE $sTableName ($sTableSQL"; + } + else + { + $query = "CREATE TABLE $sTableName ($sTableSQL)"; + } + + return !!($oProc->m_odb->query($query)); + } + + return false; + } + } +?> diff --git a/phpgwapi/inc/class.setup.inc.php b/phpgwapi/inc/class.setup.inc.php new file mode 100644 index 0000000000..9279b0cd95 --- /dev/null +++ b/phpgwapi/inc/class.setup.inc.php @@ -0,0 +1,964 @@ + * + * and Dan Kuykendall * + * and Mark Peters * + * and Miles Lott * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + \**************************************************************************/ + + /* $Id$ */ + + class setup + { + var $db; + var $oProc; + + var $detection = ''; + var $process = ''; + var $lang = ''; + var $html = ''; + var $appreg = ''; + + /* table name vars */ + var $tbl_apps; + var $tbl_config; + var $tbl_hooks; + + function setup($html=False, $translation=False) + { + $this->detection = CreateObject('phpgwapi.setup_detection'); + $this->process = CreateObject('phpgwapi.setup_process'); + $this->appreg = CreateObject('phpgwapi.app_registry'); + + /* The setup application needs these */ + $this->html = $html ? CreateObject('phpgwapi.setup_html') : ''; + $this->translation = $translation ? CreateObject('phpgwapi.setup_translation') : ''; + +// $this->tbl_apps = $this->get_apps_table_name(); +// $this->tbl_config = $this->get_config_table_name(); + $this->tbl_hooks = $this->get_hooks_table_name(); + } + + /*! + @function loaddb + @abstract include api db class for the ConfigDomain and connect to the db + */ + function loaddb() + { + if(!isset($this->ConfigDomain) || empty($this->ConfigDomain)) + { + $this->ConfigDomain = get_var('ConfigDomain',array('COOKIE','POST'),$_POST['FormDomain']); + } + + $GLOBALS['phpgw_info']['server']['db_type'] = $GLOBALS['phpgw_domain'][$this->ConfigDomain]['db_type']; + + if ($GLOBALS['phpgw_info']['server']['db_type'] == 'pgsql') + { + $GLOBALS['phpgw_info']['server']['db_persistent'] = False; + } + $this->db = CreateObject('phpgwapi.db'); + $this->db->Host = $GLOBALS['phpgw_domain'][$this->ConfigDomain]['db_host']; + $this->db->Port = $GLOBALS['phpgw_domain'][$this->ConfigDomain]['db_port']; + $this->db->Type = $GLOBALS['phpgw_domain'][$this->ConfigDomain]['db_type']; + $this->db->Database = $GLOBALS['phpgw_domain'][$this->ConfigDomain]['db_name']; + $this->db->User = $GLOBALS['phpgw_domain'][$this->ConfigDomain]['db_user']; + $this->db->Password = $GLOBALS['phpgw_domain'][$this->ConfigDomain]['db_pass']; + } + + /** + * Set the domain used for cookies + * + * @return string domain + */ + function set_cookiedomain() + { + $this->cookie_domain = $_SERVER['HTTP_HOST']; + + // remove port from HTTP_HOST + if (preg_match("/^(.*):(.*)$/",$this->cookie_domain,$arr)) + { + $this->cookie_domain = $arr[1]; + } + if (count(explode('.',$this->cookie_domain)) <= 1) + { + // setcookie dont likes domains without dots, leaving it empty, gets setcookie to fill the domain in + $this->cookie_domain = ''; + } + } + + /** + * Set a cookie + * + * @param string $cookiename name of cookie to be set + * @param string $cookievalue value to be used, if unset cookie is cleared (optional) + * @param int $cookietime when cookie should expire, 0 for session only (optional) + */ + function set_cookie($cookiename,$cookievalue='',$cookietime=0) + { + if(!isset($this->cookie_domain) || !$this->cookie_domain) + { + $this->set_cookiedomain(); + } + setcookie($cookiename,$cookievalue,$cookietime,'/',$this->cookie_domain); + } + + /*! + @function auth + @abstract authenticate the setup user + @param $auth_type ??? + */ + function auth($auth_type='Config') + { + #phpinfo(); + $FormLogout = get_var('FormLogout', array('GET','POST')); + if(!$FormLogout) + { + $ConfigLogin = get_var('ConfigLogin', array('POST')); + $HeaderLogin = get_var('HeaderLogin', array('POST')); + $FormDomain = get_var('FormDomain', array('POST')); + $FormUser = get_var('FormUser', array('POST')); + $FormPW = get_var('FormPW', array('POST')); + + $this->ConfigDomain = get_var('ConfigDomain',array('POST','COOKIE')); + $ConfigUser = get_var('ConfigUser', array('POST','COOKIE')); + $ConfigPW = get_var('ConfigPW', array('POST','COOKIE')); + $HeaderUser = get_var('HeaderUser', array('POST','COOKIE')); + $HeaderPW = get_var('HeaderPW', array('POST','COOKIE')); + $ConfigLang = get_var('ConfigLang', array('POST','COOKIE')); + + /* Setup defaults to aid in header upgrade to version 1.26. + * This was the first version to include the following values. + */ + if(!@isset($GLOBALS['phpgw_domain'][$FormDomain]['config_user']) && isset($GLOBALS['phpgw_domain'][$FormDomain])) + { + @$GLOBALS['phpgw_domain'][$FormDomain]['config_user'] = 'admin'; + } + if(!@isset($GLOBALS['phpgw_info']['server']['header_admin_user'])) + { + @$GLOBALS['phpgw_info']['server']['header_admin_user'] = 'admin'; + } + } + + $remoteip = $_SERVER['REMOTE_ADDR']; + if(!empty($remoteip) && !$this->checkip($remoteip)) { return False; } + + /* If FormLogout is set, simply invalidate the cookies (LOGOUT) */ + switch(strtolower($FormLogout)) + { + case 'config': + /* config logout */ + $expire = time() - 86400; + $this->set_cookie('ConfigUser','',$expire,'/'); + $this->set_cookie('ConfigPW','',$expire,'/'); + $this->set_cookie('ConfigDomain','',$expire,'/'); + $this->set_cookie('ConfigLang','',$expire,'/'); + $GLOBALS['phpgw_info']['setup']['LastDomain'] = $_COOKIE['ConfigDomain']; + $GLOBALS['phpgw_info']['setup']['ConfigLoginMSG'] = lang('You have successfully logged out'); + $GLOBALS['phpgw_info']['setup']['HeaderLoginMSG'] = ''; + return False; + case 'header': + /* header admin logout */ + $expire = time() - 86400; + $this->set_cookie('HeaderUser','',$expire,'/'); + $this->set_cookie('HeaderPW','',$expire,'/'); + $this->set_cookie('ConfigLang','',$expire,'/'); + $GLOBALS['phpgw_info']['setup']['HeaderLoginMSG'] = lang('You have successfully logged out'); + $GLOBALS['phpgw_info']['setup']['ConfigLoginMSG'] = ''; + return False; + } + + /* We get here if FormLogout is not set (LOGIN or subsequent pages) */ + /* Expire login if idle for 20 minutes. The cookies are updated on every page load. */ + $expire = (int)(time() + (1200*9)); + + switch(strtolower($auth_type)) + { + case 'header': + if(!empty($HeaderLogin)) + { + /* header admin login */ + /* New test is md5, cleartext version is for header < 1.26 */ + if ($this->check_auth($FormUser,$FormPW,$GLOBALS['phpgw_info']['server']['header_admin_user'], + $GLOBALS['phpgw_info']['server']['header_admin_password'])) + { + $this->set_cookie('HeaderUser',"$FormUser",$expire,'/'); + $this->set_cookie('HeaderPW',"$FormPW",$expire,'/'); + $this->set_cookie('ConfigLang',"$ConfigLang",$expire,'/'); + return True; + } + else + { + $GLOBALS['phpgw_info']['setup']['HeaderLoginMSG'] = lang('Invalid password'); + $GLOBALS['phpgw_info']['setup']['ConfigLoginMSG'] = ''; + return False; + } + } + elseif(!empty($HeaderPW) && $auth_type == 'Header') + { + // Returning after login to header admin + /* New test is md5, cleartext version is for header < 1.26 */ + if ($this->check_auth($HeaderUser,$HeaderPW,$GLOBALS['phpgw_info']['server']['header_admin_user'], + $GLOBALS['phpgw_info']['server']['header_admin_password'])) + { + $this->set_cookie('HeaderUser',"$HeaderUser",$expire,'/'); + $this->set_cookie('HeaderPW',"$HeaderPW",$expire,'/'); + $this->set_cookie('ConfigLang',"$ConfigLang",$expire,'/'); + return True; + } + else + { + $GLOBALS['phpgw_info']['setup']['HeaderLoginMSG'] = lang('Invalid password'); + $GLOBALS['phpgw_info']['setup']['ConfigLoginMSG'] = ''; + return False; + } + } + break; + case 'config': + if(!empty($ConfigLogin)) + { + /* config login */ + /* New test is md5, cleartext version is for header < 1.26 */ + if (isset($GLOBALS['phpgw_domain'][$FormDomain]) && + $this->check_auth($FormUser,$FormPW,@$GLOBALS['phpgw_domain'][$FormDomain]['config_user'], + @$GLOBALS['phpgw_domain'][$FormDomain]['config_passwd'])) + { + $this->set_cookie('ConfigUser',"$FormUser",$expire,'/'); + $this->set_cookie('ConfigPW',"$FormPW",$expire,'/'); + $this->set_cookie('ConfigDomain',"$FormDomain",$expire,'/'); + /* Set this now since the cookie will not be available until the next page load */ + $this->ConfigDomain = "$FormDomain"; + $this->set_cookie('ConfigLang',"$ConfigLang",$expire,'/'); + return True; + } + else + { + $GLOBALS['phpgw_info']['setup']['ConfigLoginMSG'] = lang('Invalid password'); + $GLOBALS['phpgw_info']['setup']['HeaderLoginMSG'] = ''; + return False; + } + } + elseif(!empty($ConfigPW)) + { + // Returning after login to config + /* New test is md5, cleartext version is for header < 1.26 */ + if ($this->check_auth($ConfigUser,$ConfigPW,@$GLOBALS['phpgw_domain'][$this->ConfigDomain]['config_user'], + @$GLOBALS['phpgw_domain'][$this->ConfigDomain]['config_passwd'])) + { + $this->set_cookie('ConfigUser',"$ConfigUser",$expire,'/'); + $this->set_cookie('ConfigPW',"$ConfigPW",$expire,'/'); + $this->set_cookie('ConfigDomain',$this->ConfigDomain,$expire,'/'); + $this->set_cookie('ConfigLang',"$ConfigLang",$expire,'/'); + return True; + } + else + { + $GLOBALS['phpgw_info']['setup']['ConfigLoginMSG'] = lang('Invalid password'); + $GLOBALS['phpgw_info']['setup']['HeaderLoginMSG'] = ''; + return False; + } + } + break; + } + + return False; + } + + // returns True if user and pw match, if conf_pw is a md5 ONLY compare with md5($pw) and NOT the plaintext !!! + function check_auth($user,$pw,$conf_user,$conf_pw) + { + if ($user != $conf_user) + { + return False; // wrong username + } + if (preg_match('/^[0-9a-f]{32}$/',$conf_pw)) // $conf_pw is a md5 + { + $pw = md5($pw); + } + return $pw == $conf_pw; + } + + function checkip($remoteip='') + { + //echo "

setup::checkip($remoteip) against setup_acl='".$GLOBALS['phpgw_info']['server']['setup_acl']."'

\n"; + $allowed_ips = explode(',',@$GLOBALS['phpgw_info']['server']['setup_acl']); + if(empty($GLOBALS['phpgw_info']['server']['setup_acl']) || !is_array($allowed_ips)) + { + return True; // no test + } + $remotes = explode('.',$remoteip); + foreach($allowed_ips as $value) + { + if (!preg_match('/^[0-9.]+$/',$value)) + { + $value = gethostbyname($was=$value); // resolve domain-name, eg. a dyndns account + //echo "resolving '$was' to '$value'
\n"; + } + $values = explode('.',$value); + for($i = 0; $i < count($values); ++$i) + { + if ((int) $values[$i] != (int) $remotes[$i]) + { + break; + } + } + if ($i == count($values)) + { + return True; // match + } + } + $GLOBALS['phpgw_info']['setup']['HeaderLoginMSG'] = ''; + $GLOBALS['phpgw_info']['setup']['ConfigLoginMSG'] = lang('Invalid IP address'); + + return False; + } + + /*! + @function get_major + @abstract Return X.X.X major version from X.X.X.X versionstring + @param $ + */ + function get_major($versionstring) + { + if(!$versionstring) + { + return False; + } + + $version = str_replace('pre','.',$versionstring); + $varray = explode('.',$version); + $major = implode('.',array($varray[0],$varray[1],$varray[2])); + + return $major; + } + + /*! + @function clear_session_cache + @abstract Clear system/user level cache so as to have it rebuilt with the next access + @param None + */ + function clear_session_cache() + { + $tables = Array(); + $tablenames = $this->db->table_names(); + foreach($tablenames as $key => $val) + { + $tables[] = $val['table_name']; + } + if(in_array('phpgw_app_sessions',$tables)) + { + $this->db->lock(array('phpgw_app_sessions')); + @$this->db->query("DELETE FROM phpgw_app_sessions WHERE sessionid = '0' and loginid = '0' and app = 'phpgwapi' and location = 'config'",__LINE__,__FILE__); + @$this->db->query("DELETE FROM phpgw_app_sessions WHERE app = 'phpgwapi' and location = 'phpgw_info_cache'",__LINE__,__FILE__); + $this->db->unlock(); + } + } + + /*! + @function register_app + @abstract Add an application to the phpgw_applications table + @param $appname Application 'name' with a matching $setup_info[$appname] array slice + @param $enable optional, set to True/False to override setup.inc.php setting + */ + function register_app($appname,$enable=99) + { + $setup_info = $GLOBALS['setup_info']; + + if(!$appname) + { + return False; + } + + if($enable==99) + { + $enable = $setup_info[$appname]['enable']; + } + $enable = (int)$enable; + + /* + Use old applications table if the currentver is less than 0.9.10pre8, + but not if the currentver = '', which probably means new install. + */ + if($this->alessthanb($setup_info['phpgwapi']['currentver'],'0.9.10pre8') && ($setup_info['phpgwapi']['currentver'] != '')) + { + $appstbl = 'applications'; + } + else + { + $appstbl = 'phpgw_applications'; + } + + if($GLOBALS['DEBUG']) + { + echo '
register_app(): ' . $appname . ', version: ' . $setup_info[$appname]['version'] . ', table: ' . $appstbl . '
'; + // _debug_array($setup_info[$appname]); + } + + if($setup_info[$appname]['version']) + { + if($setup_info[$appname]['tables']) + { + $tables = implode(',',$setup_info[$appname]['tables']); + } + if ($setup_info[$appname]['tables_use_prefix'] == True) + { + echo $setup_info[$appname]['name'] . ' uses tables_use_prefix, storing ' + . $setup_info[$appname]['tables_prefix'] + . ' as prefix for ' . $setup_info[$appname]['name'] . " tables\n"; + + $sql = "INSERT INTO phpgw_config (config_app,config_name,config_value) " + ."VALUES ('".$setup_info[$appname]['name']."','" + .$appname."_tables_prefix','".$setup_info[$appname]['tables_prefix']."');"; + $this->db->query($sql,__LINE__,__FILE__); + } + $this->db->query("INSERT INTO $appstbl " + . "(app_name,app_enabled,app_order,app_tables,app_version) " + . "VALUES (" + . "'" . $setup_info[$appname]['name'] . "'," + . $enable . "," + . (int)$setup_info[$appname]['app_order'] . "," + . "'" . $tables . "'," + . "'" . $setup_info[$appname]['version'] . "');" + ); + $this->clear_session_cache(); + } + } + + /*! + @function app_registered + @abstract Check if an application has info in the db + @param $appname Application 'name' with a matching $setup_info[$appname] array slice + @param $enabled optional, set to False to not enable this app + */ + function app_registered($appname) + { + $setup_info = $GLOBALS['setup_info']; + + if(!$appname) + { + return False; + } + + if($this->alessthanb($setup_info['phpgwapi']['currentver'],'0.9.10pre8') && ($setup_info['phpgwapi']['currentver'] != '')) + { + $appstbl = 'applications'; + } + else + { + $appstbl = 'phpgw_applications'; + } + + if(@$GLOBALS['DEBUG']) + { + echo '
app_registered(): checking ' . $appname . ', table: ' . $appstbl; + // _debug_array($setup_info[$appname]); + } + + $this->db->query("SELECT COUNT(app_name) FROM $appstbl WHERE app_name='".$appname."'"); + $this->db->next_record(); + if($this->db->f(0)) + { + if(@$GLOBALS['DEBUG']) + { + echo '... app previously registered.'; + } + return True; + } + if(@$GLOBALS['DEBUG']) + { + echo '... app not registered'; + } + return False; + } + + /*! + @function update_app + @abstract Update application info in the db + @param $appname Application 'name' with a matching $setup_info[$appname] array slice + @param $enabled optional, set to False to not enable this app + */ + function update_app($appname) + { + $setup_info = $GLOBALS['setup_info']; + + if(!$appname) + { + return False; + } + + if($this->alessthanb($setup_info['phpgwapi']['currentver'],'0.9.10pre8') && ($setup_info['phpgwapi']['currentver'] != '')) + { + $appstbl = 'applications'; + } + else + { + $appstbl = 'phpgw_applications'; + } + + if($GLOBALS['DEBUG']) + { + echo '
update_app(): ' . $appname . ', version: ' . $setup_info[$appname]['currentver'] . ', table: ' . $appstbl . '
'; + // _debug_array($setup_info[$appname]); + } + + $this->db->query("SELECT COUNT(app_name) FROM $appstbl WHERE app_name='".$appname."'"); + $this->db->next_record(); + if(!$this->db->f(0)) + { + return False; + } + + if($setup_info[$appname]['version']) + { + //echo '
' . $setup_info[$appname]['version']; + if($setup_info[$appname]['tables']) + { + $tables = implode(',',$setup_info[$appname]['tables']); + } + + $sql = "UPDATE $appstbl " + . "SET app_name='" . $setup_info[$appname]['name'] . "'," + . " app_enabled=" . (int)$setup_info[$appname]['enable'] . "," + . " app_order=" . (int)$setup_info[$appname]['app_order'] . "," + . " app_tables='" . $tables . "'," + . " app_version='" . $setup_info[$appname]['version'] . "'" + . " WHERE app_name='" . $appname . "'"; + //echo $sql; exit; + + $this->db->query($sql); + } + } + + /*! + @function update_app_version + @abstract Update application version in applications table, post upgrade + @param $setup_info Array of application information (multiple apps or single) + @param $appname Application 'name' with a matching $setup_info[$appname] array slice + @param $tableschanged ??? + */ + function update_app_version($setup_info, $appname, $tableschanged = True) + { + if(!$appname) + { + return False; + } + + if($this->alessthanb($setup_info['phpgwapi']['currentver'],'0.9.10pre8') && ($setup_info['phpgwapi']['currentver'] != '')) + { + $appstbl = 'applications'; + } + else + { + $appstbl = 'phpgw_applications'; + } + + if($tableschanged == True) + { + $GLOBALS['phpgw_info']['setup']['tableschanged'] = True; + } + if($setup_info[$appname]['currentver']) + { + $this->db->query("UPDATE $appstbl SET app_version='" . $setup_info[$appname]['currentver'] . "' WHERE app_name='".$appname."'"); + } + return $setup_info; + } + + /*! + @function deregister_app + @abstract de-Register an application + @param $appname Application 'name' with a matching $setup_info[$appname] array slice + */ + function deregister_app($appname) + { + if(!$appname) + { + return False; + } + $setup_info = $GLOBALS['setup_info']; + + if($this->alessthanb($setup_info['phpgwapi']['currentver'],'0.9.10pre8') && ($setup_info['phpgwapi']['currentver'] != '')) + { + $appstbl = 'applications'; + } + else + { + $appstbl = 'phpgw_applications'; + } + + //echo 'DELETING application: ' . $appname; + $this->db->query("DELETE FROM $appstbl WHERE app_name='". $appname ."'"); + $this->clear_session_cache(); + } + + /*! + @function register_hooks + @abstract Register an application's hooks + @param $appname Application 'name' with a matching $setup_info[$appname] array slice + */ + function register_hooks($appname) + { + $setup_info = $GLOBALS['setup_info']; + + if(!$appname) + { + return False; + } + + if($this->alessthanb($setup_info['phpgwapi']['currentver'],'0.9.8pre5') && ($setup_info['phpgwapi']['currentver'] != '')) + { + /* No phpgw_hooks table yet. */ + return False; + } + + if (!is_object($this->hooks)) + { + $this->hooks = CreateObject('phpgwapi.hooks',$this->db); + } + $this->hooks->register_hooks($appname,$setup_info[$appname]['hooks']); + } + + /*! + @function update_hooks + @abstract Update an application's hooks + @param $appname Application 'name' with a matching $setup_info[$appname] array slice + */ + function update_hooks($appname) + { + $this->register_hooks($appname); + } + + /*! + @function deregister_hooks + @abstract de-Register an application's hooks + @param $appname Application 'name' with a matching $setup_info[$appname] array slice + */ + function deregister_hooks($appname) + { + if($this->alessthanb($setup_info['phpgwapi']['currentver'],'0.9.8pre5')) + { + /* No phpgw_hooks table yet. */ + return False; + } + + if(!$appname) + { + return False; + } + + //echo "DELETING hooks for: " . $setup_info[$appname]['name']; + if (!is_object($this->hooks)) + { + $this->hooks = CreateObject('phpgwapi.hooks',$this->db); + } + $this->hooks->register_hooks($appname); + } + + /*! + @function hook + @abstract call the hooks for a single application + @param $location hook location - required + @param $appname application name - optional + */ + function hook($location, $appname='') + { + if (!is_object($this->hooks)) + { + $this->hooks = CreateObject('phpgwapi.hooks',$this->db); + } + return $this->hooks->single($location,$appname,True,True); + } + + /* + @function alessthanb + @abstract phpgw version checking, is param 1 < param 2 in phpgw versionspeak? + @param $a phpgw version number to check if less than $b + @param $b phpgw version number to check $a against + #return True if $a < $b + */ + function alessthanb($a,$b,$DEBUG=False) + { + $num = array('1st','2nd','3rd','4th'); + + if($DEBUG) + { + echo'
Input values: ' + . 'A="'.$a.'", B="'.$b.'"'; + } + $newa = str_replace('pre','.',$a); + $newb = str_replace('pre','.',$b); + $testa = explode('.',$newa); + if(@$testa[1] == '') + { + $testa[1] = 0; + } + + $testb = explode('.',$newb); + if(@$testb[1] == '') + { + $testb[1] = 0; + } + if(@$testb[3] == '') + { + $testb[3] = 0; + } + $less = 0; + + for($i=0;$iChecking if '. (int)$testa[$i] . ' is less than ' . (int)$testb[$i] . ' ...'; } + if((int)$testa[$i] < (int)$testb[$i]) + { + if ($DEBUG) { echo ' yes.'; } + $less++; + if($i<3) + { + /* Ensure that this is definitely smaller */ + if($DEBUG) { echo" This is the $num[$i] octet, so A is definitely less than B."; } + $less = 5; + break; + } + } + elseif((int)$testa[$i] > (int)$testb[$i]) + { + if($DEBUG) { echo ' no.'; } + $less--; + if($i<2) + { + /* Ensure that this is definitely greater */ + if($DEBUG) { echo" This is the $num[$i] octet, so A is definitely greater than B."; } + $less = -5; + break; + } + } + else + { + if($DEBUG) { echo ' no, they are equal or of different length.'; } + // makes sure eg. '1.0.0' is counted less the '1.0.0.xxx' ! + $less = count($testa) < count($testb) ? 1 : 0; + } + } + if($DEBUG) { echo '
Check value is: "'.$less.'"'; } + if($less>0) + { + if($DEBUG) { echo '
A is less than B'; } + return True; + } + elseif($less<0) + { + if($DEBUG) { echo '
A is greater than B'; } + return False; + } + else + { + if($DEBUG) { echo '
A is equal to B'; } + return False; + } + } + + /*! + @function amorethanb + @abstract phpgw version checking, is param 1 > param 2 in phpgw versionspeak? + @param $a phpgw version number to check if more than $b + @param $b phpgw version number to check $a against + #return True if $a < $b + */ + function amorethanb($a,$b,$DEBUG=False) + { + $num = array('1st','2nd','3rd','4th'); + + if($DEBUG) + { + echo'
Input values: ' + . 'A="'.$a.'", B="'.$b.'"'; + } + $newa = str_replace('pre','.',$a); + $newb = str_replace('pre','.',$b); + $testa = explode('.',$newa); + if($testa[3] == '') + { + $testa[3] = 0; + } + $testb = explode('.',$newb); + if($testb[3] == '') + { + $testb[3] = 0; + } + $less = 0; + + for($i=0;$iChecking if '. (int)$testa[$i] . ' is more than ' . (int)$testb[$i] . ' ...'; } + if((int)$testa[$i] > (int)$testb[$i]) + { + if($DEBUG) { echo ' yes.'; } + $less++; + if($i<3) + { + /* Ensure that this is definitely greater */ + if($DEBUG) { echo" This is the $num[$i] octet, so A is definitely greater than B."; } + $less = 5; + break; + } + } + elseif((int)$testa[$i] < (int)$testb[$i]) + { + if($DEBUG) { echo ' no.'; } + $less--; + if($i<2) + { + /* Ensure that this is definitely smaller */ + if($DEBUG) { echo" This is the $num[$i] octet, so A is definitely less than B."; } + $less = -5; + break; + } + } + else + { + if($DEBUG) { echo ' no, they are equal.'; } + $less = 0; + } + } + if($DEBUG) { echo '
Check value is: "'.$less.'"'; } + if($less>0) + { + if($DEBUG) { echo '
A is greater than B'; } + return True; + } + elseif($less<0) + { + if($DEBUG) { echo '
A is less than B'; } + return False; + } + else + { + if($DEBUG) { echo '
A is equal to B'; } + return False; + } + } + + function get_hooks_table_name() + { + if(@$this->alessthanb($GLOBALS['setup_info']['phpgwapi']['currentver'],'0.9.8pre5') && + @$GLOBALS['setup_info']['phpgwapi']['currentver'] != '') + { + /* No phpgw_hooks table yet. */ + return False; + } + return 'phpgw_hooks'; + } + + function setup_account_object() + { + if (!is_object($GLOBALS['phpgw']->accounts)) + { + if (!is_object($this->db)) + { + $this->loaddb(); + } + /* Load up some configured values */ + $this->db->query("SELECT config_name,config_value FROM phpgw_config " + . "WHERE config_name LIKE 'ldap%' OR config_name LIKE 'account_%' OR config_name LIKE '%encryption%'",__LINE__,__FILE__); + while($this->db->next_record()) + { + $GLOBALS['phpgw_info']['server'][$this->db->f('config_name')] = $this->db->f('config_value'); + } + if (!is_object($GLOBALS['phpgw'])) + { + $GLOBALS['phpgw'] = CreateObject('phpgwapi.phpgw'); + } + copyobj($this->db,$GLOBALS['phpgw']->db); + $GLOBALS['phpgw']->common = CreateObject('phpgwapi.common'); + $GLOBALS['phpgw']->accounts = CreateObject('phpgwapi.accounts'); + + if(($GLOBALS['phpgw_info']['server']['account_repository'] == 'ldap') && + !$GLOBALS['phpgw']->accounts->ds) + { + printf("Error: Error connecting to LDAP server %s!
",$GLOBALS['phpgw_info']['server']['ldap_host']); + exit; + } + } + } + + /*! + @function add_account + @abstract add an user account or a user group + @param username string alphanumerical username or groupname (account_lid) + @param first, last string first / last name + @param $passwd string cleartext pw + @param $group string/boolean Groupname for users primary group or False for a group, default 'Default' + @param $changepw boolean user has right to change pw, default False + @returns the numerical user-id + @note if the $username already exists, only the id is returned, no new user / group gets created + */ + function add_account($username,$first,$last,$passwd,$group='default',$changepw=False) + { + $this->setup_account_object(); + + $groupid = $group ? $GLOBALS['phpgw']->accounts->name2id($group) : False; + + if(!($accountid = $GLOBALS['phpgw']->accounts->name2id($username))) + { + $accountid = $accountid ? $accountid : $GLOBALS['phpgw']->accounts->create(array( + 'account_type' => $group ? 'u' : 'g', + 'account_lid' => $username, + 'account_passwd' => $passwd, + 'account_firstname' => $first, + 'account_lastname' => $last, + 'account_status' => 'A', + 'account_primary_group' => $groupid, + 'account_expires' => -1 + )); + } + $accountid = (int)$accountid; + if($groupid) + { + $this->add_acl('phpgw_group',(int)$groupid,$accountid); + } + $this->add_acl('preferences','changepassword',$accountid,(int)$changepw); + + return $accountid; + } + + /*! + @function add_acl + @abstract Add ACL rights + @param $app string/array with app-names + @param $locations string eg. run + @param $account int/string accountid or account_lid + @param $rights int rights to set, default 1 + */ + function add_acl($apps,$location,$account,$rights=1) + { + if (!is_int($account)) + { + $this->setup_account_object(); + $account = $GLOBALS['phpgw']->accounts->name2id($account); + } + $rights = (int)$rights; + if(!is_object($this->db)) + { + $this->loaddb(); + } + + if(!is_array($apps)) + { + $apps = array($apps); + } + foreach($apps as $app) + { + $this->db->query("DELETE FROM phpgw_acl WHERE acl_appname='$app' AND acl_location='$location' AND acl_account=$account"); + if ($rights) + { + $this->db->query("INSERT INTO phpgw_acl(acl_appname,acl_location,acl_account,acl_rights) VALUES('$app','$location',$account,$rights)"); + } + } + } + } +?> diff --git a/phpgwapi/inc/class.translation_sql.inc.php b/phpgwapi/inc/class.translation_sql.inc.php index c5e8010fc5..85a8f22ffe 100644 --- a/phpgwapi/inc/class.translation_sql.inc.php +++ b/phpgwapi/inc/class.translation_sql.inc.php @@ -426,7 +426,7 @@ // explode with "\t" and removing "\n" with str_replace, needed to work with mbstring.overload=7 list($message_id,$app_name,,$content) = explode("\t",$line); - $content=str_replace("\n",'',$content); + $content=str_replace(array("\n","\r"),'',$content); $message_id = substr(strtolower(chop($message_id)),0,MAX_MESSAGE_ID_LENGTH); $app_name = chop($app_name); $raw[$app_name][$message_id] = $content; diff --git a/phpgwapi/inc/class.vfs_shared.inc.php b/phpgwapi/inc/class.vfs_shared.inc.php new file mode 100644 index 0000000000..cf86ca79db --- /dev/null +++ b/phpgwapi/inc/class.vfs_shared.inc.php @@ -0,0 +1,1370 @@ + * + * This class handles file/dir access for eGroupWare * + * Copyright (C) 2001 Jason Wies * + * -------------------------------------------------------------------------* + * This library is part of the eGroupWare API * + * http://www.egroupware.org/api * + * ------------------------------------------------------------------------ * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by * + * the Free Software Foundation; either version 2.1 of the License, * + * or any later version. * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU Lesser General Public License for more details. * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + \**************************************************************************/ + + /* $Id$ */ + + /* Relative defines. Used mainly by getabsolutepath () */ + define ('RELATIVE_ROOT', 1); + define ('RELATIVE_USER', 2); + define ('RELATIVE_CURR_USER', 4); + define ('RELATIVE_USER_APP', 8); + define ('RELATIVE_PATH', 16); + define ('RELATIVE_NONE', 32); + define ('RELATIVE_CURRENT', 64); + define ('VFS_REAL', 1024); + define ('RELATIVE_ALL', RELATIVE_PATH); + + /* These are used in calls to add_journal (), and allow journal messages to be more standard */ + define ('VFS_OPERATION_CREATED', 1); + define ('VFS_OPERATION_EDITED', 2); + define ('VFS_OPERATION_EDITED_COMMENT', 4); + define ('VFS_OPERATION_COPIED', 8); + define ('VFS_OPERATION_MOVED', 16); + define ('VFS_OPERATION_DELETED', 32); + + /*! + * @class path_class + * @abstract helper class for path_parts + */ + class path_class + { + var $mask; + var $outside; + var $fake_full_path; + var $fake_leading_dirs; + var $fake_extra_path; + var $fake_name; + var $real_full_path; + var $real_leading_dirs; + var $real_extra_path; + var $real_name; + var $fake_full_path_clean; + var $fake_leading_dirs_clean; + var $fake_extra_path_clean; + var $fake_name_clean; + var $real_full_path_clean; + var $real_leading_dirs_clean; + var $real_extra_path_clean; + var $real_name_clean; + } + + /*! + * @class vfs_shared + * @abstract Base class for Virtual File System classes + * @author Zone + */ + class vfs_shared + { + /* + * All VFS classes must have some form of 'linked directories'. + * Linked directories allow an otherwise disparate "real" directory + * to be linked into the "virtual" filesystem. See make_link(). + */ + var $linked_dirs = array (); + + /* + * All VFS classes need to support the access control in some form + * (see acl_check()). There are times when applications will need + * to explictly disable access checking, for example when creating a + * user's home directory for the first time or when the admin is + * performing maintanence. When override_acl is set, any access + * checks must return True. + */ + var $override_acl = 0; + + /* + * The current relativity. See set_relative() and get_relative(). + */ + var $relative; + + /* + * Implementation dependant 'base real directory'. It is not required + * that derived classes use $basedir, but some of the shared functions + * below rely on it, so those functions will need to be overload if + * basedir isn't appropriate for a particular backend. + */ + var $basedir; + + /* + * Fake base directory. Only the administrator should change this. + */ + var $fakebase = '/home'; + + /* + * All derived classes must store certain information about each + * location. The attributes in the 'attributes' array represent + * the minimum attributes that must be stored. Derived classes + * should add to this array any custom attributes. + * + * Not all of the attributes below are appropriate for all backends. + * Those that don't apply can be replaced by dummy values, ie. '' or 0. + */ + var $attributes = array( + 'file_id', /* Integer. Unique to each location */ + 'owner_id', /* phpGW account_id of owner */ + 'createdby_id', /* phpGW account_id of creator */ + 'modifiedby_id',/* phpGW account_id of who last modified */ + 'created', /* Datetime created, in SQL format */ + 'modified', /* Datetime last modified, in SQL format */ + 'size', /* Size in bytes */ + 'mime_type', /* Mime type. 'Directory' for directories */ + 'comment', /* User-supplied comment. Can be empty */ + 'app', /* Name of phpGW application responsible for location */ + 'directory', /* Directory location is in */ + 'name', /* Name of file/directory */ + 'link_directory', /* Directory location is linked to, if any */ + 'link_name', /* Name location is linked to, if any */ + 'version', /* Version of file. May be 0 */ + ); + + /*! + * @function vfs_shared + * @abstract constructor + * @description All derived classes should call this function in their + * constructor ($this->vfs_shared()) + */ + function vfs_shared () + { + } + + /* + * Definitions for functions that every derived + * class must have, and suggestions for private functions + * to completement the public ones. The prototypes for + * the public functions need to be uniform for all + * classes. Of course, each derived class should overload these + * functions with their own version. + */ + + /* + * Journal functions. + * + * See also: VFS_OPERATION_* defines + * + * Overview: + * Each action performed on a location + * should be recorded, in both machine and human + * readable format. + * + * PRIVATE functions (suggested examples only, not mandatory): + * + * add_journal - Add journal entry + * flush_journal - Clear all journal entries for a location + * + * PUBLIC functions (mandatory): + * + * get_journal - Get journal entries for a location + */ + + /* Private, suggestions only */ + function add_journal ($data) {} + function flush_journal ($data) {} + + /*! + * @function get_journal + * @abstract Get journal entries for a location + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @optional type [0|1|2] + * 0 = any journal entries + * 1 = current journal entries + * 2 = deleted journal entries + * @result Array of arrays of journal entries + * The keys will vary depending on the implementation, + * with most attributes in this->attributes being valid, + * and these keys being mandatory: + * created - Datetime in SQL format that journal entry + * was entered + * comment - Human readable comment describing the action + * version - May be 0 if the derived class does not support + * versioning + */ + function get_journal ($data) { return array(array()); } + + /* + * Access checking functions. + * + * Overview: + * Each derived class should have some kind of + * user and group access control. This will + * usually be based directly on the ACL class. + * + * If $this->override_acl is set, acl_check() + * must always return True. + * + * PUBLIC functions (mandatory): + * + * acl_check() - Check access for a user to a given + */ + + /*! + * @function acl_check + * @abstract Check access for a user to a given location + * @discussion If $this->override_acl is set, always return True + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @required operation Operation to check access for. Any combination + * of the PHPGW_ACL_* defines, for example: + * PHPGW_ACL_READ + * PHPGW_ACL_READ|PHPGW_ACL_WRITE + * @optional owner_id phpGW ID to check access for. + * Default: $GLOBALS['phpgw_info']['user']['account_id'] + * @optional must_exist If set, string must exist, and acl_check() must + * return False if it doesn't. If must_exist isn't + * passed, and string doesn't exist, check the owner_id's + * access to the parent directory, if it exists. + * @result Boolean. True if access is ok, False otherwise. + */ + function acl_check ($data) { return True; } + + /* + * Operations functions. + * + * Overview: + * These functions perform basic file operations. + * + * PUBLIC functions (mandatory): + * + * read - Retreive file contents + * + * write - Store file contents + * + * touch - Create a file if it doesn't exist. + * Optionally, update the modified time and + * modified user if the file exists. + * + * cp - Copy location + * + * mv - Move location + * + * rm - Delete location + * + * mkdir - Create directory + */ + + /*! + * @function read + * @abstract Retreive file contents + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @result String. Contents of 'string', or False on error. + */ + function read ($data) { return False; } + + /*! + @function view + @abstract Views the specified file (does not return!) + @param string filename + @param relatives Relativity array + @result None (doesnt return) + @discussion By default this function just reads the file and + outputs it too the browser, after setting the content-type header + appropriately. For some other VFS implementations though, there + may be some more sensible way of viewing the file. + */ + function view($data) + { + + $default_values = array + ( + 'relatives' => array (RELATIVE_CURRENT) + ); + $data = array_merge ($this->default_values ($data, $default_values), $data); + + $GLOBALS['phpgw_info']['flags']['noheader'] = true; + $GLOBALS['phpgw_info']['flags']['nonavbar'] = true; + $GLOBALS['phpgw_info']['flags']['noappheader'] = true; + $GLOBALS['phpgw_info']['flags']['noappfooter'] = true; + $ls_array = $this->ls (array ( + 'string' => $data['string'], + 'relatives' => $data['relatives'], + 'checksubdirs' => False, + 'nofiles' => True + ) + ); + + if ($ls_array[0]['mime_type']) + { + $mime_type = $ls_array[0]['mime_type']; + } + elseif ($GLOBALS['settings']['viewtextplain']) + { + $mime_type = 'text/plain'; + } + + header('Content-type: ' . $mime_type); + echo $this->read (array ( + 'string' => $data['string'], + 'relatives' => $data['relatives'], + ) + ); + exit(); + } + + /*! + * @function write + * @abstract Store file contents + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @result Boolean. True on success, False otherwise. + */ + function write ($data) { return False; } + + /*! + * @function touch + * @abstract Create a file if it doesn't exist. + * Optionally, update the modified time and + * modified user if the file exists. + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @result Boolean. True on success, False otherwise. + */ + function touch ($data) { return False; } + + /*! + * @function cp + * @abstract Copy location + * @required from Path to location to copy from + * @required to Path to location to copy to + * @optional relatives Relativity array (default: RELATIVE_CURRENT, RELATIVE_CURRENT) + * @result Boolean. True on success, False otherwise. + */ + function cp ($data) { return False; } + + /*! + * @function mv + * @abstract Move location + * @required from Path to location to move from + * @required to Path to location to move to + * @optional relatives Relativity array (default: RELATIVE_CURRENT, RELATIVE_CURRENT) + * @result Boolean. True on success, False otherwise. + */ + function mv ($data) { return False; } + + /*! + * @function rm + * @abstract Delete location + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @result Boolean. True on success, False otherwise. + */ + function rm ($data) { return False; } + + /*! + * @function mkdir + * @abstract Create directory + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @result Boolean. True on success, False otherwise. + */ + function mkdir ($data) { return False; } + + /* + * Information functions. + * + * Overview: + * These functions set or return information about locations. + * + * PUBLIC functions (mandatory): + * + * set_attributes - Set attributes for a location + * + * file_exists - Check if a location (file or directory) exists + * + * get_size - Determine size of location + * + * ls - Return detailed information for location(s) + */ + + /*! + * @function set_attributes + * @abstract Set attributes for a location + * @discussion Valid attributes are listed in vfs->attributes, + * which may be extended by each derived class + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @optional attributes Keyed array of attributes. Key is attribute + * name, value is attribute value. + * @result Boolean. True on success, False otherwise. + */ + function set_attributes ($data) { return False; } + + /*! + * @function file_exists + * @abstract Check if a location (file or directory) exists + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @result Boolean. True if file exists, False otherwise. + */ + function file_exists ($data) { return False; } + + /*! + * @function get_size + * @abstract Determine size of location + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @optional checksubdirs Boolean. If set, include the size of + * all subdirectories recursively. + * @result Integer. Size of location in bytes. + */ + function get_size ($data) { return 0; } + + /*! + * @function ls + * @abstract Return detailed information for location(s) + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @optional checksubdirs Boolean. If set, return information for all + * subdirectories recursively. + * @optional mime String. Only return information for locations with MIME type + * specified. VFS classes must recogize these special types: + * "Directory" - Location is a directory + * " " - Location doesn't not have a MIME type + * @optional nofiles Boolean. If set and 'string' is a directory, return + * information about the directory, not the files in it. + * @result Array of arrays of file information. + * Keys may vary depending on the implementation, but must include + * at least those attributes listed in $this->attributes. + */ + function ls ($data) { return array(array()); } + + /* + * Linked directory functions. + * + * Overview: + * One 'special' feature that VFS classes must support + * is linking an otherwise unrelated 'real' directory into + * the virtual filesystem. For a traditional filesystem, this + * might mean linking /var/specialdir in the real filesystem to + * /home/user/specialdir in the VFS. For networked filesystems, + * this might mean linking 'another.host.com/dir' to + * 'this.host.com/home/user/somedir'. + * + * This is a feature that will be used mostly be administrators, + * in order to present a consistent view to users. Each VFS class + * will almost certainly need a new interface for the administrator + * to use to make links, but the concept is the same across all the + * VFS backends. + * + * Note that by using $this->linked_dirs in conjunction with + * $this->path_parts(), you can keep the implementation of linked + * directories very isolated in your code. + * + * PUBLIC functions (mandatory): + * + * make_link - Create a real to virtual directory link + */ + + /*! + * @function make_link + * @abstract Create a real to virtual directory link + * @required rdir Real directory to make link from/to + * @required vdir Virtual directory to make link to/from + * @optional relatives Relativity array (default: RELATIVE_CURRENT, RELATIVE_CURRENT) + * @result Boolean. True on success, False otherwise. + */ + function make_link ($data) { return False; } + + /* + * Miscellaneous functions. + * + * PUBLIC functions (mandatory): + * + * update_real - Ensure that information about a location is + * up-to-date + */ + + /*! + * @function update_real + * @abstract Ensure that information about a location is up-to-date + * @discussion Some VFS backends store information about locations + * in a secondary location, for example in a database + * or in a cache file. update_real() can be called to + * ensure that the information in the secondary location + * is up-to-date. + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @result Boolean. True on success, False otherwise. + */ + function update_real ($data) { return False; } + + /* + * SHARED FUNCTIONS + * + * The rest of the functions in this file are shared between + * all derived VFS classes. + * + * Derived classes can overload any of these functions if they + * see it fit to do so, as long as the prototypes and return + * values are the same for public functions, and the function + * accomplishes the same goal. + * + * PRIVATE functions: + * + * securitycheck - Check if location string is ok to use in VFS functions + * + * sanitize - Remove any possible security problems from a location + * string (i.e. remove leading '..') + * + * clean_string - Clean location string. This function is used if + * any special characters need to be escaped or removed + * before accessing a database, network protocol, etc. + * The default is to escape characters before doing an SQL + * query. + * + * getabsolutepath - Translate a location string depending on the + * relativity. This is the only function that is + * directly concerned with relativity. + * + * get_ext_mime_type - Return MIME type based on file extension + * + * PUBLIC functions (mandatory): + * + * set_relative - Sets the current relativity, the relativity used + * when RELATIVE_CURRENT is passed to a function + * + * get_relative - Return the current relativity + * + * path_parts - Return information about the component parts of a location string + * + * cd - Change current directory. This function is used to store the + * current directory in a standard way, so that it may be accessed + * throughout phpGroupWare to provide a consistent view for the user. + * + * pwd - Return current directory + * + * copy - Alias for cp + * + * move - Alias for mv + * + * delete - Alias for rm + * + * dir - Alias for ls + * + * command_line - Process and run a Unix-sytle command line + */ + + /* PRIVATE functions */ + + /*! + * @function securitycheck + * @abstract Check if location string is ok to use in VFS functions + * @discussion Checks for basic violations such as .. + * If securitycheck () fails, run your string through $this->sanitize () + * @required string Path to location + * @result Boolean. True if string is ok, False otherwise. + */ + function securitycheck ($data) + { + if (!is_array ($data)) + { + $data = array (); + } + + if (substr ($data['string'], 0, 1) == "\\" || strstr ($data['string'], "..") || strstr ($data['string'], "\\..") || strstr ($data['string'], ".\\.")) + { + return False; + } + else + { + return True; + } + } + + /*! + * @function sanitize + * @abstract Remove any possible security problems from a location + * string (i.e. remove leading '..') + * @discussion You should not pass all filenames through sanitize () + * unless you plan on rejecting .files. Instead, pass + * the name through securitycheck () first, and if it fails, + * pass it through sanitize. + * @required string Path to location + * @result String. 'string' with any security problems fixed. + */ + function sanitize ($data) + { + if (!is_array ($data)) + { + $data = array (); + } + + /* We use path_parts () just to parse the string, not translate paths */ + $p = $this->path_parts (array( + 'string' => $data['string'], + 'relatives' => array (RELATIVE_NONE) + ) + ); + + return (ereg_replace ("^\.+", '', $p->fake_name)); + } + + /*! + * @function clean_string + * @abstract Clean location string. This function is used if + * any special characters need to be escaped or removed + * before accessing a database, network protocol, etc. + * The default is to escape characters before doing an SQL + * query. + * @required string Location string to clean + * @result String. Cleaned version of 'string'. + */ + function clean_string ($data) + { + if (!is_array ($data)) + { + $data = array (); + } + + $string = $GLOBALS['phpgw']->db->db_addslashes ($data['string']); + + return $string; + } + + /*! + * @function getabsolutepath + * @abstract Translate a location string depending on the + * relativity. This is the only function that is + * directly concerned with relativity. + * @optional string Path to location, relative to mask[0]. + * Defaults to empty string. + * @optional mask Relativity array (default: RELATIVE_CURRENT) + * @optional fake Boolean. If set, returns the 'fake' path, + * i.e. /home/user/dir/file. This is not always + * possible, use path_parts() instead. + * @result String. Full fake or real path, or False on error. + */ + function getabsolutepath ($data) + { + if (!is_array ($data)) + { + $data = array (); + } + + $default_values = array + ( + 'string' => False, + 'mask' => array (RELATIVE_CURRENT), + 'fake' => True + ); + + $data = array_merge ($this->default_values ($data, $default_values), $data); + + $currentdir = $this->pwd (False); + + /* If they supply just VFS_REAL, we assume they want current relativity */ + if ($data['mask'][0] == VFS_REAL) + { + $data['mask'][0] |= RELATIVE_CURRENT; + } + + if (!$this->securitycheck (array( + 'string' => $data['string'] + )) + ) + { + return False; + } + + if ($data['mask'][0] & RELATIVE_NONE) + { + return $data['string']; + } + + if ($data['fake']) + { + $sep = '/'; + } + else + { + $sep = SEP; + } + + /* if RELATIVE_CURRENT, retrieve the current mask */ + if ($data['mask'][0] & RELATIVE_CURRENT) + { + $mask = $data['mask'][0]; + /* Respect any additional masks by re-adding them after retrieving the current mask*/ + $data['mask'][0] = $this->get_relative () + ($mask - RELATIVE_CURRENT); + } + + if ($data['fake']) + { + $basedir = "/"; + } + else + { + $basedir = $this->basedir . $sep; + + /* This allows all requests to use /'s */ + $data['string'] = preg_replace ("|/|", $sep, $data['string']); + } + + if (($data['mask'][0] & RELATIVE_PATH) && $currentdir) + { + $basedir = $basedir . $currentdir . $sep; + } + elseif (($data['mask'][0] & RELATIVE_USER) || ($data['mask'][0] & RELATIVE_USER_APP)) + { + $basedir = $basedir . $this->fakebase . $sep; + } + + if ($data['mask'][0] & RELATIVE_CURR_USER) + { + $basedir = $basedir . $this->working_lid . $sep; + } + + if (($data['mask'][0] & RELATIVE_USER) || ($data['mask'][0] & RELATIVE_USER_APP)) + { + $basedir = $basedir . $GLOBALS['phpgw_info']['user']['account_lid'] . $sep; + } + + if ($data['mask'][0] & RELATIVE_USER_APP) + { + $basedir = $basedir . "." . $GLOBALS['phpgw_info']['flags']['currentapp'] . $sep; + } + + /* Don't add string if it's a /, just for aesthetics */ + if ($data['string'] && $data['string'] != $sep) + { + $basedir = $basedir . $data['string']; + } + + /* Let's not return // */ + while (ereg ($sep . $sep, $basedir)) + { + $basedir = ereg_replace ($sep . $sep, $sep, $basedir); + } + + $basedir = ereg_replace ($sep . '$', '', $basedir); + + return $basedir; + } + + /*! + * @function get_ext_mime_type + * @abstract Return MIME type based on file extension + * @description Internal use only. Applications should call vfs->file_type () + * @author skeeter + * @required string Real path to file, with or without leading paths + * @result String. MIME type based on file extension. + */ + function get_ext_mime_type ($data) + { + if (!is_array ($data)) + { + $data = array (); + } + + $file=basename($data['string']); + $mimefile=PHPGW_API_INC.'/phpgw_mime.types'; + $fp=fopen($mimefile,'r'); + $contents = explode("\n",fread($fp,filesize($mimefile))); + fclose($fp); + + $parts=explode('.',strtolower($file)); + $ext=$parts[(sizeof($parts)-1)]; + + for($i=0;$i= 2) + { + for($j=1;$jrelative); + } + else + { + $this->relative = $data['mask']; + } + } + + /*! + * @function get_relative + * @abstract Return the current relativity + * @discussion Returns relativity bitmask, or the default + * of "completely relative" if unset + * @result Integer. One of the RELATIVE_* defines. + */ + function get_relative () + { + if (isset ($this->relative) && $this->relative) + { + return $this->relative; + } + else + { + return RELATIVE_ALL; + } + } + + /*! + * @function path_parts + * @abstract Return information about the component parts of a location string + * @discussion Most VFS functions call path_parts() with their 'string' and + * 'relatives' arguments before doing their work, in order to + * determine the file/directory to work on. + * @required string Path to location + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + * @optional object If set, return an object instead of an array + * @optional nolinks Don't check for linked directories (made with + * make_link()). Used internally to prevent recursion. + * @result Array or object. Contains the fake and real component parts of the path. + * @discussion Returned values are: + * mask + * outside + * fake_full_path + * fake_leading_dirs + * fake_extra_path BROKEN + * fake_name + * real_full_path + * real_leading_dirs + * real_extra_path BROKEN + * real_name + * fake_full_path_clean + * fake_leading_dirs_clean + * fake_extra_path_clean BROKEN + * fake_name_clean + * real_full_path_clean + * real_leading_dirs_clean + * real_extra_path_clean BROKEN + * real_name_clean + * "clean" values are run through vfs->clean_string () and + * are safe for use in SQL queries that use key='value' + * They should be used ONLY for SQL queries, so are used + * mostly internally + * mask is either RELATIVE_NONE or RELATIVE_NONE|VFS_REAL, + * and is used internally + * outside is boolean, True if 'relatives' contains VFS_REAL + */ + function path_parts ($data) + { + if (!is_array ($data)) + { + $data = array (); + } + + $default_values = array + ( + 'relatives' => array (RELATIVE_CURRENT), + 'object' => True, + 'nolinks' => False + ); + + $data = array_merge ($this->default_values ($data, $default_values), $data); + + $sep = SEP; + + $rarray['mask'] = RELATIVE_NONE; + + if (!($data['relatives'][0] & VFS_REAL)) + { + $rarray['outside'] = False; + $fake = True; + } + else + { + $rarray['outside'] = True; + $rarray['mask'] |= VFS_REAL; + } + + $string = $this->getabsolutepath (array( + 'string' => $data['string'], + 'mask' => array ($data['relatives'][0]), + 'fake' => $fake + ) + ); + + if ($fake) + { + $base_sep = '/'; + $base = '/'; + + $opp_base = $this->basedir . $sep; + + $rarray['fake_full_path'] = $string; + } + else + { + $base_sep = $sep; + if (substr($string,0,strlen($this->basedir)+1) == $this->basedir . $sep) + { + $base = $this->basedir . $sep; + } + else + { + $base = $sep; + } + + $opp_base = '/'; + + $rarray['real_full_path'] = $string; + } + + /* This is needed because of substr's handling of negative lengths */ + $baselen = strlen ($base); + $lastslashpos = @strrpos ($string, $base_sep); + $length = $lastslashpos < $baselen ? 0 : $lastslashpos - $baselen; + + $extra_path = $rarray['fake_extra_path'] = $rarray['real_extra_path'] = substr ($string, strlen ($base), $length); + if($string[1] != ':') + { + $name = $rarray['fake_name'] = $rarray['real_name'] = substr ($string, @strrpos ($string, $base_sep) + 1); + } + else + { + $name = $rarray['fake_name'] = $rarray['real_name'] = $string; + } + + if ($fake) + { + $rarray['real_extra_path'] ? $dispsep = $sep : $dispsep = ''; + $rarray['real_full_path'] = $opp_base . $rarray['real_extra_path'] . $dispsep . $rarray['real_name']; + if ($extra_path) + { + $rarray['fake_leading_dirs'] = $base . $extra_path; + $rarray['real_leading_dirs'] = $opp_base . $extra_path; + } + elseif (@strrpos ($rarray['fake_full_path'], $sep) == 0) + { + /* If there is only one $sep in the path, we don't want to strip it off */ + $rarray['fake_leading_dirs'] = $sep; + $rarray['real_leading_dirs'] = substr ($opp_base, 0, strlen ($opp_base) - 1); + } + else + { + /* These strip the ending / */ + $rarray['fake_leading_dirs'] = substr ($base, 0, strlen ($base) - 1); + $rarray['real_leading_dirs'] = substr ($opp_base, 0, strlen ($opp_base) - 1); + } + } + else + { + if($rarray['fake_name'][1] != ':') + { + $rarray['fake_full_path'] = $opp_base . $rarray['fake_extra_path'] . '/' . $rarray['fake_name']; + } + else + { + $rarray['fake_full_path'] = $rarray['fake_name']; + } + if ($extra_path) + { + $rarray['fake_leading_dirs'] = $opp_base . $extra_path; + $rarray['real_leading_dirs'] = $base . $extra_path; + } + else + { + $rarray['fake_leading_dirs'] = substr ($opp_base, 0, strlen ($opp_base) - 1); + $rarray['real_leading_dirs'] = substr ($base, 0, strlen ($base) - 1); + } + } + + /* We check for linked dirs made with make_link (). This could be better, but it works */ + if (!$data['nolinks']) + { + reset ($this->linked_dirs); + while (list ($num, $link_info) = each ($this->linked_dirs)) + { + if (ereg ("^$link_info[directory]/$link_info[name](/|$)", $rarray['fake_full_path'])) + { + $rarray['real_full_path'] = ereg_replace ("^$this->basedir", '', $rarray['real_full_path']); + $rarray['real_full_path'] = ereg_replace ("^$link_info[directory]" . SEP . "$link_info[name]", $link_info['link_directory'] . SEP . $link_info['link_name'], $rarray['real_full_path']); + + $p = $this->path_parts (array( + 'string' => $rarray['real_full_path'], + 'relatives' => array (RELATIVE_NONE|VFS_REAL), + 'nolinks' => True + ) + ); + + $rarray['real_leading_dirs'] = $p->real_leading_dirs; + $rarray['real_extra_path'] = $p->real_extra_path; + $rarray['real_name'] = $p->real_name; + } + } + } + + /* + We have to count it before because new keys will be added, + which would create an endless loop + */ + $count = count ($rarray); + reset ($rarray); + for ($i = 0; (list ($key, $value) = each ($rarray)) && $i != $count; $i++) + { + $rarray[$key . '_clean'] = $this->clean_string (array ('string' => $value)); + } + + if ($data['object']) + { + $robject = new path_class; + + reset ($rarray); + while (list ($key, $value) = each ($rarray)) + { + $robject->$key = $value; + } + } + + /* + echo "
fake_full_path: $rarray[fake_full_path] +
fake_leading_dirs: $rarray[fake_leading_dirs] +
fake_extra_path: $rarray[fake_extra_path] +
fake_name: $rarray[fake_name] +
real_full_path: $rarray[real_full_path] +
real_leading_dirs: $rarray[real_leading_dirs] +
real_extra_path: $rarray[real_extra_path] +
real_name: $rarray[real_name]"; + */ + + if ($data['object']) + { + return ($robject); + } + else + { + return ($rarray); + } + } + + /*! + * @function cd + * @abstract Change current directory. This function is used to store the + * current directory in a standard way, so that it may be accessed + * throughout phpGroupWare to provide a consistent view for the user. + * @discussion To cd to the root '/', use: + * cd (array( + * 'string' => '/', + * 'relative' => False, + * 'relatives' => array (RELATIVE_NONE) + * )); + * @optional string Directory location to cd into. Default is '/'. + * @optional relative If set, add target to current path. + * Else, pass 'relative' as mask to getabsolutepath() + * Default is True. + * @optional relatives Relativity array (default: RELATIVE_CURRENT) + */ + function cd ($data = '') + { + if (!is_array ($data)) + { + $noargs = 1; + $data = array (); + } + + $default_values = array + ( + 'string' => '/', + 'relative' => True, + 'relatives' => array (RELATIVE_CURRENT) + ); + + $data = array_merge ($this->default_values ($data, $default_values), $data); + + if ($data['relatives'][0] & VFS_REAL) + { + $sep = SEP; + } + else + { + $sep = '/'; + } + + if ($data['relative'] == 'relative' || $data['relative'] == True) + { + /* if 'string' is "/" and 'relative' is set, we cd to the user/group home dir */ + if ($data['string'] == '/') + { + $data['relatives'][0] = RELATIVE_USER; + $basedir = $this->getabsolutepath (array( + 'string' => False, + 'mask' => array ($data['relatives'][0]), + 'fake' => True + ) + ); + } + else + { + $currentdir = $GLOBALS['phpgw']->session->appsession('vfs',''); + $basedir = $this->getabsolutepath (array( + 'string' => $currentdir . $sep . $data['string'], + 'mask' => array ($data['relatives'][0]), + 'fake' => True + ) + ); + } + } + else + { + $basedir = $this->getabsolutepath (array( + 'string' => $data['string'], + 'mask' => array ($data['relatives'][0]) + ) + ); + } + + $GLOBALS['phpgw']->session->appsession('vfs','',$basedir); + + return True; + } + + /*! + * @function pwd + * @abstract Return current directory + * @optional full If set, return full fake path, else just + * the extra dirs (False strips the leading /). + * Default is True. + * @result String. The current directory. + */ + function pwd ($data = '') + { + if (!is_array ($data)) + { + $data = array (); + } + + $default_values = array + ( + 'full' => True + ); + + $data = array_merge ($this->default_values ($data, $default_values), $data); + + $currentdir = $GLOBALS['phpgw']->session->appsession('vfs',''); + + if (!$data['full']) + { + $currentdir = ereg_replace ("^/", '', $currentdir); + } + + if ($currentdir == '' && $data['full']) + { + $currentdir = '/'; + } + + $currentdir = trim ($currentdir); + + return $currentdir; + } + + /*! + * @function copy + * @abstract shortcut to cp + */ + function copy ($data) + { + return $this->cp ($data); + } + + /*! + * @function move + * @abstract shortcut to mv + */ + function move ($data) + { + return $this->mv ($data); + } + + /*! + * @function delete + * @abstract shortcut to rm + */ + function delete ($data) + { + return $this->rm ($data); + } + + /*! + * @function dir + * @abstract shortcut to ls + */ + function dir ($data) + { + return $this->ls ($data); + } + + /*! + * @function command_line + * @abstract Process and run a Unix-sytle command line + * @discussion EXPERIMENTAL. DANGEROUS. DO NOT USE THIS UNLESS YOU + * KNOW WHAT YOU'RE DOING! + * This is mostly working, but the command parser needs + * to be improved to take files with spaces into + * consideration (those should be in ""). + * @required command_line Unix-style command line with one of the + * commands in the $args array + * @result The return value of the actual VFS call + */ + function command_line ($data) + { + if (!is_array ($data)) + { + $data = array (); + } + + $args = array + ( + array ('name' => 'mv', 'params' => 2), + array ('name' => 'cp', 'params' => 2), + array ('name' => 'rm', 'params' => 1), + array ('name' => 'ls', 'params' => -1), + array ('name' => 'du', 'params' => 1, 'func' => get_size), + array ('name' => 'cd', 'params' => 1), + array ('name' => 'pwd', 'params' => 0), + array ('name' => 'cat', 'params' => 1, 'func' => read), + array ('name' => 'file', 'params' => 1, 'func' => file_type), + array ('name' => 'mkdir', 'params' => 1), + array ('name' => 'touch', 'params' => 1) + ); + + if (!$first_space = strpos ($data['command_line'], ' ')) + { + $first_space = strlen ($data['command_line']); + } + if ((!$last_space = strrpos ($data['command_line'], ' ')) || ($last_space == $first_space)) + { + $last_space = strlen ($data['command_line']) + 1; + } + $argv[0] = substr ($data['command_line'], 0, $first_space); + if (strlen ($argv[0]) != strlen ($data['command_line'])) + { + $argv[1] = substr ($data['command_line'], $first_space + 1, $last_space - ($first_space + 1)); + if ((strlen ($argv[0]) + 1 + strlen ($argv[1])) != strlen ($data['command_line'])) + { + $argv[2] = substr ($data['command_line'], $last_space + 1); + } + } + $argc = count ($argv); + + reset ($args); + while (list (,$arg_info) = each ($args)) + { + if ($arg_info['name'] == $argv[0]) + { + $command_ok = 1; + if (($argc == ($arg_info['params'] + 1)) || ($arg_info['params'] == -1)) + { + $param_count_ok = 1; + } + break; + } + } + + if (!$command_ok) + { +// return E_VFS_BAD_COMMAND; + return False; + } + if (!$param_count_ok) + { +// return E_VFS_BAD_PARAM_COUNT; + return False; + } + + for ($i = 1; $i != ($arg_info['params'] + 1); $i++) + { + if (substr ($argv[$i], 0, 1) == "/") + { + $relatives[] = RELATIVE_NONE; + } + else + { + $relatives[] = RELATIVE_ALL; + } + } + + $func = $arg_info['func'] ? $arg_info['func'] : $arg_info['name']; + + if (!$argv[2]) + { + $rv = $this->$func (array( + 'string' => $argv[1], + 'relatives' => $relatives + ) + ); + } + else + { + $rv = $this->$func (array( + 'from' => $argv[1], + 'to' => $argv[2], + 'relatives' => $relatives + ) + ); + } + + return ($rv); + } + + /* Helper functions, not public */ + + function default_values ($data, $default_values) + { + for ($i = 0; list ($key, $value) = each ($default_values); $i++) + { + if (!isset ($data[$key])) + { + $data[$key] = $value; + } + } + + return $data; + } + } + +?> diff --git a/setup/setup_demo.php b/setup/setup_demo.php new file mode 100644 index 0000000000..e84198cd90 --- /dev/null +++ b/setup/setup_demo.php @@ -0,0 +1,183 @@ + True, + 'nonavbar' => True, + 'currentapp' => 'home', + 'noapi' => True + ); + include('./inc/functions.inc.php'); + + // Authorize the user to use setup app and load the database + // Does not return unless user is authorized + if(!$GLOBALS['phpgw_setup']->auth('Config') || get_var('cancel',Array('POST'))) + { + Header('Location: index.php'); + exit; + } + + if(!get_var('submit',Array('POST'))) + { + $tpl_root = $GLOBALS['phpgw_setup']->html->setup_tpl_dir('setup'); + $setup_tpl = CreateObject('setup.Template',$tpl_root); + $setup_tpl->set_file(array( + 'T_head' => 'head.tpl', + 'T_footer' => 'footer.tpl', + 'T_alert_msg' => 'msg_alert_msg.tpl', + 'T_login_main' => 'login_main.tpl', + 'T_login_stage_header' => 'login_stage_header.tpl', + 'T_setup_demo' => 'setup_demo.tpl' + )); + $setup_tpl->set_block('T_login_stage_header','B_multi_domain','V_multi_domain'); + $setup_tpl->set_block('T_login_stage_header','B_single_domain','V_single_domain'); + + $GLOBALS['phpgw_setup']->html->show_header(lang('Demo Server Setup')); + + $setup_tpl->set_var('action_url','setup_demo.php'); + $setup_tpl->set_var('description',lang('This will create 1 admin account and 3 demo accounts
The username/passwords are: demo/guest, demo2/guest and demo3/guest.')); + $setup_tpl->set_var('lang_deleteall',lang('Delete all existing SQL accounts, groups, ACLs and preferences (normally not necessary)?')); + + $setup_tpl->set_var('detailadmin',lang('Details for Admin account')); + $setup_tpl->set_var('adminusername',lang('Admin username')); + $setup_tpl->set_var('adminfirstname',lang('Admin first name')); + $setup_tpl->set_var('adminlastname',lang('Admin last name')); + $setup_tpl->set_var('adminpassword',lang('Admin password')); + $setup_tpl->set_var('adminpassword2',lang('Re-enter password')); + $setup_tpl->set_var('create_demo_accounts',lang('Create demo accounts')); + + $setup_tpl->set_var('lang_submit',lang('Save')); + $setup_tpl->set_var('lang_cancel',lang('Cancel')); + $setup_tpl->pparse('out','T_setup_demo'); + $GLOBALS['phpgw_setup']->html->show_footer(); + } + else + { + /* Posted admin data */ + $passwd = get_var('passwd',Array('POST')); + $passwd2 = get_var('passwd2',Array('POST')); + $username = get_var('username',Array('POST')); + $fname = get_var('fname',Array('POST')); + $lname = get_var('lname',Array('POST')); + + if($passwd != $passwd2) + { + echo lang('Passwords did not match, please re-enter') . '.'; + exit; + } + if(!$username) + { + echo lang('You must enter a username for the admin') . '.'; + exit; + } + + $GLOBALS['phpgw_setup']->loaddb(); + /* Begin transaction for acl, etc */ + $GLOBALS['phpgw_setup']->db->transaction_begin(); + + if($_POST['delete_all']) + { + /* Now, clear out existing tables */ + $GLOBALS['phpgw_setup']->db->query('DELETE FROM phpgw_accounts'); + $GLOBALS['phpgw_setup']->db->query('DELETE FROM phpgw_preferences'); + $GLOBALS['phpgw_setup']->db->query('DELETE FROM phpgw_acl'); + } + /* Create the demo groups */ + $defaultgroupid = (int)$GLOBALS['phpgw_setup']->add_account('Default','Default','Group',False,False); + $admingroupid = (int)$GLOBALS['phpgw_setup']->add_account('Admins','Admin','Group',False,False); + + if (!$defaultgroupid || !$admingroupid) + { + echo '

'.lang('Error in group-creation !!!')."

\n"; + echo '

'.lang('click here to return to setup.')."

\n"; + $GLOBALS['phpgw_setup']->db->transaction_abort(); + exit; + } + + /* Group perms for the default group */ + $GLOBALS['phpgw_setup']->add_acl(array('addressbook','calendar','infolog','email','preferences'),'run',$defaultgroupid); + + // give admin access to all apps, to save us some support requests + $all_apps = array(); + $GLOBALS['phpgw_setup']->db->query('SELECT app_name FROM phpgw_applications WHERE app_enabled<3'); + while ($GLOBALS['phpgw_setup']->db->next_record()) + { + $all_apps[] = $GLOBALS['phpgw_setup']->db->f('app_name'); + } + $GLOBALS['phpgw_setup']->add_acl($all_apps,'run',$admingroupid); + + function insert_default_prefs($accountid) + { + $defaultprefs = array( + 'common' => array( + 'maxmatchs' => 15, + 'template_set' => 'idots', + 'theme' => 'idots', + 'navbar_format' => 'icons', + 'tz_offset' => 0, + 'dateformat' => 'Y/m/d', + 'timeformat' => '24', + 'lang' => get_var('ConfigLang',Array('POST','COOKIE'),'en'), + 'default_app' => 'calendar', + 'currency' => '$', + 'show_help' => True, + ), + 'calendar' => array( + 'workdaystarts' => 9, + 'workdayends' => 17, + 'weekdaystarts' => 'Monday', + 'defaultcalendar' => 'day', + 'planner_start_with_group' => $GLOBALS['defaultgroupid'], + ), + ); + + foreach ($defaultprefs as $app => $prefs) + { + $prefs = $GLOBALS['phpgw_setup']->db->db_addslashes(serialize($prefs)); + $GLOBALS['phpgw_setup']->db->query("INSERT INTO phpgw_preferences(preference_owner,preference_app,preference_value) VALUES($accountid,'$app','$prefs')",__FILE__,__LINE__); + } + } + insert_default_prefs(-2); // set some default prefs + + /* Creation of the demo accounts is optional - the checkbox is on by default. */ + if(get_var('create_demo',Array('POST'))) + { + // Create 3 demo accounts + $GLOBALS['phpgw_setup']->add_account('demo','Demo','Account','guest'); + $GLOBALS['phpgw_setup']->add_account('demo2','Demo2','Account','guest'); + $GLOBALS['phpgw_setup']->add_account('demo3','Demo3','Account','guest'); + } + + /* Create records for administrator account, with Admins as primary and Default as additional group */ + $accountid = $GLOBALS['phpgw_setup']->add_account($username,$fname,$lname,$passwd,'Admins',True); + if (!$accountid) + { + echo '

'.lang('Error in admin-creation !!!')."

\n"; + echo '

'.lang('click here to return to setup.')."

\n"; + $GLOBALS['phpgw_setup']->db->transaction_abort(); + exit; + } + $GLOBALS['phpgw_setup']->add_acl('phpgw_group',$admingroupid,$accountid); + $GLOBALS['phpgw_setup']->add_acl('phpgw_group',$defaultgroupid,$accountid); + + /* Clear the access log, since these are all new users anyway */ + $GLOBALS['phpgw_setup']->db->query('DELETE FROM phpgw_access_log'); + + $GLOBALS['phpgw_setup']->db->transaction_commit(); + + Header('Location: index.php'); + exit; + } +?>