From add5646e48088a729772971e291f2871a8a3a215 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 20 Mar 2008 18:43:11 +0000 Subject: [PATCH] fixed a few errors in the etemplate editor --- etemplate/inc/class.boetemplate.inc.php | 20 +- etemplate/inc/class.db_tools.inc.php | 1841 ++++++++------- etemplate/inc/class.editor.inc.php | 2887 +++++++++++------------ etemplate/inc/class.soetemplate.inc.php | 55 +- etemplate/inc/class.uietemplate.inc.php | 11 +- 5 files changed, 2406 insertions(+), 2408 deletions(-) diff --git a/etemplate/inc/class.boetemplate.inc.php b/etemplate/inc/class.boetemplate.inc.php index 76a55da95c..4c4a185d44 100644 --- a/etemplate/inc/class.boetemplate.inc.php +++ b/etemplate/inc/class.boetemplate.inc.php @@ -1,15 +1,15 @@ - * @copyright 2002-8 by RalfBecker@outdoor-training.de - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api - * @version $Id$ - */ + * eGroupWare EditableTemplates - Business Objects + * + * @link http://www.egroupware.org + * @author Ralf Becker + * @copyright 2002-8 by RalfBecker@outdoor-training.de + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @version $Id$ + */ /** * Business Object for eTemplates, extending the Storage Object diff --git a/etemplate/inc/class.db_tools.inc.php b/etemplate/inc/class.db_tools.inc.php index 49a4036b7b..3904ebee26 100644 --- a/etemplate/inc/class.db_tools.inc.php +++ b/etemplate/inc/class.db_tools.inc.php @@ -1,841 +1,838 @@ - * @version $Id$ - */ +/** + * eGroupWare eTemplates - DB-Tools + * + * @link http://www.egroupware.org + * @author Ralf Becker + * @copyright 2002-8 by RalfBecker@outdoor-training.de + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage tools + * @version $Id$ + */ + +/** + * db-tools: creats and modifys eGroupWare schem-files (to be installed via setup) + */ +class db_tools +{ + var $public_functions = array + ( + 'edit' => True, + 'needs_save' => True, + ); + + var $debug = 0; + var $editor; // editor eTemplate + var $data; // Table definitions + var $app; // used app + var $table; // used table + var $types = array( + 'varchar' => 'varchar', + 'int' => 'int', + 'auto' => 'auto', + 'blob' => 'blob', + 'char' => 'char', + 'date' => 'date', + 'decimal' => 'decimal', + 'float' => 'float', + 'longtext' => 'longtext', + 'text' => 'text', + 'timestamp' => 'timestamp', + 'bool' => 'boolean', +// 'abstime' => 'abstime (mysql:timestamp)', + ); /** - * db-tools: creats and modifys eGroupWare schem-files (to be installed via setup) - * - * @package etemplate - * @subpackage tools - * @author RalfBecker-AT-outdoor-training.de - * @license GPL + * constructor of class */ - class db_tools + function db_tools() { - var $public_functions = array - ( - 'edit' => True, - 'needs_save' => True, - ); + $this->editor = new etemplate.etemplate('etemplate.db-tools.edit'); + $this->data = array(); - var $debug = 0; - var $editor; // editor eTemplate - var $data; // Table definitions - var $app; // used app - var $table; // used table - var $types = array( - 'varchar' => 'varchar', - 'int' => 'int', - 'auto' => 'auto', - 'blob' => 'blob', - 'char' => 'char', - 'date' => 'date', - 'decimal' => 'decimal', - 'float' => 'float', - 'longtext' => 'longtext', - 'text' => 'text', - 'timestamp' => 'timestamp', - 'bool' => 'boolean', -// 'abstime' => 'abstime (mysql:timestamp)', - ); - - /** - * constructor of class - */ - function db_tools() + if (!is_array($GLOBALS['egw_info']['apps']) || !count($GLOBALS['egw_info']['apps'])) { - $this->editor =& CreateObject('etemplate.etemplate','etemplate.db-tools.edit'); - $this->data = array(); - - if (!is_array($GLOBALS['egw_info']['apps']) || !count($GLOBALS['egw_info']['apps'])) - { - ExecMethod('phpgwapi.applications.read_installed_apps'); - } - $GLOBALS['egw_info']['flags']['app_header'] = - $GLOBALS['egw_info']['apps']['etemplate']['title'].' - '.lang('DB-Tools'); + ExecMethod('phpgwapi.applications.read_installed_apps'); } + $GLOBALS['egw_info']['flags']['app_header'] = + $GLOBALS['egw_info']['apps']['etemplate']['title'].' - '.lang('DB-Tools'); + } - /** - * table editor (and the callback/submit-method too) - */ - function edit($content='',$msg = '') + /** + * table editor (and the callback/submit-method too) + */ + function edit($content='',$msg = '') + { + if (isset($_GET['app'])) + { + $this->app = $_GET['app']; + } + if (is_array($content)) { - if (isset($_GET['app'])) - { - $this->app = $_GET['app']; - } - if (is_array($content)) - { - if ($this->debug) - { - echo "content ="; _debug_array($content); - } - $this->app = $content['app']; // this is what the user selected - $this->table = $content['table_name']; - $posted_app = $content['posted_app']; // this is the old selection - $posted_table = $content['posted_table']; - } - if ($posted_app && $posted_table && // user changed app or table - ($posted_app != $this->app || $posted_table != $this->table)) - { - if ($this->needs_save('',$posted_app,$posted_table,$this->content2table($content))) - { - return; - } - $this->renames = array(); - } - if (!$this->app) - { - $this->table = ''; - $table_names = array('' => lang('none')); - } - else - { - $this->read($this->app,$this->data); - - foreach($this->data as $name => $table) - { - $table_names[$name] = $name; - } - } - if (!$this->table || $this->app != $posted_app) - { - reset($this->data); - list($this->table) = each($this->data); // use first table - } - elseif ($this->app == $posted_app && $posted_table) - { - $this->data[$posted_table] = $this->content2table($content); - } - if ($content['write_tables']) - { - if ($this->needs_save('',$this->app,$this->table,$this->data[$posted_table])) - { - return; - } - $msg .= lang('Table unchanged, no write necessary !!!'); - } - elseif ($content['delete']) - { - list($col) = each($content['delete']); - - @reset($this->data[$posted_table]['fd']); - while ($col-- > 0 && list($key,$data) = @each($this->data[$posted_table]['fd'])) ; - - unset($this->data[$posted_table]['fd'][$key]); - $this->changes[$posted_table][$key] = '**deleted**'; - } - elseif ($content['add_column']) - { - $this->data[$posted_table]['fd'][''] = array(); - } - elseif ($content['add_table'] || $content['import']) - { - if (!$this->app) - { - $msg .= lang('Select an app first !!!'); - } - elseif (!$content['new_table_name']) - { - $msg .= lang('Please enter table-name first !!!'); - } - elseif ($content['add_table']) - { - $this->table = $content['new_table_name']; - $this->data[$this->table] = array('fd' => array(),'pk' =>array(),'ix' => array(),'uc' => array(),'fk' => array()); - $msg .= lang('New table created'); - } - else // import - { - $oProc =& CreateObject('phpgwapi.schema_proc',$GLOBALS['egw_info']['server']['db_type']); - if (method_exists($oProc,'GetTableDefinition')) - { - $this->data[$this->table = $content['new_table_name']] = $oProc->GetTableDefinition($content['new_table_name']); - } - else // to support eGW 1.0 - { - $oProc->m_odb = clone($GLOBALS['egw']->db); - $oProc->m_oTranslator->_GetColumns($oProc,$content['new_table_name'],$nul); - - while (list($key,$tbldata) = each ($oProc->m_oTranslator->sCol)) - { - $cols .= $tbldata; - } - eval('$cols = array('. $cols . ');'); - - $this->data[$this->table = $content['new_table_name']] = array( - 'fd' => $cols, - 'pk' => $oProc->m_oTranslator->pk, - 'fk' => $oProc->m_oTranslator->fk, - 'ix' => $oProc->m_oTranslator->ix, - 'uc' => $oProc->m_oTranslator->uc - ); - } - } - } - elseif ($content['editor']) - { - ExecMethod('etemplate.editor.edit'); - return; - } - $add_index = isset($content['add_index']); - - // from here on, filling new content for eTemplate - $content = array( - 'msg' => $msg, - 'table_name' => $this->table, - 'app' => $this->app, - ); - if (!isset($table_names[$this->table])) // table is not jet written - { - $table_names[$this->table] = $this->table; - } - $sel_options = array( - 'table_name' => $table_names, - 'type' => $this->types - ); - if ($this->table != '' && isset($this->data[$this->table])) - { - $content += $this->table2content($this->data[$this->table],$sel_options['Index'],$add_index); - } - $no_button = array( ); - if (!$this->app || !$this->table) - { - $no_button += array('write_tables' => True); - } if ($this->debug) { - echo 'editor.edit: content ='; _debug_array($content); + echo "content ="; _debug_array($content); } - $this->editor->exec('etemplate.db_tools.edit',$content,$sel_options,$no_button, - array('posted_table' => $this->table,'posted_app' => $this->app,'changes' => $this->changes)); + $this->app = $content['app']; // this is what the user selected + $this->table = $content['table_name']; + $posted_app = $content['posted_app']; // this is the old selection + $posted_table = $content['posted_table']; } - - /** - * checks if table was changed and if so offers user to save changes - * - * @param array $cont the content of the form (if called by process_exec) - * @param string $posted_app the app the table is from - * @param string $posted_table the table-name - * @param array $edited_table the edited table-definitions - * @return only if no changes - */ - function needs_save($cont='',$posted_app='',$posted_table='',$edited_table='',$msg='') + if ($posted_app && $posted_table && // user changed app or table + ($posted_app != $this->app || $posted_table != $this->table)) { - //echo "

db_tools::needs_save(cont,'$posted_app','$posted_table',edited_table,'$msg')

cont=\n"; _debug_array($cont); echo "edited_table="; _debug_array($edited_table); - if (!$posted_app && is_array($cont)) + if ($this->needs_save('',$posted_app,$posted_table,$this->content2table($content))) { - if (isset($cont['yes'])) - { - $this->app = $cont['app']; - $this->table = $cont['table']; - $this->read($this->app,$this->data); - $this->data[$this->table] = $cont['edited_table']; - $this->changes = $cont['changes']; - if ($cont['new_version']) - { - $this->update($this->app,$this->data,$cont['new_version']); - } - else - { - foreach($this->data as $tname => $tinfo) - { - $tables .= ($tables ? ',' : '') . "'$tname'"; - } - $this->setup_version($this->app,'',$tables); - } - if (!$this->write($this->app,$this->data)) - { - $this->app = $cont['new_app']; // these are the ones, the users whiches to change too - $this->table = $cont['new_table']; - - return $this->needs_save('',$cont['app'],$cont['table'],$cont['edited_table'], - lang('Error: writing file (no write-permission for the webserver) !!!')); - } - $msg = lang('File writen'); - } - $this->changes = array(); - // return to edit with everything set, so the user gets the table he asked for - $this->edit(array( - 'app' => $cont['new_app'], - 'table_name' => $cont['app']==$cont['new_app'] ? $cont['new_table'] : '', - 'posted_app' => $cont['new_app'] - ),$msg); - - return True; + return; } - $new_app = $this->app; // these are the ones, the users whiches to change too - $new_table = $this->table; - - $this->app = $posted_app; - $this->data = array(); - $this->read($posted_app,$this->data); - - if (isset($this->data[$posted_table]) && - $this->tables_identical($this->data[$posted_table],$edited_table)) - { - if ($new_app != $this->app) // are we changeing the app, or hit the user just write - { - $this->app = $new_app; // if we change init the data empty - $this->data = array(); - } - return False; // continue edit - } - $content = array( - 'msg' => $msg, - 'app' => $posted_app, - 'table' => $posted_table, - 'version' => $this->setup_version($posted_app) - ); - $preserv = $content + array( - 'new_app' => $new_app, - 'new_table' => $new_table, - 'edited_table' => $edited_table, - 'changes' => $this->changes - ); - $new_version = explode('.',$content['version']); - $minor = count($new_version)-1; - $new_version[$minor] = sprintf('%03d',1+$new_version[$minor]); - $content['new_version'] = implode('.',$new_version); - - $tmpl =& new etemplate('etemplate.db-tools.ask_save'); - - if (!file_exists(EGW_SERVER_ROOT."/$posted_app/setup/tables_current.inc.php")) - { - $tmpl->disable_cells('version'); - $tmpl->disable_cells('new_version'); - } - $tmpl->exec('etemplate.db_tools.needs_save',$content,array(),array(),$preserv); - - return True; // dont continue in edit + $this->renames = array(); } - - /** - * checks if there is an index (only) on $col (not a multiple index incl. $col) - * - * @param string $col column name - * @param array $index ix or uc array of table-defintion - * @param string &$options db specific options - * @return True if $col has a single index - */ - function has_single_index($col,$index,&$options) + if (!$this->app) { - foreach($index as $in) - { - if ($in == $col || is_array($in) && $in[0] == $col && !isset($in[1])) - { - if ($in != $col && isset($in['options'])) - { - foreach($in['options'] as $db => $opts) - { - $options[] = $db.'('.(is_array($opts)?implode(',',$opts):$opts).')'; - } - $options = implode(', ',$options); - } - return True; - } - } - return False; + $this->table = ''; + $table_names = array('' => lang('none')); } - - /** - * creates content-array from a table - * - * @param array $table table-definition, eg. $phpgw_baseline[$table_name] - * @param array &$columns returns array with column-names - * @param bool $extra_index add an additional index-row - * @return array content-array to call exec with - */ - function table2content($table,&$columns,$extra_index=False) + else { - $content = $columns = array(); - for ($n = 1; list($col_name,$col_defs) = each($table['fd']); ++$n) - { - $col_defs['name'] = $col_name; - $col_defs['pk'] = in_array($col_name,$table['pk']); - $col_defs['uc'] = $this->has_single_index($col_name,$table['uc'],$col_defs['options']); - $col_defs['ix'] = $this->has_single_index($col_name,$table['ix'],$col_defs['options']); - $col_defs['fk'] = $table['fk'][$col_name]; - if (isset($col_defs['default']) && $col_defs['default'] == '') - { - $col_defs['default'] = is_int($col_defs['default']) ? '0' : "''"; // spezial value for empty, but set, default - } - $col_defs['notnull'] = isset($col_defs['nullable']) && !$col_defs['nullable']; + $this->read($this->app,$this->data); - $col_defs['n'] = $n; - - $content["Row$n"] = $col_defs; - - $columns[$n] = $col_name; - } - $n = 2; - foreach(array('uc','ix') as $type) + foreach($this->data as $name => $table) { - foreach($table[$type] as $index) - { - if (is_array($index) && isset($index[1])) // multicolum index - { - $content['Index'][$n]['unique'] = $type == 'uc'; - $content['Index'][$n]['n'] = $n - 1; - foreach($index as $col) - { - $content['Index'][$n][] = array_search($col,$columns); - } - ++$n; - } - } + $table_names[$name] = $name; } - if ($extra_index) - { - $content['Index'][$n]['n'] = $n-1; - } - if ($this->debug >= 3) - { - echo "

table2content(,,'$extra_index'): content ="; _debug_array($content); - echo "

columns ="; _debug_array($columns); - } - return $content; } - - /** - * creates table-definition from posted content - * - * It sets some reasonalbe defaults for not set precisions (else setup will not install) - * - * @param array $content posted content-array - * @return table-definition - */ - function content2table($content) + if (!$this->table || $this->app != $posted_app) { - if (!is_array($this->data)) - { - $this->read($content['posted_app'],$this->data); - } - $old_cols = $this->data[$posted_table = $content['posted_table']]['fd']; - $this->changes = $content['changes']; - - $table = array(); - $table['fd'] = array(); // do it in the default order of tables_* - $table['pk'] = array(); - $table['fk'] = array(); - $table['ix'] = array(); - $table['uc'] = array(); - for (reset($content),$n = 1; isset($content["Row$n"]); ++$n) - { - $col = $content["Row$n"]; - - if ($col['type'] == 'auto') // auto columns are the primary key and not null! - { - $col['pk'] = $col['notnull'] = true; // set it, in case the user forgot - } - - while ((list($old_name,$old_col) = @each($old_cols)) && - $this->changes[$posted_table][$old_name] == '**deleted**') ; - - if (($name = $col['name']) != '') // ignoring lines without column-name - { - if ($col['name'] != $old_name && $n <= count($old_cols)) // column renamed --> remeber it - { - $this->changes[$posted_table][$old_name] = $col['name']; - //echo "

content2table: $posted_table.$old_name renamed to $col[name]

\n"; - } - if ($col['precision'] <= 0) - { - switch ($col['type']) // set some defaults for precision, else setup fails - { - case 'float': - case 'int': $col['precision'] = 4; break; - case 'char': $col['precision'] = 1; break; - case 'varchar': $col['precision'] = 255; break; - } - } - while (list($prop,$val) = each($col)) - { - switch ($prop) - { - case 'default': - case 'type': // selectbox ensures type is not empty - case 'precision': - case 'scale': -// case 'nullable': - if ($val != '' || $prop == 'nullable') - { - $table['fd'][$name][$prop] = $prop=='default'&& $val=="''" ? '' : $val; - } - break; - case 'notnull': - if ($val) - { - $table['fd'][$name]['nullable'] = False; - } - break; - case 'pk': - case 'uc': - case 'ix': - if ($val) - { - if ($col['options']) - { - $opts = array(); - foreach(explode(',',$col['options']) as $opt) - { - list($db,$opt) = split('[(:)]',$opt); - $opts[$db] = is_numeric($opt) ? intval($opt) : $opt; - } - $table[$prop][] = array( - $name, - 'options' => $opts - ); - } - else - { - $table[$prop][] = $name; - } - } - break; - case 'fk': - if ($val != '') - { - $table['fk'][$name] = $val; - } - break; - } - } - $num2col[$n] = $col['name']; - } - } - foreach($content['Index'] as $n => $index) - { - $idx_arr = array(); - foreach($index as $key => $num) - { - if (is_numeric($key) && $num && @$num2col[$num]) - { - $idx_arr[] = $num2col[$num]; - } - } - if (count($idx_arr) && !isset($content['delete_index'][$n])) - { - if ($index['unique']) - { - $table['uc'][] = $idx_arr; - } - else - { - $table['ix'][] = $idx_arr; - } - } - } - if ($this->debug >= 2) - { - echo "

content2table: table ="; _debug_array($table); - echo "

changes = "; _debug_array($this->changes); - } - return $table; + reset($this->data); + list($this->table) = each($this->data); // use first table } - - /** - * includes $app/setup/tables_current.inc.php - * @param string $app application name - * @param array &$phpgw_baseline where to return the data - * @return boolean True if file found, False else - */ - function read($app,&$phpgw_baseline) + elseif ($this->app == $posted_app && $posted_table) { - $file = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; - - $phpgw_baseline = array(); - - if ($app != '' && file_exists($file)) - { - include($file); - } - else - { - return False; - } - if ($this->debug >= 5) - { - echo "

read($app): file='$file', phpgw_baseline ="; - _debug_array($phpgw_baseline); - } - return True; + $this->data[$posted_table] = $this->content2table($content); } - - /** - * returns an array as string in php-notation - * - * @param array $arr - * @param int $depth for idention - * @param string $parent - * @return string - */ - function write_array($arr,$depth,$parent='') + if ($content['write_tables']) { - if (in_array($parent,array('pk','fk','ix','uc'))) + if ($this->needs_save('',$this->app,$this->table,$this->data[$posted_table])) { - $depth = 0; + return; } - if ($depth) - { - $tabs = "\n"; - for ($n = 0; $n < $depth; ++$n) - { - $tabs .= "\t"; - } - ++$depth; - } - $def = "array($tabs".($tabs ? "\t" : ''); + $msg .= lang('Table unchanged, no write necessary !!!'); + } + elseif ($content['delete']) + { + list($col) = each($content['delete']); - $n = 0; - foreach($arr as $key => $val) + @reset($this->data[$posted_table]['fd']); + while ($col-- > 0 && list($key,$data) = @each($this->data[$posted_table]['fd'])) ; + + unset($this->data[$posted_table]['fd'][$key]); + $this->changes[$posted_table][$key] = '**deleted**'; + } + elseif ($content['add_column']) + { + $this->data[$posted_table]['fd'][''] = array(); + } + elseif ($content['add_table'] || $content['import']) + { + if (!$this->app) { - if (!is_int($key)) + $msg .= lang('Select an app first !!!'); + } + elseif (!$content['new_table_name']) + { + $msg .= lang('Please enter table-name first !!!'); + } + elseif ($content['add_table']) + { + $this->table = $content['new_table_name']; + $this->data[$this->table] = array('fd' => array(),'pk' =>array(),'ix' => array(),'uc' => array(),'fk' => array()); + $msg .= lang('New table created'); + } + else // import + { + $oProc =& CreateObject('phpgwapi.schema_proc',$GLOBALS['egw_info']['server']['db_type']); + if (method_exists($oProc,'GetTableDefinition')) { - $def .= "'$key' => "; + $this->data[$this->table = $content['new_table_name']] = $oProc->GetTableDefinition($content['new_table_name']); } - if (is_array($val)) + else // to support eGW 1.0 { - $def .= $this->write_array($val,$parent == 'fd' ? 0 : $depth,$key); + $oProc->m_odb = clone($GLOBALS['egw']->db); + $oProc->m_oTranslator->_GetColumns($oProc,$content['new_table_name'],$nul); + + while (list($key,$tbldata) = each ($oProc->m_oTranslator->sCol)) + { + $cols .= $tbldata; + } + eval('$cols = array('. $cols . ');'); + + $this->data[$this->table = $content['new_table_name']] = array( + 'fd' => $cols, + 'pk' => $oProc->m_oTranslator->pk, + 'fk' => $oProc->m_oTranslator->fk, + 'ix' => $oProc->m_oTranslator->ix, + 'uc' => $oProc->m_oTranslator->uc + ); + } + } + } + elseif ($content['editor']) + { + ExecMethod('etemplate.editor.edit'); + return; + } + $add_index = isset($content['add_index']); + + // from here on, filling new content for eTemplate + $content = array( + 'msg' => $msg, + 'table_name' => $this->table, + 'app' => $this->app, + ); + if (!isset($table_names[$this->table])) // table is not jet written + { + $table_names[$this->table] = $this->table; + } + $sel_options = array( + 'table_name' => $table_names, + 'type' => $this->types + ); + if ($this->table != '' && isset($this->data[$this->table])) + { + $content += $this->table2content($this->data[$this->table],$sel_options['Index'],$add_index); + } + $no_button = array( ); + if (!$this->app || !$this->table) + { + $no_button += array('write_tables' => True); + } + if ($this->debug) + { + echo 'editor.edit: content ='; _debug_array($content); + } + $this->editor->exec('etemplate.db_tools.edit',$content,$sel_options,$no_button, + array('posted_table' => $this->table,'posted_app' => $this->app,'changes' => $this->changes)); + } + + /** + * checks if table was changed and if so offers user to save changes + * + * @param array $cont the content of the form (if called by process_exec) + * @param string $posted_app the app the table is from + * @param string $posted_table the table-name + * @param array $edited_table the edited table-definitions + * @return only if no changes + */ + function needs_save($cont='',$posted_app='',$posted_table='',$edited_table='',$msg='') + { + //echo "

db_tools::needs_save(cont,'$posted_app','$posted_table',edited_table,'$msg')

cont=\n"; _debug_array($cont); echo "edited_table="; _debug_array($edited_table); + if (!$posted_app && is_array($cont)) + { + if (isset($cont['yes'])) + { + $this->app = $cont['app']; + $this->table = $cont['table']; + $this->read($this->app,$this->data); + $this->data[$this->table] = $cont['edited_table']; + $this->changes = $cont['changes']; + if ($cont['new_version']) + { + $this->update($this->app,$this->data,$cont['new_version']); } else { - if (!$only_vals && $key === 'nullable') + foreach($this->data as $tname => $tinfo) { - $def .= $val ? 'True' : 'False'; - } - else - { - $def .= "'$val'"; + $tables .= ($tables ? ',' : '') . "'$tname'"; } + $this->setup_version($this->app,'',$tables); } - if ($n < count($arr)-1) + if (!$this->write($this->app,$this->data)) { - $def .= ",$tabs".($tabs ? "\t" : ''); + $this->app = $cont['new_app']; // these are the ones, the users whiches to change too + $this->table = $cont['new_table']; + + return $this->needs_save('',$cont['app'],$cont['table'],$cont['edited_table'], + lang('Error: writing file (no write-permission for the webserver) !!!')); } - ++$n; + $msg = lang('File writen'); } - $def .= "$tabs)"; - - return $def; - } - - /** - * writes tabledefinitions $phpgw_baseline to file /$app/setup/tables_current.inc.php - * - * @param string $app app-name - * @param array $phpgw_baseline tabledefinitions - * @return boolean True if file writen else False - */ - function write($app,$phpgw_baseline) - { - $file = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; - - if (file_exists($file) && ($f = fopen($file,'r'))) - { - $header = fread($f,filesize($file)); - if ($end = strpos($header,');')) - { - $footer = substr($header,$end+3); // this preservs other stuff, which should not be there - } - $header = substr($header,0,strpos($header,'$phpgw_baseline')); - fclose($f); - - if (is_writable(EGW_SERVER_ROOT."/$app/setup")) - { - $old_file = EGW_SERVER_ROOT . "/$app/setup/tables_current.old.inc.php"; - if (file_exists($old_file)) - { - unlink($old_file); - } - rename($file,$old_file); - } - while ($header[strlen($header)-1] == "\t") - { - $header = substr($header,0,strlen($header)-1); - } - } - if (!$header) - { - $header = $this->setup_header($this->app) . "\n\n"; - } - if (!is_writeable(EGW_SERVER_ROOT."/$app/setup") || !($f = fopen($file,'w'))) - { - return False; - } - $def .= "\t\$phpgw_baseline = "; - $def .= $this->write_array($phpgw_baseline,1); - $def .= ";\n"; - - fwrite($f,$header . $def . $footer); - fclose($f); + $this->changes = array(); + // return to edit with everything set, so the user gets the table he asked for + $this->edit(array( + 'app' => $cont['new_app'], + 'table_name' => $cont['app']==$cont['new_app'] ? $cont['new_table'] : '', + 'posted_app' => $cont['new_app'] + ),$msg); return True; } + $new_app = $this->app; // these are the ones, the users whiches to change too + $new_table = $this->table; - /** - * reads and updates the version and tables info in file $app/setup/setup.inc.php - * @param string $app the app - * @param string $new new version number to set, if $new != '' - * @param string $tables new tables to include (comma delimited), if != '' - * @return the version or False if the file could not be read or written - */ - function setup_version($app,$new = '',$tables='') + $this->app = $posted_app; + $this->data = array(); + $this->read($posted_app,$this->data); + + if (isset($this->data[$posted_table]) && + $this->tables_identical($this->data[$posted_table],$edited_table)) { - //echo "

etemplate.db_tools.setup_version('$app','$new','$tables')

\n"; + if ($new_app != $this->app) // are we changeing the app, or hit the user just write + { + $this->app = $new_app; // if we change init the data empty + $this->data = array(); + } + return False; // continue edit + } + $content = array( + 'msg' => $msg, + 'app' => $posted_app, + 'table' => $posted_table, + 'version' => $this->setup_version($posted_app) + ); + $preserv = $content + array( + 'new_app' => $new_app, + 'new_table' => $new_table, + 'edited_table' => $edited_table, + 'changes' => $this->changes + ); + $new_version = explode('.',$content['version']); + $minor = count($new_version)-1; + $new_version[$minor] = sprintf('%03d',1+$new_version[$minor]); + $content['new_version'] = implode('.',$new_version); - $file = EGW_SERVER_ROOT."/$app/setup/setup.inc.php"; - if (file_exists($file)) + $tmpl =& new etemplate('etemplate.db-tools.ask_save'); + + if (!file_exists(EGW_SERVER_ROOT."/$posted_app/setup/tables_current.inc.php")) + { + $tmpl->disable_cells('version'); + $tmpl->disable_cells('new_version'); + } + $tmpl->exec('etemplate.db_tools.needs_save',$content,array(),array(),$preserv); + + return True; // dont continue in edit + } + + /** + * checks if there is an index (only) on $col (not a multiple index incl. $col) + * + * @param string $col column name + * @param array $index ix or uc array of table-defintion + * @param string &$options db specific options + * @return True if $col has a single index + */ + function has_single_index($col,$index,&$options) + { + foreach($index as $in) + { + if ($in == $col || is_array($in) && $in[0] == $col && !isset($in[1])) { - include($file); + if ($in != $col && isset($in['options'])) + { + foreach($in['options'] as $db => $opts) + { + $options[] = $db.'('.(is_array($opts)?implode(',',$opts):$opts).')'; + } + $options = implode(', ',$options); + } + return True; } - if (!is_array($setup_info[$app]) || !isset($setup_info[$app]['version'])) + } + return False; + } + + /** + * creates content-array from a table + * + * @param array $table table-definition, eg. $phpgw_baseline[$table_name] + * @param array &$columns returns array with column-names + * @param bool $extra_index add an additional index-row + * @return array content-array to call exec with + */ + function table2content($table,&$columns,$extra_index=False) + { + $content = $columns = array(); + for ($n = 1; list($col_name,$col_defs) = each($table['fd']); ++$n) + { + $col_defs['name'] = $col_name; + $col_defs['pk'] = in_array($col_name,$table['pk']); + $col_defs['uc'] = $this->has_single_index($col_name,$table['uc'],$col_defs['options']); + $col_defs['ix'] = $this->has_single_index($col_name,$table['ix'],$col_defs['options']); + $col_defs['fk'] = $table['fk'][$col_name]; + if (isset($col_defs['default']) && $col_defs['default'] == '') { - return False; + $col_defs['default'] = is_int($col_defs['default']) ? '0' : "''"; // spezial value for empty, but set, default } - if (($new == '' || $setup_info[$app]['version'] == $new) && - (!$tables || $setup_info[$app]['tables'] && "'".implode("','",$setup_info[$app]['tables'])."'" == $tables)) + $col_defs['notnull'] = isset($col_defs['nullable']) && !$col_defs['nullable']; + + $col_defs['n'] = $n; + + $content["Row$n"] = $col_defs; + + $columns[$n] = $col_name; + } + $n = 2; + foreach(array('uc','ix') as $type) + { + foreach($table[$type] as $index) { - return $setup_info[$app]['version']; // no change requested or not necessary + if (is_array($index) && isset($index[1])) // multicolum index + { + $content['Index'][$n]['unique'] = $type == 'uc'; + $content['Index'][$n]['n'] = $n - 1; + foreach($index as $col) + { + $content['Index'][$n][] = array_search($col,$columns); + } + ++$n; + } } - if ($new == '') - { - $new = $setup_info[$app]['version']; - } - if (!($f = fopen($file,'r'))) - { - return False; - } - $fcontent = fread($f,filesize($file)); - fclose ($f); + } + if ($extra_index) + { + $content['Index'][$n]['n'] = $n-1; + } + if ($this->debug >= 3) + { + echo "

table2content(,,'$extra_index'): content ="; _debug_array($content); + echo "

columns ="; _debug_array($columns); + } + return $content; + } + + /** + * creates table-definition from posted content + * + * It sets some reasonalbe defaults for not set precisions (else setup will not install) + * + * @param array $content posted content-array + * @return table-definition + */ + function content2table($content) + { + if (!is_array($this->data)) + { + $this->read($content['posted_app'],$this->data); + } + $old_cols = $this->data[$posted_table = $content['posted_table']]['fd']; + $this->changes = $content['changes']; + + $table = array(); + $table['fd'] = array(); // do it in the default order of tables_* + $table['pk'] = array(); + $table['fk'] = array(); + $table['ix'] = array(); + $table['uc'] = array(); + for (reset($content),$n = 1; isset($content["Row$n"]); ++$n) + { + $col = $content["Row$n"]; - $app_pattern = "'$app'"; - if (preg_match("/define\('([^']+)',$app_pattern\)/",$fcontent,$matches)) + if ($col['type'] == 'auto') // auto columns are the primary key and not null! { - $app_pattern = $matches[1]; + $col['pk'] = $col['notnull'] = true; // set it, in case the user forgot } + + while ((list($old_name,$old_col) = @each($old_cols)) && + $this->changes[$posted_table][$old_name] == '**deleted**') ; + + if (($name = $col['name']) != '') // ignoring lines without column-name + { + if ($col['name'] != $old_name && $n <= count($old_cols)) // column renamed --> remeber it + { + $this->changes[$posted_table][$old_name] = $col['name']; + //echo "

content2table: $posted_table.$old_name renamed to $col[name]

\n"; + } + if ($col['precision'] <= 0) + { + switch ($col['type']) // set some defaults for precision, else setup fails + { + case 'float': + case 'int': $col['precision'] = 4; break; + case 'char': $col['precision'] = 1; break; + case 'varchar': $col['precision'] = 255; break; + } + } + while (list($prop,$val) = each($col)) + { + switch ($prop) + { + case 'default': + case 'type': // selectbox ensures type is not empty + case 'precision': + case 'scale': +// case 'nullable': + if ($val != '' || $prop == 'nullable') + { + $table['fd'][$name][$prop] = $prop=='default'&& $val=="''" ? '' : $val; + } + break; + case 'notnull': + if ($val) + { + $table['fd'][$name]['nullable'] = False; + } + break; + case 'pk': + case 'uc': + case 'ix': + if ($val) + { + if ($col['options']) + { + $opts = array(); + foreach(explode(',',$col['options']) as $opt) + { + list($db,$opt) = split('[(:)]',$opt); + $opts[$db] = is_numeric($opt) ? intval($opt) : $opt; + } + $table[$prop][] = array( + $name, + 'options' => $opts + ); + } + else + { + $table[$prop][] = $name; + } + } + break; + case 'fk': + if ($val != '') + { + $table['fk'][$name] = $val; + } + break; + } + } + $num2col[$n] = $col['name']; + } + } + foreach($content['Index'] as $n => $index) + { + $idx_arr = array(); + foreach($index as $key => $num) + { + if (is_numeric($key) && $num && @$num2col[$num]) + { + $idx_arr[] = $num2col[$num]; + } + } + if (count($idx_arr) && !isset($content['delete_index'][$n])) + { + if ($index['unique']) + { + $table['uc'][] = $idx_arr; + } + else + { + $table['ix'][] = $idx_arr; + } + } + } + if ($this->debug >= 2) + { + echo "

content2table: table ="; _debug_array($table); + echo "

changes = "; _debug_array($this->changes); + } + return $table; + } + + /** + * includes $app/setup/tables_current.inc.php + * @param string $app application name + * @param array &$phpgw_baseline where to return the data + * @return boolean True if file found, False else + */ + function read($app,&$phpgw_baseline) + { + $file = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; + + $phpgw_baseline = array(); + + if ($app != '' && file_exists($file)) + { + include($file); + } + else + { + return False; + } + if ($this->debug >= 5) + { + echo "

read($app): file='$file', phpgw_baseline ="; + _debug_array($phpgw_baseline); + } + return True; + } + + /** + * returns an array as string in php-notation + * + * @param array $arr + * @param int $depth for idention + * @param string $parent + * @return string + */ + function write_array($arr,$depth,$parent='') + { + if (in_array($parent,array('pk','fk','ix','uc'))) + { + $depth = 0; + } + if ($depth) + { + $tabs = "\n"; + for ($n = 0; $n < $depth; ++$n) + { + $tabs .= "\t"; + } + ++$depth; + } + $def = "array($tabs".($tabs ? "\t" : ''); + + $n = 0; + foreach($arr as $key => $val) + { + if (!is_int($key)) + { + $def .= "'$key' => "; + } + if (is_array($val)) + { + $def .= $this->write_array($val,$parent == 'fd' ? 0 : $depth,$key); + } + else + { + if (!$only_vals && $key === 'nullable') + { + $def .= $val ? 'True' : 'False'; + } + else + { + $def .= "'$val'"; + } + } + if ($n < count($arr)-1) + { + $def .= ",$tabs".($tabs ? "\t" : ''); + } + ++$n; + } + $def .= "$tabs)"; + + return $def; + } + + /** + * writes tabledefinitions $phpgw_baseline to file /$app/setup/tables_current.inc.php + * + * @param string $app app-name + * @param array $phpgw_baseline tabledefinitions + * @return boolean True if file writen else False + */ + function write($app,$phpgw_baseline) + { + $file = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; + + if (file_exists($file) && ($f = fopen($file,'r'))) + { + $header = fread($f,filesize($file)); + if ($end = strpos($header,');')) + { + $footer = substr($header,$end+3); // this preservs other stuff, which should not be there + } + $header = substr($header,0,strpos($header,'$phpgw_baseline')); + fclose($f); + if (is_writable(EGW_SERVER_ROOT."/$app/setup")) { - $old_file = EGW_SERVER_ROOT . "/$app/setup/setup.old.inc.php"; + $old_file = EGW_SERVER_ROOT . "/$app/setup/tables_current.old.inc.php"; if (file_exists($old_file)) { unlink($old_file); } rename($file,$old_file); } - $fnew = preg_replace('/(.*\\$'."setup_info\\[$app_pattern\\]\\['version'\\][ \\t]*=[ \\t]*)'[^']*'(.*)/i","\\1'$new'\\2",$fcontent); - - if ($tables != '') + while ($header[strlen($header)-1] == "\t") { - if (isset($setup_info[$app]['tables'])) // if there is already tables array, update it - { - $fnew = preg_replace('/(.*\\$'."setup_info\\[$app_pattern\\]\\['tables'\\][ \\t]*=[ \\t]*array\()[^)]*/i","\\1$tables",$fwas=$fnew); + $header = substr($header,0,strlen($header)-1); + } + } + if (!$header) + { + $header = $this->setup_header($this->app) . "\n\n"; + } + if (!is_writeable(EGW_SERVER_ROOT."/$app/setup") || !($f = fopen($file,'w'))) + { + return False; + } + $def .= "\t\$phpgw_baseline = "; + $def .= $this->write_array($phpgw_baseline,1); + $def .= ";\n"; - if ($fwas == $fnew) // nothing changed => tables are in single lines + fwrite($f,$header . $def . $footer); + fclose($f); + + return True; + } + + /** + * reads and updates the version and tables info in file $app/setup/setup.inc.php + * @param string $app the app + * @param string $new new version number to set, if $new != '' + * @param string $tables new tables to include (comma delimited), if != '' + * @return the version or False if the file could not be read or written + */ + function setup_version($app,$new = '',$tables='') + { + //echo "

etemplate.db_tools.setup_version('$app','$new','$tables')

\n"; + + $file = EGW_SERVER_ROOT."/$app/setup/setup.inc.php"; + if (file_exists($file)) + { + include($file); + } + if (!is_array($setup_info[$app]) || !isset($setup_info[$app]['version'])) + { + return False; + } + if (($new == '' || $setup_info[$app]['version'] == $new) && + (!$tables || $setup_info[$app]['tables'] && "'".implode("','",$setup_info[$app]['tables'])."'" == $tables)) + { + return $setup_info[$app]['version']; // no change requested or not necessary + } + if ($new == '') + { + $new = $setup_info[$app]['version']; + } + if (!($f = fopen($file,'r'))) + { + return False; + } + $fcontent = fread($f,filesize($file)); + fclose ($f); + + $app_pattern = "'$app'"; + if (preg_match("/define\('([^']+)',$app_pattern\)/",$fcontent,$matches)) + { + $app_pattern = $matches[1]; + } + if (is_writable(EGW_SERVER_ROOT."/$app/setup")) + { + $old_file = EGW_SERVER_ROOT . "/$app/setup/setup.old.inc.php"; + if (file_exists($old_file)) + { + unlink($old_file); + } + rename($file,$old_file); + } + $fnew = preg_replace('/(.*\\$'."setup_info\\[$app_pattern\\]\\['version'\\][ \\t]*=[ \\t]*)'[^']*'(.*)/i","\\1'$new'\\2",$fcontent); + + if ($tables != '') + { + if (isset($setup_info[$app]['tables'])) // if there is already tables array, update it + { + $fnew = preg_replace('/(.*\\$'."setup_info\\[$app_pattern\\]\\['tables'\\][ \\t]*=[ \\t]*array\()[^)]*/i","\\1$tables",$fwas=$fnew); + + if ($fwas == $fnew) // nothing changed => tables are in single lines + { + $fwas = explode("\n",$fwas); + $fnew = $prefix = ''; + $stage = 0; // 0 = before, 1 = in, 2 = after tables section + foreach($fwas as $line) { - $fwas = explode("\n",$fwas); - $fnew = $prefix = ''; - $stage = 0; // 0 = before, 1 = in, 2 = after tables section - foreach($fwas as $line) + if (preg_match('/(.*\\$'."setup_info\\[$app_pattern\\]\\['tables'\\]\\[[ \\t]*\\][ \\t]*=[ \\t]*)'/i",$line,$parts)) { - if (preg_match('/(.*\\$'."setup_info\\[$app_pattern\\]\\['tables'\\]\\[[ \\t]*\\][ \\t]*=[ \\t]*)'/i",$line,$parts)) + if ($stage == 0) // first line of tables-section { - if ($stage == 0) // first line of tables-section - { - $stage = 1; - $prefix = $parts[1]; - } + $stage = 1; + $prefix = $parts[1]; } - else // not in table-section + } + else // not in table-section + { + if ($stage == 1) // first line after tables-section ==> add it { - if ($stage == 1) // first line after tables-section ==> add it + $tables = explode(',',$tables); + foreach ($tables as $table) { - $tables = explode(',',$tables); - foreach ($tables as $table) - { - $fnew .= $prefix . $table . ";\n"; - } - $stage = 2; - } - if (strpos($line,'?>') === False) // dont write the closeing tag - { - $fnew .= $line . "\n"; + $fnew .= $prefix . $table . ";\n"; } + $stage = 2; + } + if (strpos($line,'?>') === False) // dont write the closeing tag + { + $fnew .= $line . "\n"; } } } } - else // add the tables array - { - if (strpos($fnew,'?>') !== false) // remove a closeing tag - { - $fnew = str_replace('?>','',$fnew); - } - $fnew .= "\t\$setup_info[$app_pattern]['tables'] = array($tables);\n"; - } } - if (!is_writeable(EGW_SERVER_ROOT."/$app/setup") || !($f = fopen($file,'w'))) + else // add the tables array { - return False; + if (strpos($fnew,'?>') !== false) // remove a closeing tag + { + $fnew = str_replace('?>','',$fnew); + } + $fnew .= "\t\$setup_info[$app_pattern]['tables'] = array($tables);\n"; } - fwrite($f,$fnew); - fclose($f); - - return $new; } - - /** - * updates file /$app/setup/tables_update.inc.php to reflect changes in $current - * - * @param string $app app-name - * @param array $current new tabledefinitions - * @param string $version new version - * @return boolean True if file writen else False - */ - function update($app,$current,$version) + if (!is_writeable(EGW_SERVER_ROOT."/$app/setup") || !($f = fopen($file,'w'))) { - //echo "

etemplate.db_tools.update('$app',...,'$version')

\n"; - if (!is_writable(EGW_SERVER_ROOT."/$app/setup")) - { - return False; - } - $file_baseline = EGW_SERVER_ROOT."/$app/setup/tables_baseline.inc.php"; - $file_current = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; - $file_update = EGW_SERVER_ROOT."/$app/setup/tables_update.inc.php"; + return False; + } + fwrite($f,$fnew); + fclose($f); - if (!file_exists($file_baseline) && !copy($file_current,$file_baseline)) - { - //echo "

Can't copy $file_current to $file_baseline !!!

\n"; - return False; - } - $old_version = $this->setup_version($app); - $old_version_ = str_replace('.','_',$old_version); + return $new; + } - if (file_exists($file_update)) + /** + * updates file /$app/setup/tables_update.inc.php to reflect changes in $current + * + * @param string $app app-name + * @param array $current new tabledefinitions + * @param string $version new version + * @return boolean True if file writen else False + */ + function update($app,$current,$version) + { + //echo "

etemplate.db_tools.update('$app',...,'$version')

\n"; + if (!is_writable(EGW_SERVER_ROOT."/$app/setup")) + { + return False; + } + $file_baseline = EGW_SERVER_ROOT."/$app/setup/tables_baseline.inc.php"; + $file_current = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; + $file_update = EGW_SERVER_ROOT."/$app/setup/tables_update.inc.php"; + + if (!file_exists($file_baseline) && !copy($file_current,$file_baseline)) + { + //echo "

Can't copy $file_current to $file_baseline !!!

\n"; + return False; + } + $old_version = $this->setup_version($app); + $old_version_ = str_replace('.','_',$old_version); + + if (file_exists($file_update)) + { + $f = fopen($file_update,'r'); + $update = fread($f,filesize($file_update)); + $update = str_replace('?>','',$update); + fclose($f); + $old_file = EGW_SERVER_ROOT . "/$app/setup/tables_update.old.inc.php"; + if (file_exists($old_file)) { - $f = fopen($file_update,'r'); - $update = fread($f,filesize($file_update)); - $update = str_replace('?>','',$update); - fclose($f); - $old_file = EGW_SERVER_ROOT . "/$app/setup/tables_update.old.inc.php"; - if (file_exists($old_file)) - { - unlink($old_file); - } - rename($file_update,$old_file); + unlink($old_file); } - else - { - $update = $this->setup_header($this->app); - } - $update .= " + rename($file_update,$old_file); + } + else + { + $update = $this->setup_header($this->app); + } + $update .= " \$test[] = '$old_version'; function $app"."_upgrade$old_version_() {\n"; @@ -846,215 +843,215 @@ return \$GLOBALS['setup_info']['$app']['currentver'] = '$version'; } ?".">\n"; - if (!($f = fopen($file_update,'w'))) - { - //echo "

Cant open '$update' for writing !!!

\n"; - return False; - } - fwrite($f,$update); - fclose($f); - - $this->setup_version($app,$version,$tables); - - return True; - } - - /** - * unsets all keys in an array which have a given value - * - * @param array &$arr - * @param mixed $val value to check against - */ - function remove_from_array(&$arr,$value) + if (!($f = fopen($file_update,'w'))) { - foreach($arr as $key => $val) + //echo "

Cant open '$update' for writing !!!

\n"; + return False; + } + fwrite($f,$update); + fclose($f); + + $this->setup_version($app,$version,$tables); + + return True; + } + + /** + * unsets all keys in an array which have a given value + * + * @param array &$arr + * @param mixed $val value to check against + */ + function remove_from_array(&$arr,$value) + { + foreach($arr as $key => $val) + { + if ($val == $value) { - if ($val == $value) - { - unset($arr[$key]); - } + unset($arr[$key]); } } + } - /** - * creates an update-script - * - * @param string $app app-name - * @param array $current new table-defintion - * @param string &$tables returns comma delimited list of new table-names - * @return string the update-script - */ - function update_schema($app,$current,&$tables) + /** + * creates an update-script + * + * @param string $app app-name + * @param array $current new table-defintion + * @param string &$tables returns comma delimited list of new table-names + * @return string the update-script + */ + function update_schema($app,$current,&$tables) + { + $this->read($app,$old); + + $tables = ''; + foreach($old as $name => $table_def) { - $this->read($app,$old); - - $tables = ''; - foreach($old as $name => $table_def) + if (!isset($current[$name])) // table $name droped { - if (!isset($current[$name])) // table $name droped - { - $update .= "\t\t\$GLOBALS['egw_setup']->oProc->DropTable('$name');\n"; - } - else - { - $tables .= ($tables ? ',' : '') . "'$name'"; + $update .= "\t\t\$GLOBALS['egw_setup']->oProc->DropTable('$name');\n"; + } + else + { + $tables .= ($tables ? ',' : '') . "'$name'"; - $new_table_def = $table_def; - foreach($table_def['fd'] as $col => $col_def) + $new_table_def = $table_def; + foreach($table_def['fd'] as $col => $col_def) + { + if (!isset($current[$name]['fd'][$col])) // column $col droped { - if (!isset($current[$name]['fd'][$col])) // column $col droped + if (!isset($this->changes[$name][$col]) || $this->changes[$name][$col] == '**deleted**') { - if (!isset($this->changes[$name][$col]) || $this->changes[$name][$col] == '**deleted**') - { - unset($new_table_def['fd'][$col]); - $this->remove_from_array($new_table_def['pk'],$col); - $this->remove_from_array($new_table_def['fk'],$col); - $this->remove_from_array($new_table_def['ix'],$col); - $this->remove_from_array($new_table_def['uc'],$col); - $update .= "\t\t\$GLOBALS['egw_setup']->oProc->DropColumn('$name',"; - $update .= $this->write_array($new_table_def,2).",'$col');\n"; - } - else // column $col renamed - { - $new_col = $this->changes[$name][$col]; - $update .= "\t\t\$GLOBALS['egw_setup']->oProc->RenameColumn('$name','$col','$new_col');\n"; - } + unset($new_table_def['fd'][$col]); + $this->remove_from_array($new_table_def['pk'],$col); + $this->remove_from_array($new_table_def['fk'],$col); + $this->remove_from_array($new_table_def['ix'],$col); + $this->remove_from_array($new_table_def['uc'],$col); + $update .= "\t\t\$GLOBALS['egw_setup']->oProc->DropColumn('$name',"; + $update .= $this->write_array($new_table_def,2).",'$col');\n"; + } + else // column $col renamed + { + $new_col = $this->changes[$name][$col]; + $update .= "\t\t\$GLOBALS['egw_setup']->oProc->RenameColumn('$name','$col','$new_col');\n"; } } - if (is_array($this->changes[$name])) + } + if (is_array($this->changes[$name])) + { + foreach($this->changes[$name] as $col => $new_col) { - foreach($this->changes[$name] as $col => $new_col) + if ($new_col != '**deleted**') { - if ($new_col != '**deleted**') - { - $old[$name]['fd'][$new_col] = $old[$name]['fd'][$col]; // to be able to detect further changes of the definition - unset($old[$name]['fd'][$col]); - } + $old[$name]['fd'][$new_col] = $old[$name]['fd'][$col]; // to be able to detect further changes of the definition + unset($old[$name]['fd'][$col]); } } } } - foreach($current as $name => $table_def) + } + foreach($current as $name => $table_def) + { + if (!isset($old[$name])) // table $name added { - if (!isset($old[$name])) // table $name added - { - $tables .= ($tables ? ',' : '') . "'$name'"; + $tables .= ($tables ? ',' : '') . "'$name'"; - $update .= "\t\t\$GLOBALS['egw_setup']->oProc->CreateTable('$name',"; + $update .= "\t\t\$GLOBALS['egw_setup']->oProc->CreateTable('$name',"; + $update .= $this->write_array($table_def,2).");\n"; + } + else + { + $old_norm = $this->normalize($old[$name]); + $new_norm = $this->normalize($table_def); + $old_norm_fd = $old_norm['fd']; unset($old_norm['fd']); + $new_norm_fd = $new_norm['fd']; unset($new_norm['fd']); + + // check if the indices are changed and refresh the table if so + $do_refresh = serialize($old_norm) != serialize($new_norm); + // we comment out the Add or AlterColumn code as it is not needed, but might be useful for more complex updates + foreach($table_def['fd'] as $col => $col_def) + { + if (($add = !isset($old[$name]['fd'][$col])) || // column $col added + serialize($old_norm_fd[$col]) != serialize($new_norm_fd[$col])) // column definition altered + { + $update .= "\t\t".($do_refresh ? "/* done by RefreshTable() anyway\n\t\t" : ''). + "\$GLOBALS['egw_setup']->oProc->".($add ? 'Add' : 'Alter')."Column('$name','$col',"; + $update .= $this->write_array($col_def,2) . ');' . ($do_refresh ? '*/' : '') . "\n"; + } + } + if ($do_refresh) + { + $update .= "\t\t\$GLOBALS['egw_setup']->oProc->RefreshTable('$name',"; $update .= $this->write_array($table_def,2).");\n"; } - else - { - $old_norm = $this->normalize($old[$name]); - $new_norm = $this->normalize($table_def); - $old_norm_fd = $old_norm['fd']; unset($old_norm['fd']); - $new_norm_fd = $new_norm['fd']; unset($new_norm['fd']); + } + } + if ($this->debug) + { + echo "

update_schema($app, ...) =

$update
)

\n"; + } + return $update; + } - // check if the indices are changed and refresh the table if so - $do_refresh = serialize($old_norm) != serialize($new_norm); - // we comment out the Add or AlterColumn code as it is not needed, but might be useful for more complex updates - foreach($table_def['fd'] as $col => $col_def) - { - if (($add = !isset($old[$name]['fd'][$col])) || // column $col added - serialize($old_norm_fd[$col]) != serialize($new_norm_fd[$col])) // column definition altered - { - $update .= "\t\t".($do_refresh ? "/* done by RefreshTable() anyway\n\t\t" : ''). - "\$GLOBALS['egw_setup']->oProc->".($add ? 'Add' : 'Alter')."Column('$name','$col',"; - $update .= $this->write_array($col_def,2) . ');' . ($do_refresh ? '*/' : '') . "\n"; - } - } - if ($do_refresh) - { - $update .= "\t\t\$GLOBALS['egw_setup']->oProc->RefreshTable('$name',"; - $update .= $this->write_array($table_def,2).");\n"; - } + /** + * orders the single-colum-indices after the columns and the multicolunm ones behind + * + * @param array $index array with indices + * @param array $cols array with column-defs (col-name is the key) + * @return array the new array of indices + */ + function normalize_index($index,$cols) + { + $normalized = array(); + foreach($cols as $col => $data) + { + foreach($index as $n => $idx) + { + if ($idx == $col || is_array($idx) && $idx[0] == $col && !isset($idx[1])) + { + $normalized[] = isset($idx['options']) ? $idx : $col; + unset($index[$n]); + break; } } - if ($this->debug) - { - echo "

update_schema($app, ...) =

$update
)

\n"; - } - return $update; } - - /** - * orders the single-colum-indices after the columns and the multicolunm ones behind - * - * @param array $index array with indices - * @param array $cols array with column-defs (col-name is the key) - * @return array the new array of indices - */ - function normalize_index($index,$cols) + foreach($index as $idx) { - $normalized = array(); - foreach($cols as $col => $data) - { - foreach($index as $n => $idx) - { - if ($idx == $col || is_array($idx) && $idx[0] == $col && !isset($idx[1])) - { - $normalized[] = isset($idx['options']) ? $idx : $col; - unset($index[$n]); - break; - } - } - } - foreach($index as $idx) - { - $normalized[] = $idx; - } - return $normalized; + $normalized[] = $idx; } + return $normalized; + } - /** - * normalices all properties in a table-definiton, eg. all nullable properties to True or False - * - * this is necessary to compare two table-defitions - * - * @param array $table table-definition - * @return array the normaliced defintion - */ - function normalize($table) + /** + * normalices all properties in a table-definiton, eg. all nullable properties to True or False + * + * this is necessary to compare two table-defitions + * + * @param array $table table-definition + * @return array the normaliced defintion + */ + function normalize($table) + { + $all_props = array('type','precision','nullable','default'); + + foreach($table['fd'] as $col => $props) { - $all_props = array('type','precision','nullable','default'); - - foreach($table['fd'] as $col => $props) - { - $table['fd'][$col] = array( - 'type' => ''.$props['type'], - 'precision' => 0+$props['precision'], - 'scale' => 0+$props['scale'], - 'nullable' => !isset($props['nullable']) || !!$props['nullable'], - 'default' => ''.$props['default'] - ); - } - return array( - 'fd' => $table['fd'], - 'pk' => $table['pk'], - 'fk' => $table['fk'], - 'ix' => $this->normalize_index($table['ix'],$table['fd']), - 'uc' => $this->normalize_index($table['uc'],$table['fd']) + $table['fd'][$col] = array( + 'type' => ''.$props['type'], + 'precision' => 0+$props['precision'], + 'scale' => 0+$props['scale'], + 'nullable' => !isset($props['nullable']) || !!$props['nullable'], + 'default' => ''.$props['default'] ); } + return array( + 'fd' => $table['fd'], + 'pk' => $table['pk'], + 'fk' => $table['fk'], + 'ix' => $this->normalize_index($table['ix'],$table['fd']), + 'uc' => $this->normalize_index($table['uc'],$table['fd']) + ); + } - /** - * compares two table-definitions, by comparing normaliced string-representations (serialize) - * - * @param array $a - * @param array $b - * @return boolean true if they are identical (would create an identical schema), false otherwise - * - */ - function tables_identical($a,$b) - { - $a = serialize($this->normalize($a)); - $b = serialize($this->normalize($b)); + /** + * compares two table-definitions, by comparing normaliced string-representations (serialize) + * + * @param array $a + * @param array $b + * @return boolean true if they are identical (would create an identical schema), false otherwise + * + */ + function tables_identical($a,$b) + { + $a = serialize($this->normalize($a)); + $b = serialize($this->normalize($b)); - //echo "

checking if tables identical = ".($a == $b ? 'True' : 'False')."
\n"; - //echo "a: $a
\nb: $b

\n"; + //echo "

checking if tables identical = ".($a == $b ? 'True' : 'False')."
\n"; + //echo "a: $a
\nb: $b

\n"; - return $a == $b; - } + return $a == $b; + } /** * creates file header @@ -1072,7 +1069,7 @@ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package '. $app. ' * @subpackage setup - * @version $Id$ + * @version $Id'.'$ */ '; } diff --git a/etemplate/inc/class.editor.inc.php b/etemplate/inc/class.editor.inc.php index 82a9e7f5ea..b3c34f88b7 100644 --- a/etemplate/inc/class.editor.inc.php +++ b/etemplate/inc/class.editor.inc.php @@ -1,1533 +1,1530 @@ - * @version $Id$ - */ +/** + * eGroupWare eTemplates - Editor + * + * @link http://www.egroupware.org + * @author Ralf Becker + * @copyright 2002-8 by RalfBecker@outdoor-training.de + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage tools + * @version $Id$ + */ +/** + * template editor of the eTemplate package + */ +class editor +{ + var $debug; /** - * template editor of the eTemplate package + * eTemplate we edit * - * @package etemplate - * @subpackage tools - * @author RalfBecker-AT-outdoor-training.de - * @license GPL + * @var etemplate */ - class editor + var $etemplate; + /** + * editor eTemplate + * + * @var etemplate + */ + var $editor; + var $aligns = array( + '' => 'Left', + 'right' => 'Right', + 'center' => 'Center', + ); + var $valigns = array( + '' => 'Middle', + 'top' => 'Top', + 'bottom' => 'Bottom', + 'baseline' => 'Baseline', + ); + var $edit_menu = array( + 'delete' => 'delete', + 'cut' => 'cut', + 'copy' => 'copy', + 'paste' => 'paste', + 'swap' => 'swap', + ); + var $row_menu = array( + 'row_delete' => 'delete this row', + 'row_insert_above' => 'insert a row above', + 'row_insert_below' => 'insert a row below', + 'row_swap_next' => 'swap with next row', + ); + var $column_menu = array( + 'column_delete' => 'delete this column', + 'column_insert_before' => 'insert a column before', + 'column_insert_behind' => 'insert a column behind', + 'column_swap_next' => 'swap with next column', + ); + var $box_menu = array( + 'box_insert_before' => 'insert a widget before', + 'box_insert_behind' => 'insert a widget behind', + 'box_swap_next' => 'swap widget with next one', + ); + var $options = array( + 'width', + 'height', + 'border', + 'class', + 'spacing', + 'padding', + 'overflow' + ); + var $overflows = array( + '' => 'visible', + 'hidden' => 'hidden', + 'scroll' => 'scroll', + 'auto' => 'auto' + ); + var $onclick_types = array( + '' => 'nothing', + 'confirm' => 'confirm', + 'popup' => 'popup', + 'custom' => 'custom', + ); + var $onchange_types = array( + '' => 'nothing', + 'submit' => 'submit form', + 'custom' => 'custom', + ); + var $extensions = ''; + + var $public_functions = array + ( + 'edit' => True, + 'widget' => True, + 'styles' => True, + ); + + function editor() { - var $debug; - /** - * eTemplate we edit - * - * @var etemplate - */ - var $etemplate; - /** - * editor eTemplate - * - * @var etemplate - */ - var $editor; - var $aligns = array( - '' => 'Left', - 'right' => 'Right', - 'center' => 'Center', - ); - var $valigns = array( - '' => 'Middle', - 'top' => 'Top', - 'bottom' => 'Bottom', - 'baseline' => 'Baseline', - ); - var $edit_menu = array( - 'delete' => 'delete', - 'cut' => 'cut', - 'copy' => 'copy', - 'paste' => 'paste', - 'swap' => 'swap', - ); - var $row_menu = array( - 'row_delete' => 'delete this row', - 'row_insert_above' => 'insert a row above', - 'row_insert_below' => 'insert a row below', - 'row_swap_next' => 'swap with next row', - ); - var $column_menu = array( - 'column_delete' => 'delete this column', - 'column_insert_before' => 'insert a column before', - 'column_insert_behind' => 'insert a column behind', - 'column_swap_next' => 'swap with next column', - ); - var $box_menu = array( - 'box_insert_before' => 'insert a widget before', - 'box_insert_behind' => 'insert a widget behind', - 'box_swap_next' => 'swap widget with next one', - ); - var $options = array( - 'width', - 'height', - 'border', - 'class', - 'spacing', - 'padding', - 'overflow' - ); - var $overflows = array( - '' => 'visible', - 'hidden' => 'hidden', - 'scroll' => 'scroll', - 'auto' => 'auto' - ); - var $onclick_types = array( - '' => 'nothing', - 'confirm' => 'confirm', - 'popup' => 'popup', - 'custom' => 'custom', - ); - var $onchange_types = array( - '' => 'nothing', - 'submit' => 'submit form', - 'custom' => 'custom', - ); - var $extensions = ''; + $this->etemplate =& CreateObject('etemplate.etemplate'); - var $public_functions = array - ( - 'edit' => True, - 'widget' => True, - 'styles' => True, - ); + $this->extensions = $GLOBALS['egw']->session->appsession('extensions','etemplate'); + } - function editor() + function export_xml(&$xml,&$xml_label) + { + $name = $this->etemplate->name; + $template = $this->etemplate->template != '' ? $this->etemplate->template : 'default'; + + list($app) = explode('.',$name); + + if (!is_object($this->etemplate->xul_io)) { - $this->etemplate =& CreateObject('etemplate.etemplate'); + $this->etemplate->xul_io =& CreateObject('etemplate.xul_io'); + } + $xml = $this->etemplate->xul_io->export($this->etemplate); - $this->extensions = $GLOBALS['egw']->session->appsession('extensions','etemplate'); + $dir = EGW_SERVER_ROOT . "/$app/templates/$template"; + if (($create_it = !is_dir($dir))) + { + $dir = EGW_SERVER_ROOT . "/$app/templates"; + } + if (!is_writeable($dir)) + { + return lang("Error: webserver is not allowed to write into '%1' !!!",$dir); + } + if ($create_it) + { + mkdir($dir .= "/$template"); + } + $file = $dir . '/' . substr($name,strlen($app)+1); + if ($this->etemplate->lang) + { + $file .= '.' . $this->etemplate->lang; + } + $old_file = $file . '.old.xet'; + $file .= '.xet'; + if (file_exists($file)) + { + if (file_exists($old_file)) + { + unlink($old_file); + } + rename($file,$old_file); } - function export_xml(&$xml,&$xml_label) + if (!($f = fopen($xml_label=$file,'w'))) { - $name = $this->etemplate->name; - $template = $this->etemplate->template != '' ? $this->etemplate->template : 'default'; - - list($app) = explode('.',$name); - - if (!is_object($this->etemplate->xul_io)) - { - $this->etemplate->xul_io =& CreateObject('etemplate.xul_io'); - } - $xml = $this->etemplate->xul_io->export($this->etemplate); - - $dir = EGW_SERVER_ROOT . "/$app/templates/$template"; - if (($create_it = !is_dir($dir))) - { - $dir = EGW_SERVER_ROOT . "/$app/templates"; - } - if (!is_writeable($dir)) - { - return lang("Error: webserver is not allowed to write into '%1' !!!",$dir); - } - if ($create_it) - { - mkdir($dir .= "/$template"); - } - $file = $dir . '/' . substr($name,strlen($app)+1); - if ($this->etemplate->lang) - { - $file .= '.' . $this->etemplate->lang; - } - $old_file = $file . '.old.xet'; - $file .= '.xet'; - if (file_exists($file)) - { - if (file_exists($old_file)) - { - unlink($old_file); - } - rename($file,$old_file); - } - - if (!($f = fopen($xml_label=$file,'w'))) - { - return 0; - } - if (!is_object($this->etemplate->xul_io)) - { - $this->etemplate->xul_io =& CreateObject('etemplate.xul_io'); - } - $xml = $this->etemplate->xul_io->export($this->etemplate); - - fwrite($f,$xml); - fclose($f); - - return lang("eTemplate '%1' written to '%2'",$name,$file); + return 0; } - - function import_xml($file,&$xml) + if (!is_object($this->etemplate->xul_io)) { - if ($file == 'none' || $file == '' || !($f = fopen($file,'r'))) - { - return lang('no filename given or selected via Browse...')."file='$file'"; - } - $xml = fread ($f, filesize ($file)); - fclose($f); - - if (!is_object($this->etemplate->xul_io)) - { - $this->etemplate->xul_io =& CreateObject('etemplate.xul_io'); - } - $imported = $this->etemplate->xul_io->import($this->etemplate,$xml); - $this->etemplate->modified = @filemtime($f); - $this->etemplate->modified_set = 'xul-import'; - - if (is_array($imported)) - { - if (count($imported) == 1) - { - $imported = lang("eTemplate '%1' imported, use Save to put it in the database",$this->etemplate->name); - } - else - { - $imported = lang('File contains more than one eTemplate, last one is shown !!!'); - } - } - return $imported; + $this->etemplate->xul_io =& CreateObject('etemplate.xul_io'); } + $xml = $this->etemplate->xul_io->export($this->etemplate); - function list_result($cont='',$msg='') + fwrite($f,$xml); + fclose($f); + + return lang("eTemplate '%1' written to '%2'",$name,$file); + } + + function import_xml($file,&$xml) + { + if ($file == 'none' || $file == '' || !($f = fopen($file,'r'))) { - if ($this->debug) - { - echo "

etemplate.editor.list_result: cont="; _debug_array($cont); - } - if (!$cont || !is_array($cont)) - { - return $this->edit('error'); - } - if (!isset($cont['result']) || isset($cont['search'])) - { - $cont['result'] = $this->etemplate->search($cont); - } - $result = $cont['result']; + return lang('no filename given or selected via Browse...')."file='$file'"; + } + $xml = fread ($f, filesize ($file)); + fclose($f); - if (isset($cont['delete'])) + if (!is_object($this->etemplate->xul_io)) + { + $this->etemplate->xul_io =& CreateObject('etemplate.xul_io'); + } + $imported = $this->etemplate->xul_io->import($this->etemplate,$xml); + $this->etemplate->modified = @filemtime($f); + $this->etemplate->modified_set = 'xul-import'; + + if (is_array($imported)) + { + if (count($imported) == 1) { - list($delete) = each($cont['delete']); - $this->etemplate->init($result[$delete-1]); - if ($this->etemplate->delete()) + $imported = lang("eTemplate '%1' imported, use Save to put it in the database",$this->etemplate->name); + } + else + { + $imported = lang('File contains more than one eTemplate, last one is shown !!!'); + } + } + return $imported; + } + + function list_result($cont='',$msg='') + { + if ($this->debug) + { + echo "

etemplate.editor.list_result: cont="; _debug_array($cont); + } + if (!$cont || !is_array($cont)) + { + return $this->edit('error'); + } + if (!isset($cont['result']) || isset($cont['search'])) + { + $cont['result'] = $this->etemplate->search($cont); + } + $result = $cont['result']; + + if (isset($cont['delete'])) + { + list($delete) = each($cont['delete']); + $this->etemplate->init($result[$delete-1]); + if ($this->etemplate->delete()) + { + $msg = lang('Template deleted'); + unset($result[$delete-1]); + $result = array_values($result); + } + else + { + $msg = lang('Error: Template not found !!!'); + } + } + if (isset($cont['delete_selected'])) + { + foreach($cont['selected'] as $row => $sel) + { + if ($sel) { - $msg = lang('Template deleted'); - unset($result[$delete-1]); - $result = array_values($result); + $this->etemplate->init($result[$row-1]); + if ($this->etemplate->delete()) + { + unset($result[$row-1]); + ++$n; + } + } + } + if ($n) + { + $msg = lang('%1 eTemplates deleted',$n); + } + unset($cont['selected']); + unset($cont['delete_selected']); + $result = array_values($result); + } + if (isset($cont['read'])) + { + list($read) = each($cont['read']); + $this->etemplate->read($result[$read-1]); + $this->edit(); + return; + } + if (!$msg) + { + $msg = lang('%1 eTemplates found',count($result)); + } + unset($cont['result']); + if (!isset($cont['name'])) + { + $cont += $this->etemplate->as_array(); + } + $content = $cont + array('msg' => $msg); + + reset($result); + for ($row=1; list(,$param) = each($result); ++$row) + { + $content[$row] = $param; + } + $list_result =& new etemplate('etemplate.editor.list_result'); + $GLOBALS['egw_info']['flags']['app_header'] = lang('Editable Templates - Search'); + $list_result->exec('etemplate.editor.list_result',$content,'','',array( + 'result' => $result, + ),''); + } + + /** + * new eTemplate editor, which edits widgets in a popup + * + * @param array $content content from the process_exec call + * @param string $msg message to show + */ + function edit($content=null,$msg = '') + { + if ($this->debug) + { + echo "

etemplate.editor.show: content="; _debug_array($content); + } + if (!is_array($content)) $content = array(); + $preserv = array(); + + if ($content['import_xml']) + { + $msg .= $this->import_xml($content['file']['tmp_name'],$xml); + //$this->etemplate->echo_tmpl(); + $xml_label = $content['file']['name']; + $preserv['import'] = $this->etemplate->as_array(1); + } + elseif (is_array($content['import']) && !$content['read']) // imported not yet saved tmpl + { + $this->etemplate->init($content['import']); + $preserv['import'] = $content['import']; + } + if ($content['save']) + { + if (!is_array($content['import'])) $this->etemplate->read($content['old_keys']); + + if (!$this->etemplate->modified_set || !$this->etemplate->modified) + { + $this->etemplate->modified = time(); + } + $ok = $this->etemplate->save(trim($content['name']),trim($content['template']),trim($content['lang']),(int) $content['group'],trim($content['version'])); + $msg = $ok ? lang('Template saved') : lang('Error: while saving !!!'); + if ($ok) unset($preserv['import']); + } + elseif (!$content['import_xml'] && (isset($_GET['name']) || isset($content['name']))) + { + if ($_GET['name']) + { + foreach(etemplate::$db_key_cols as $var) + { + $content[$var] = $_GET[$var]; + } + } + if ($content['version'] != '') + { + $save_version = $content['version']; + unset($content['version']); + $this->etemplate->read($content); + $newest_version = $this->etemplate->version; + $content['version'] = $save_version; + } + if (!$this->etemplate->read($content)) + { + if (isset($content['name'])) + { + $version_backup = $content['version']; + $content['version'] = ''; // trying it without version + if ($this->etemplate->read($content)) + { + $msg = lang('only an other Version found !!!'); + } + else + { + $result = $this->etemplate->search($content); + if (count($result) > 1) + { + return $this->list_result(array('result' => $result)); + } + elseif (!count($result) || !$this->etemplate->read($result[0])) + { + $msg = lang('Error: Template not found !!!'); + $this->etemplate->version = $content['version'] = $version_backup; + } + elseif ($content['name'] == $result[0]['name']) + { + $msg = lang('only an other Version found !!!'); + } + } } else { $msg = lang('Error: Template not found !!!'); } } - if (isset($cont['delete_selected'])) + elseif ($newest_version != '' && $this->etemplate->version != $newest_version) { - foreach($cont['selected'] as $row => $sel) - { - if ($sel) - { - $this->etemplate->init($result[$row-1]); - if ($this->etemplate->delete()) - { - unset($result[$row-1]); - ++$n; - } - } - } - if ($n) - { - $msg = lang('%1 eTemplates deleted',$n); - } - unset($cont['selected']); - unset($cont['delete_selected']); - $result = array_values($result); + $link = $this->etemplate->as_array(-1); + $link['menuaction'] = 'etemplate.editor.edit'; + $link['version'] = $newest_version; + $msg = lang("newer version '%1' exists !!!",html::a_href($newest_version,$link)); } - if (isset($cont['read'])) - { - list($read) = each($cont['read']); - $this->etemplate->read($result[$read-1]); - $this->edit(); - return; - } - if (!$msg) - { - $msg = lang('%1 eTemplates found',count($result)); - } - unset($cont['result']); - if (!isset($cont['name'])) - { - $cont += $this->etemplate->as_array(); - } - $content = $cont + array('msg' => $msg); - - reset($result); - for ($row=1; list(,$param) = each($result); ++$row) - { - $content[$row] = $param; - } - $list_result =& new etemplate('etemplate.editor.list_result'); - $GLOBALS['egw_info']['flags']['app_header'] = lang('Editable Templates - Search'); - $list_result->exec('etemplate.editor.list_result',$content,'','',array( - 'result' => $result, - ),''); } - - /** - * new eTemplate editor, which edits widgets in a popup - * - * @param array $content content from the process_exec call - * @param string $msg message to show - */ - function edit($content=null,$msg = '') + if (!is_array($this->extensions)) { - if ($this->debug) + if (($extensions = $this->scan_for_extensions())) { - echo "

etemplate.editor.show: content="; _debug_array($content); + $msg .= lang('Extensions loaded:') . ' ' . $extensions; + $msg_ext_loaded = True; } - if (!is_array($content)) $content = array(); - $preserv = array(); + } + list($app) = explode('.',$this->etemplate->name); + if ($app && $app != 'etemplate') + { + $GLOBALS['egw']->translation->add_app($app); // load translations for app - if ($content['import_xml']) + if (($extensions = $this->scan_for_extensions($app))) { - $msg .= $this->import_xml($content['file']['tmp_name'],$xml); - //$this->etemplate->echo_tmpl(); - $xml_label = $content['file']['name']; - $preserv['import'] = $this->etemplate->as_array(1); + $msg .= (!$msg_ext_loaded?lang('Extensions loaded:').' ':', ') . $extensions; } - elseif (is_array($content['import']) && !$content['read']) // imported not yet saved tmpl + } + if (!$msg && $content['delete']) + { + if (!$content['version'] && $this->etemplate->version) { - $this->etemplate->init($content['import']); - $preserv['import'] = $content['import']; + $this->etemplate->version = ''; // else the newest would get deleted and not the one without version } - if ($content['save']) + $ok = $this->etemplate->delete(); + $msg = $ok ? lang('Template deleted') : lang('Error: Template not found !!!'); + $preserv['import'] = $this->etemplate->as_array(1); // that way the content can be saved again + } + elseif ($content['dump']) + { + if (empty($app) || !@is_dir(EGW_SERVER_ROOT.'/'.$app)) { - if (!is_array($content['import'])) $this->etemplate->read($content['old_keys']); - - if (!$this->etemplate->modified_set || !$this->etemplate->modified) - { - $this->etemplate->modified = time(); - } - $ok = $this->etemplate->save(trim($content['name']),trim($content['template']),trim($content['lang']),(int) $content['group'],trim($content['version'])); - $msg = $ok ? lang('Template saved') : lang('Error: while saving !!!'); - if ($ok) unset($preserv['import']); - } - elseif (!$content['import_xml'] && (isset($_GET['name']) || isset($content['name']))) - { - if ($_GET['name']) - { - foreach($this->etemplate->db_key_cols as $var) - { - $content[$var] = $_GET[$var]; - } - } - if ($content['version'] != '') - { - $save_version = $content['version']; - unset($content['version']); - $this->etemplate->read($content); - $newest_version = $this->etemplate->version; - $content['version'] = $save_version; - } - if (!$this->etemplate->read($content)) - { - if (isset($content['name'])) - { - $version_backup = $content['version']; - $content['version'] = ''; // trying it without version - if ($this->etemplate->read($content)) - { - $msg = lang('only an other Version found !!!'); - } - else - { - $result = $this->etemplate->search($content); - if (count($result) > 1) - { - return $this->list_result(array('result' => $result)); - } - elseif (!count($result) || !$this->etemplate->read($result[0])) - { - $msg = lang('Error: Template not found !!!'); - $this->etemplate->version = $content['version'] = $version_backup; - } - elseif ($content['name'] == $result[0]['name']) - { - $msg = lang('only an other Version found !!!'); - } - } - } - else - { - $msg = lang('Error: Template not found !!!'); - } - } - elseif ($newest_version != '' && $this->etemplate->version != $newest_version) - { - $link = $this->etemplate->as_array(-1); - $link['menuaction'] = 'etemplate.editor.edit'; - $link['version'] = $newest_version; - $msg = lang("newer version '%1' exists !!!",html::a_href($newest_version,$link)); - } - } - if (!is_array($this->extensions)) - { - if (($extensions = $this->scan_for_extensions())) - { - $msg .= lang('Extensions loaded:') . ' ' . $extensions; - $msg_ext_loaded = True; - } - } - list($app) = explode('.',$this->etemplate->name); - if ($app && $app != 'etemplate') - { - $GLOBALS['egw']->translation->add_app($app); // load translations for app - - if (($extensions = $this->scan_for_extensions($app))) - { - $msg .= (!$msg_ext_loaded?lang('Extensions loaded:').' ':', ') . $extensions; - } - } - if (!$msg && $content['delete']) - { - if (!$content['version'] && $this->etemplate->version) - { - $this->etemplate->version = ''; // else the newest would get deleted and not the one without version - } - $ok = $this->etemplate->delete(); - $msg = $ok ? lang('Template deleted') : lang('Error: Template not found !!!'); - $preserv['import'] = $this->etemplate->as_array(1); // that way the content can be saved again - } - elseif ($content['dump']) - { - if (empty($app) || !@is_dir(EGW_SERVER_ROOT.'/'.$app)) - { - $msg .= lang('Application name needed to write a langfile or dump the eTemplates !!!'); - } - else - { - $msg .= $this->etemplate->dump4setup($app); - } - } - elseif ($content['langfile']) - { - if (empty($app) || !@is_dir(EGW_SERVER_ROOT.'/'.$app)) - { - $msg = lang('Application name needed to write a langfile or dump the eTemplates !!!'); - } - else - { - $additional = array(); - if ($app == 'etemplate') - { - $additional = $this->etemplate->types + $this->extensions + $this->aligns + $this->valigns + - $this->edit_menu + $this->box_menu + $this->row_menu + $this->column_menu + $this->onclick_types + $this->onchange_types; - } - else // try to call the writeLangFile function of the app's ui-layer - { - foreach(array('ui'.$name,'ui',$name,'bo'.$name) as $class) - { - if (file_exists(EGW_INCLUDE_ROOT.'/'.$name.'/inc/class.'.$class.'.inc.php') && - ($ui =& CreateObject($name.'.'.$class)) && is_object($ui)) - { - break; - } - } - if (is_object($ui) && @$ui->public_functions['writeLangFile']) - { - $msg = "$class::writeLangFile: ".$ui->writeLangFile(); - } - unset($ui); - } - //if (empty($msg)) - { - $msg = $this->etemplate->writeLangFile($app,'en',$additional); - } - } - } - elseif ($content['export_xml']) - { - $msg .= $this->export_xml($xml,$xml_label); - } - $new_content = $this->etemplate->as_array() + array( - 'msg' => $msg, - 'xml_label' => $xml_label, - 'xml' => $xml ? '

'.html::htmlspecialchars($xml)."
\n" : '', - ); - - $editor =& new etemplate('etemplate.editor.new'); - if (!$msg && isset($content['values']) && !isset($content['vals'])) - { - $r = 1; - foreach((array)$content['cont'] as $key => $val) - { - $vals["@$r"] = $key; - $vals["A$r"] = is_array($val) ? serialize($val).'#SeR#' : $val; - ++$r; - } - $editor->data[$editor->rows]['A']['name'] = 'etemplate.editor.values'; - $editor->data[$editor->rows]['A']['size'] = 'vals'; - $new_content['vals'] = $vals; + $msg .= lang('Application name needed to write a langfile or dump the eTemplates !!!'); } else { - // set onclick handler - $this->etemplate->onclick_handler = "edit_widget('%p');"; - // setting the javascript via the content, allows looping too - $new_content['onclick'] = ' - '; - if ($app != 'etemplate' && file_exists(EGW_SERVER_ROOT.'/'.$app.'/templates/default/app.css')) - { - $new_content['onclick'] .= html::style('@import url('.$GLOBALS['egw_info']['server']['webserver_url'].'/'.$app.'/templates/default/app.css);'); - } - $editor->data[$editor->rows]['A']['obj'] = &$this->etemplate; - $vals = $content['vals']; - $olds = $content['olds']; - - for ($r = 1; isset($vals["A$r"]); ++$r) - { - $new_content['cont'][$olds["@$r"]] = substr($vals["A$r"],-5)=='#SeR#' ? - unserialize(substr($vals["A$r"],0,-5)) : $vals["A$r"]; - } + $msg .= $this->etemplate->dump4setup($app); } - $preserv['olds'] = $vals; - $preserv['old_keys'] = $this->etemplate->as_array(-1); // in case we do a save as - - $GLOBALS['egw_info']['flags']['app_header'] = lang('Editable Templates - Show Template'); - $editor->exec('etemplate.editor.edit',$new_content,array(),'',$preserv,0,'/^cont/'); } - - /** - * initialises the children arrays for the new widget type, converts boxes <--> grids - * - * @internal - * @param array &$widget reference to the new widget data - * @param array $old the old widget data - */ - function change_widget_type(&$widget,$old) + elseif ($content['langfile']) { - //echo "

editor::change_widget_type($widget[type]=$old[type])

\n"; - $old_type = $old['type']; - $old_had_children = isset($this->etemplate->widgets_with_children[$old_type]); - - if (!isset($this->etemplate->widgets_with_children[$widget['type']]) || - ($old_type == 'grid') == ($widget['type'] == 'grid')) + if (empty($app) || !@is_dir(EGW_SERVER_ROOT.'/'.$app)) { - if ($this->etemplate->widgets_with_children[$widget['type']] == 'box') // box + $msg = lang('Application name needed to write a langfile or dump the eTemplates !!!'); + } + else + { + $additional = array(); + if ($app == 'etemplate') { - if ((int) $widget['size'] < 1) // min. 1 child - { - list(,$options) = explode(',',$widget['size'],2); - $widget['size'] = '1'.($options ? ','.$options : ''); - } - // create the needed cells, if they dont exist - for ($n = 1; $n <= (int) $widget['size']; ++$n) - { - if (!is_array($widget[$n])) $widget[$n] = $n == 1 ? $old : soetemplate::empty_cell(); - } - unset($widget['onclick']); // not valid for a box + $additional = $this->etemplate->types + $this->extensions + $this->aligns + $this->valigns + + $this->edit_menu + $this->box_menu + $this->row_menu + $this->column_menu + $this->onclick_types + $this->onchange_types; } - return; // no change necessary, eg. between different box-types - } - switch ($this->etemplate->widgets_with_children[$widget['type']]) - { - case 'grid': - $widget['data'] = array(array()); - $widget['cols'] = $widget['rows'] = 0; - - if ($old_had_children) // box --> grid: hbox --> 1 row, other boxes --> 1 column - { - list($num) = explode(',',$old['size']); - for ($n = 1; is_array($old[$n]) && $n <= $num; ++$n) - { - $new_line = null; - if ($old_type != 'hbox') soetemplate::add_child($widget,$new_line); - soetemplate::add_child($widget,$old[$n]); - unset($widget[$n]); - } - $widget['size'] = ''; - } - else // 1 row with 1 column/child - { - soetemplate::add_child($widget,$cell=soetemplate::empty_cell()); - } - break; - - case 'box': - $widget['size'] = 0; - - if ($old_type == 'grid') - { - if (preg_match('/,(vertical|horizontal)/',$widget['size'],$matches)) - { - $orient = $matches[1]; - } - else - { - $orient = $widget['type'] == 'hbox' ? 'horizontal' : 'vertical'; - } - if ($orient == 'horizontal') // ==> use first row - { - $row =& $old['data'][1]; - for ($n = 0; $n < $old['cols']; ++$n) - { - $cell =& $row[soetemplate::num2chrs($n)]; - soetemplate::add_child($widget,$cell); - list($span) = (int)explode(',',$cell['span']); - if ($span == 'all') break; - while ($span-- > 1) ++$n; - } - } - else // vertical ==> use 1 column - { - for ($n = 1; $n <= $old['rows']; ++$n) - { - soetemplate::add_child($widget,$old['data'][$n][soetemplate::num2chrs(0)]); - } - } - } - if (!$widget['size']) // minimum one child - { - soetemplate::add_child($widget,soetemplate::empty_cell()); - } - break; - } - //_debug_array($widget); - } - - /** - * returns array with path => type pairs for each parent of $path - * - * @param string $path path to the widget not the parent! - * @return array - */ - function path_components($path) - { - $path_parts = explode('/',$path); - array_pop($path_parts); // removed the widget itself - array_shift($path_parts); // removed the leading empty string - - $components = array(); - $part_path = ''; - foreach($path_parts as $part) - { - $part_path .= '/'.$part; - $parent =& $this->etemplate->get_widget_by_path($part_path); - $components[$part_path] = $parent['type']; - } - return $components; - } - - /** - * returns array with path => type pairs for each parent of $path - * - * @param array $parent the parent - * @param string $child_id id of child - * @param string $parent_path path of the parent - * @return array with keys left, right, up and down and their pathes set (if applicable) - */ - function parent_navigation($parent,$parent_path,$child_id,$widget) - { - if ($parent['type'] == 'grid' && preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) - { - list(,$r,$c) = $matches; - // find the column-number (base 0) for $c (A, B, C, ...) - for($col = 0; soetemplate::num2chrs($col) != $c && $col < 100; ++$col) ; - - if ($col > 0) $left = $parent_path.'/'.$r.soetemplate::num2chrs($col-1); - - if ($col < $parent['cols']-1) $right = $parent_path.'/'.$r.soetemplate::num2chrs($col+1); - - if ($r > 1) $up = $parent_path.'/'.($r-1).$c; - - if ($r < $parent['rows']) $down = $parent_path.'/'.($r+1).$c; - } - elseif ($parent['type']) // any box - { - if ($child_id > 1) $previous = $parent_path.'/'.($child_id-1); - - if ($child_id < (int) $parent['size']) $next = $parent_path.'/'.($child_id+1); - } - else // template - { - if ($child_id > 0) $previous = '/'.($child_id-1); - - if ($child_id < count($this->etemplate->children)-1) $next = '/'.($child_id+1); - } - if ($widget['type'] == 'grid') - { - $in = $parent_path.'/'.$child_id.'/1A'; - } - elseif (isset($this->etemplate->widgets_with_children[$widget['type']]) && $widget['type'] != 'template') - { - if ($widget['type']) // box + else // try to call the writeLangFile function of the app's ui-layer { - $in = $parent_path.'/'.$child_id.'/1'; + foreach(array('ui'.$name,'ui',$name,'bo'.$name) as $class) + { + if (file_exists(EGW_INCLUDE_ROOT.'/'.$name.'/inc/class.'.$class.'.inc.php') && + ($ui =& CreateObject($name.'.'.$class)) && is_object($ui)) + { + break; + } + } + if (is_object($ui) && @$ui->public_functions['writeLangFile']) + { + $msg = "$class::writeLangFile: ".$ui->writeLangFile(); + } + unset($ui); } - else + //if (empty($msg)) { - $in = '/0'; + $msg = $this->etemplate->writeLangFile($app,'en',$additional); } } - $navi = array(); - foreach(array('left'=>'←','up'=>' ↑ ','down'=>' ↓ ', - 'right'=>'→','previous'=>'←↑','next'=>'↓→','in'=>'×') as $var => $dir) - { - if ($$var) $navi[$$var] = $dir; - } - return $navi; } - - /** - * functions of the edit-menu: paste, swap, cut, delete, copy - * - * @internal - * @param string &$action row_delete, row_insert_above, row_insert_below, row_swap, row_prefs - * @param array &$parent referece to the parent - * @param array &$content reference to the content-array - * @param string $child_id id of a cell - * @return string msg to display - */ - function edit_actions(&$action,&$parent,&$content,$child_id) + elseif ($content['export_xml']) { - switch ($action) - { - case 'paste': - case 'swap': - $clipboard = $GLOBALS['egw']->session->appsession('clipboard','etemplate'); - if (!is_array($clipboard)) - { - return lang('nothing in clipboard to paste !!!'); - } - if ($action == 'swap') - { - $GLOBALS['egw']->session->appsession('clipboard','etemplate',$content['cell']); - } - $content['cell'] = $clipboard; - break; + $msg .= $this->export_xml($xml,$xml_label); + } + $new_content = $this->etemplate->as_array() + array( + 'msg' => $msg, + 'xml_label' => $xml_label, + 'xml' => $xml ? '
'.html::htmlspecialchars($xml)."
\n" : '', + ); - case 'copy': - case 'cut': - $GLOBALS['egw']->session->appsession('clipboard','etemplate',$content['cell']); - if ($action != 'cut') + $editor =& new etemplate('etemplate.editor.new'); + if (!$msg && isset($content['values']) && !isset($content['vals'])) + { + $r = 1; + foreach((array)$content['cont'] as $key => $val) + { + $vals["@$r"] = $key; + $vals["A$r"] = is_array($val) ? serialize($val).'#SeR#' : $val; + ++$r; + } + $editor->data[$editor->rows]['A']['name'] = 'etemplate.editor.values'; + $editor->data[$editor->rows]['A']['size'] = 'vals'; + $new_content['vals'] = $vals; + } + else + { + // set onclick handler + $this->etemplate->onclick_handler = "edit_widget('%p');"; + // setting the javascript via the content, allows looping too + $new_content['onclick'] = ' + '; + if ($app != 'etemplate' && file_exists(EGW_SERVER_ROOT.'/'.$app.'/templates/default/app.css')) + { + $new_content['onclick'] .= html::style('@import url('.$GLOBALS['egw_info']['server']['webserver_url'].'/'.$app.'/templates/default/app.css);'); + } + $editor->data[$editor->rows]['A']['obj'] = &$this->etemplate; + $vals = $content['vals']; + $olds = $content['olds']; + + for ($r = 1; isset($vals["A$r"]); ++$r) + { + $new_content['cont'][$olds["@$r"]] = substr($vals["A$r"],-5)=='#SeR#' ? + unserialize(substr($vals["A$r"],0,-5)) : $vals["A$r"]; + } + } + $preserv['olds'] = $vals; + $preserv['old_keys'] = $this->etemplate->as_array(-1); // in case we do a save as + + $GLOBALS['egw_info']['flags']['app_header'] = lang('Editable Templates - Show Template'); + $editor->exec('etemplate.editor.edit',$new_content,array(),'',$preserv,0,'/^cont/'); + } + + /** + * initialises the children arrays for the new widget type, converts boxes <--> grids + * + * @internal + * @param array &$widget reference to the new widget data + * @param array $old the old widget data + */ + function change_widget_type(&$widget,$old) + { + //echo "

editor::change_widget_type($widget[type]=$old[type])

\n"; + $old_type = $old['type']; + $old_had_children = isset(etemplate::$widgets_with_children[$old_type]); + + if (!isset(etemplate::$widgets_with_children[$widget['type']]) || + ($old_type == 'grid') == ($widget['type'] == 'grid')) + { + if (etemplate::$widgets_with_children[$widget['type']] == 'box') // box + { + if ((int) $widget['size'] < 1) // min. 1 child + { + list(,$options) = explode(',',$widget['size'],2); + $widget['size'] = '1'.($options ? ','.$options : ''); + } + // create the needed cells, if they dont exist + for ($n = 1; $n <= (int) $widget['size']; ++$n) + { + if (!is_array($widget[$n])) $widget[$n] = $n == 1 ? $old : etemplate::empty_cell(); + } + unset($widget['onclick']); // not valid for a box + } + return; // no change necessary, eg. between different box-types + } + switch (etemplate::$widgets_with_children[$widget['type']]) + { + case 'grid': + $widget['data'] = array(array()); + $widget['cols'] = $widget['rows'] = 0; + + if ($old_had_children) // box --> grid: hbox --> 1 row, other boxes --> 1 column + { + list($num) = explode(',',$old['size']); + for ($n = 1; is_array($old[$n]) && $n <= $num; ++$n) { - return lang('widget copied into clipboard'); + $new_line = null; + if ($old_type != 'hbox') etemplate::add_child($widget,$new_line); + etemplate::add_child($widget,$old[$n]); + unset($widget[$n]); } - // fall-through - case 'delete': - if ($parent['type'] != 'grid') + $widget['size'] = ''; + } + else // 1 row with 1 column/child + { + etemplate::add_child($widget,$cell=etemplate::empty_cell()); + } + break; + + case 'box': + $widget['size'] = 0; + + if ($old_type == 'grid') + { + if (preg_match('/,(vertical|horizontal)/',$widget['size'],$matches)) { - // delete widget from parent - if ($parent['type']) // box - { - list($num,$options) = explode(',',$parent['size'],2); - if ($num <= 1) // cant delete last child --> only empty it - { - $parent[$num=1] = soetemplate::empty_cell(); - } - else - { - for($n = $child_id; $n < $num; ++$n) - { - $parent[$n] = $parent[1+$n]; - } - unset($parent[$num--]); - } - $parent['size'] = $num . ($options ? ','.$options : ''); - } - else // template itself - { - if (count($this->etemplate->children) <= 1) // cant delete last child - { - $this->etemplate->children[0] = soetemplate::empty_cell(); - } - else - { - unset($parent[$child_id]); - $this->etemplate->children = array_values($this->etemplate->children); - } - } - $action = 'save-no-merge'; + $orient = $matches[1]; } else { - $content['cell'] = soetemplate::empty_cell(); - return lang('cant delete a single widget from a grid !!!'); + $orient = $widget['type'] == 'hbox' ? 'horizontal' : 'vertical'; } - break; - } - return ''; - } - - /** - * functions of the box-menu: insert-before, -behind und swap - * - * @internal - * @param string &$action row_delete, row_insert_above, row_insert_below, row_swap, row_prefs - * @param array &$parent referece to the parent - * @param array &$content reference to the content-array - * @param string &$child_id id of a cell, may change to the next cell if inserting behind - * @param string $parent_path path of parent - * @return string msg to display - */ - function box_actions(&$action,&$parent,&$content,&$child_id,$parent_path) - { - switch ($action) - { - case 'box_insert_before': - case 'box_insert_behind': - $n = $child_id + (int)($action == 'box_insert_behind'); - if (!$parent['type']) // template + if ($orient == 'horizontal') // ==> use first row { - $num = count($parent)-1; // 0..count()-1 + $row =& $old['data'][1]; + for ($n = 0; $n < $old['cols']; ++$n) + { + $cell =& $row[etemplate::num2chrs($n)]; + etemplate::add_child($widget,$cell); + list($span) = (int)explode(',',$cell['span']); + if ($span == 'all') break; + while ($span-- > 1) ++$n; + } } - else // boxes + else // vertical ==> use 1 column + { + for ($n = 1; $n <= $old['rows']; ++$n) + { + etemplate::add_child($widget,$old['data'][$n][etemplate::num2chrs(0)]); + } + } + } + if (!$widget['size']) // minimum one child + { + etemplate::add_child($widget,etemplate::empty_cell()); + } + break; + } + //_debug_array($widget); + } + + /** + * returns array with path => type pairs for each parent of $path + * + * @param string $path path to the widget not the parent! + * @return array + */ + function path_components($path) + { + $path_parts = explode('/',$path); + array_pop($path_parts); // removed the widget itself + array_shift($path_parts); // removed the leading empty string + + $components = array(); + $part_path = ''; + foreach($path_parts as $part) + { + $part_path .= '/'.$part; + $parent =& $this->etemplate->get_widget_by_path($part_path); + $components[$part_path] = $parent['type']; + } + return $components; + } + + /** + * returns array with path => type pairs for each parent of $path + * + * @param array $parent the parent + * @param string $child_id id of child + * @param string $parent_path path of the parent + * @return array with keys left, right, up and down and their pathes set (if applicable) + */ + function parent_navigation($parent,$parent_path,$child_id,$widget) + { + if ($parent['type'] == 'grid' && preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) + { + list(,$r,$c) = $matches; + // find the column-number (base 0) for $c (A, B, C, ...) + for($col = 0; etemplate::num2chrs($col) != $c && $col < 100; ++$col) ; + + if ($col > 0) $left = $parent_path.'/'.$r.etemplate::num2chrs($col-1); + + if ($col < $parent['cols']-1) $right = $parent_path.'/'.$r.etemplate::num2chrs($col+1); + + if ($r > 1) $up = $parent_path.'/'.($r-1).$c; + + if ($r < $parent['rows']) $down = $parent_path.'/'.($r+1).$c; + } + elseif ($parent['type']) // any box + { + if ($child_id > 1) $previous = $parent_path.'/'.($child_id-1); + + if ($child_id < (int) $parent['size']) $next = $parent_path.'/'.($child_id+1); + } + else // template + { + if ($child_id > 0) $previous = '/'.($child_id-1); + + if ($child_id < count($this->etemplate->children)-1) $next = '/'.($child_id+1); + } + if ($widget['type'] == 'grid') + { + $in = $parent_path.'/'.$child_id.'/1A'; + } + elseif (isset(etemplate::$widgets_with_children[$widget['type']]) && $widget['type'] != 'template') + { + if ($widget['type']) // box + { + $in = $parent_path.'/'.$child_id.'/1'; + } + else + { + $in = '/0'; + } + } + $navi = array(); + foreach(array('left'=>'←','up'=>' ↑ ','down'=>' ↓ ', + 'right'=>'→','previous'=>'←↑','next'=>'↓→','in'=>'×') as $var => $dir) + { + if ($$var) $navi[$$var] = $dir; + } + return $navi; + } + + /** + * functions of the edit-menu: paste, swap, cut, delete, copy + * + * @internal + * @param string &$action row_delete, row_insert_above, row_insert_below, row_swap, row_prefs + * @param array &$parent referece to the parent + * @param array &$content reference to the content-array + * @param string $child_id id of a cell + * @return string msg to display + */ + function edit_actions(&$action,&$parent,&$content,$child_id) + { + switch ($action) + { + case 'paste': + case 'swap': + $clipboard = $GLOBALS['egw']->session->appsession('clipboard','etemplate'); + if (!is_array($clipboard)) + { + return lang('nothing in clipboard to paste !!!'); + } + if ($action == 'swap') + { + $GLOBALS['egw']->session->appsession('clipboard','etemplate',$content['cell']); + } + $content['cell'] = $clipboard; + break; + + case 'copy': + case 'cut': + $GLOBALS['egw']->session->appsession('clipboard','etemplate',$content['cell']); + if ($action != 'cut') + { + return lang('widget copied into clipboard'); + } + // fall-through + case 'delete': + if ($parent['type'] != 'grid') + { + // delete widget from parent + if ($parent['type']) // box { list($num,$options) = explode(',',$parent['size'],2); - } - for($i = $num; $i >= $n; --$i) - { - $parent[1+$i] = $parent[$i]; - } - $parent[$n] = $content['cell'] = soetemplate::empty_cell(); - $child_id = $n; - if ($parent['type']) $parent['size'] = (1+$num) . ($options ? ','.$options : ''); - break; - - case 'box_swap_next': - if (!$parent['type']) // template - { - $num = count($parent)-1; // 0..count()-1 - } - else // boxes - { - list($num) = explode(',',$parent['size'],2); - } - if ($child_id == $num) // if on last cell, swap with the one before - { - $this->swap($parent[$child_id],$parent[$child_id-1]); - --$child_id; - } - else - { - $this->swap($parent[$child_id],$parent[$child_id+1]); - ++$child_id; - } - break; - } - $action = 'apply-no-merge'; - - return ''; - } - - /** - * functions of the row-menu: insert, deleting & swaping of rows - * - * @internal - * @param string &$action row_delete, row_insert_above, row_insert_below, row_swap_next, row_prefs - * @param array &$grid grid - * @param string $child_id id of a cell - * @return string msg to display - */ - function row_actions(&$action,&$grid,$child_id) - { - $data =& $grid['data']; - $rows =& $grid['rows']; - $cols =& $grid['cols']; - $opts =& $data[0]; - - if (preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) list(,$r,$c) = $matches; - - if (!$c || !$r || $r > $rows) return "wrong child_id='$child_id' => r='$r', c='$c'"; - - switch($action) - { - case 'row_swap_next': - if ($r > $rows-1) - { - if ($r != $rows) return lang('no row to swap with !!!'); - --$r; // in last row swap with row above - } - $this->swap($data[$r],$data[1+$r]); - $this->swap($opts['c'.$r],$opts['c'.(1+$r)]); - $this->swap($opts['h'.$r],$opts['h'.(1+$r)]); - break; - - case 'row_delete': - if ($rows <= 1) // one row only => delete whole grid - { - return lang('cant delete the only row in a grid !!!'); - // todo: delete whole grid instead - } - for($i = $r; $i < $rows; ++$i) - { - $opts['c'.$i] = $opts['c'.(1+$i)]; - $opts['h'.$i] = $opts['h'.(1+$i)]; - $data[$i] = $data[1+$i]; - } - unset($opts['c'.$rows]); - unset($opts['h'.$rows]); - unset($data[$rows--]); - break; - - case 'row_insert_above': - --$r; - // fall-through - case 'row_insert_below': - //echo "row_insert_below($r) rows=$rows, cols=$cols"; _debug_array($grid); - // move height + class options of rows - for($i = $rows; $i > $r; --$i) - { - echo ($i+1)."=$i
\n"; - $data[1+$i] = $data[$i]; - $opts['c'.(1+$i)] = $opts['c'.$i]; - $opts['h'.(1+$i)] = $opts['h'.$i]; - } - for($i = 0; $i < $cols; ++$i) - { - echo (1+$r).":$i=".soetemplate::num2chrs($i)."=empty_cell()
\n"; - $data[1+$r][soetemplate::num2chrs($i)] = soetemplate::empty_cell(); - } - $opts['c'.(1+$r)] = $opts['h'.(1+$r)] = ''; - ++$rows; - //_debug_array($grid); return ''; - break; - } - $action = 'save-no-merge'; - - return ''; - } - - /** - * functions of the column-menu: insert, deleting & swaping of columns - * - * @internal - * @param string &$action column_delete, column_insert_before, column_insert_behind, column_swap_next, column_prefs - * @param array &$grid grid - * @param string $child_id id of a cell - * @return string msg to display - */ - function column_actions(&$action,&$grid,$child_id) - { - $data =& $grid['data']; - $rows =& $grid['rows']; - $cols =& $grid['cols']; - $opts =& $data[0]; - - if (preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) list(,$r,$c) = $matches; - // find the column-number (base 0) for $c (A, B, C, ...) - for($col = 0; soetemplate::num2chrs($col) != $c && $col < 100; ++$col) ; - - if (!$c || !$r || $r > $rows || $col >= $cols) return "wrong child_id='$child_id' => r='$r', c='$c', col=$col"; - - switch($action) - { - case 'column_swap_next': - if ($col >= $cols-1) - { - if ($col != $cols-1) return lang('no column to swap with !!!'); - $c = soetemplate::num2chrs(--$col); // in last column swap with the one before - } - $c_next = soetemplate::num2chrs(1+$col); - for($row = 1; $row <= $rows; ++$row) - { - $this->swap($data[$row][$c],$data[$row][$c_next]); - } - $this->swap($opts[$c],$opts[$c_next]); - //_debug_array($grid); return ''; - break; - - case 'column_insert_behind': - ++$col; - case 'column_insert_before': - //echo "

column_insert_before: col=$col

\n"; - // $col is where the new column data goes - for ($row = 1; $row <= $rows; ++$row) - { - for ($i = $cols; $i > $col; --$i) + if ($num <= 1) // cant delete last child --> only empty it { - $data[$row][soetemplate::num2chrs($i)] = $data[$row][soetemplate::num2chrs($i-1)]; - } - $data[$row][soetemplate::num2chrs($col)] = soetemplate::empty_cell(); - } - for ($i = $cols; $i > $col; --$i) - { - $opts[soetemplate::num2chrs($i)] = $opts[soetemplate::num2chrs($i-1)]; - } - unset($opts[soetemplate::num2chrs($col)]); - ++$cols; - //_debug_array($grid); return ''; - break; - - case 'column_delete': - if ($cols <= 1) - { - return lang('cant delete the only column of a grid !!!'); - // todo: delete whole grid instead - } - for ($row = 1; $row <= $rows; ++$row) - { - for ($i = $col; $i < $cols-1; ++$i) - { - $data[$row][soetemplate::num2chrs($i)] = $data[$row][soetemplate::num2chrs($i+1)]; - } - unset($data[$row][soetemplate::num2chrs($cols-1)]); - } - for ($i = $col; $i < $cols-1; ++$i) - { - $opts[soetemplate::num2chrs($i)] = $opts[soetemplate::num2chrs($i+1)]; - } - unset($opts[soetemplate::num2chrs(--$cols)]); - break; - } - $action = 'save-no-merge'; - - return ''; - } - - /** - * converts onclick selectbox and onclick text to one javascript call - * - * @param array &$widget reference into the widget-tree - * @param array &$cell_content cell array in content - * @param boolean $widget2content=true copy from widget to content or other direction - */ - function fix_set_onclick(&$widget,&$cell_content,$widget2content=true) - { - if ($widget2content) - { - if (preg_match('/^return confirm\(["\']{1}?(.*)["\']{1}\);?$/',$widget['onclick'],$matches)) - { - $cell_content['onclick'] = $matches[1]; - $cell_content['onclick_type'] = 'confirm'; - } - elseif (preg_match('/^window.open\(egw::link\(\'\/index.php\',\'([^\']+)\'\)(\+values2url\(.*\))?,\'([^\']+)\',\'dependent=yes,width=([0-9]+),height=([0-9]+),scrollbars=yes,status=yes\'\); return false;$/',$widget['onclick'],$matches)) - { - $cell_content['onclick'] = $matches[1].($matches[2] ? str_replace('+values2url(this.form,','&values2url(',$matches[2]) : ''); - if ($matches[3] != '_blank') - { - $cell_content['onclick'] .= ','.$matches[3]; - } - if ($matches[4] != '600') - { - $cell_content['onclick'] .= ($matches[3]=='_blank' ? ',':'').','.$matches[4]; - } - if ($matches[5] != '450') - { - $cell_content['onclick'] .= ($matches[4]=='600' ? ','.($matches[3]=='_blank' ? ',':'') : ''). - ','.$matches[5]; - } - $cell_content['onclick_type'] = 'popup'; - } - else - { - $cell_content['onclick_type'] = !$widget['onclick'] ? '' : 'custom'; - } - } - else // content --> widget - { - if (preg_match('/^return confirm\(["\']{1}?(.*)["\']{1}\);?$/',$cell_content['onclick'],$matches) || - $cell_content['onclick_type'] == 'confirm' && $cell_content['onclick']) - { - $cell_content['onclick_type'] = 'confirm'; - $cell_content['onclick'] = is_array($matches) && $matches[1] ? $matches[1] : $cell_content['onclick']; - $widget['onclick'] = "return confirm('".$cell_content['onclick']."');"; - } - elseif ($cell_content['onclick_type'] == 'popup' && $cell_content['onclick']) - { - // eg: menuaction=calendar.uiforms.freetimesearch&values2url('start,end,participants'),ft_search,700,500 - if (($values2url = preg_match('/&values2url\((\'[^\']+\')\)/',$cell_content['onclick'],$matches))) - { - $values2url = $matches[1]; - $onclick = str_replace('&values2url('.$values2url.')','',$cell_content['onclick']); - } - list($get,$target,$width,$height) = explode(',',$values2url ? $onclick : $cell_content['onclick']); - if (!$target) $target = '_blank'; - if (!$width) $width = 600; - if (!$height) $height = 450; - $widget['onclick'] = "window.open(egw::link('/index.php','$get')".($values2url ? "+values2url(this.form,$values2url)" : ''). - ",'$target','dependent=yes,width=$width,height=$height,scrollbars=yes,status=yes'); return false;"; - } - elseif ($cell_content['onclick']) - { - $wiget['onclick'] = $cell_content['onclick']; - $cell_content['onclick_type'] = 'custom'; - } - else - { - $cell_content['onclick_type'] = ''; - } - unset($widget['onclick_type']); - } - //echo "

editor::fix_set_onclick(,,widget2content=".(int)$widget2content.") widget="; _debug_array($widget); echo "content="; _debug_array($cell_content); - } - - /** - * converts onchange selectbox and onchange text to one javascript call - * - * @param array &$widget reference into the widget-tree - * @param array &$cell_content cell array in content - * @param boolean $widget2content=true copy from widget to content or other direction - */ - function fix_set_onchange(&$widget,&$cell_content,$widget2content=true) - { - if ($widget2content) - { - if (!$widget['onchange']) - { - $cell_content['onchange_type'] = $cell_content['onchange'] = ''; - } - elseif ($widget['onchange'] == 1 || $widget['onchange'] == 'this.form.submit();') - { - $cell_content['onchange'] = ''; - $cell_content['onchange_type'] = 'submit'; - } - else - { - $cell_content['onchange_type'] = 'custom'; - } - } - else // content --> widget - { - if ($cell_content['onchange_type'] == 'submit' || $cell_content['onchange'] == 'this.form.submit();') - { - $widget['onchange'] = 1; - } - elseif(!$cell_content['onchange']) - { - $widget['onchange'] = 0; - } - unset($widget['onchange_type']); - } - //echo "

editor::fix_set_onchange(,,widget2content=".(int)$widget2content.") widget="; _debug_array($widget); echo "content="; _debug_array($cell_content); - } - - /** - * edit dialog for a widget - * - * @param array $content the submitted content of the etemplate::exec function, default null - * @param string $msg msg to display, default '' - */ - function widget($content=null,$msg='') - { - if (is_array($content)) - { - $path = $content['goto'] ? $content['goto'] : ($content['goto2'] ? $content['goto2'] : $content['path']); - $Ok = $this->etemplate->read($content['name'],$content['template'],$content['lang'],0,$content['goto'] || $content['goto2'] ? $content['version'] : $content['old_version']); - - // build size from options array, if applicable - if (is_array($content['cell']['options'])) - { - $size = ''; - for ($n = max(array_keys($content['cell']['options'])); $n >= 0; --$n) - { - if (strlen($content['cell']['options'][$n]) || strlen($size)) - { - $size = $content['cell']['options'][$n].(strlen($size) ? ','.$size : ''); - } - } - $content['cell']['size'] = $size; - } - } - else - { - //echo "

".($_GET['path']).":

\n"; - list($name,$version,$path) = explode(':',$_GET['path'],3); // :: - $Ok = $this->etemplate->read(array( - 'name' => $name, - 'version' => $version, - )); - } - if (!$Ok && !$content['cancel']) - { - $msg .= lang('Error: Template not found !!!'); - } - $path_parts = explode('/',$path); - $child_id = array_pop($path_parts); - $parent_path = implode('/',$path_parts); - //echo "

path='$path': child_id='$child_id', parent_path='$parent_path'

\n"; - $parent =& $this->etemplate->get_widget_by_path($parent_path); - - if (is_array($content)) - { - foreach(array('save','apply','cancel','goto','goto2','edit_menu','box_menu','row_menu','column_menu') as $n => $name) - { - if (($action = $content[$name] ? ($n < 5 ? $name : $content[$name]) : false)) break; - $name = ''; - } - unset($content[$name]); - - //echo "

name='$name', parent-type='$parent[type]', action='$action'

\n"; - if (($name == 'row_menu' || $name == 'column_menu') && $parent['type'] != 'grid' || - $name == 'box_menu' && $parent['type'] == 'grid') - { - $msg .= lang("parent is a '%1' !!!",lang($parent['type'] ? $parent['type'] : 'template')); - $action = false; - } - switch($name) - { - case 'edit_menu': - $msg .= $this->edit_actions($action,$parent,$content,$child_id); - break; - - case 'box_menu': - $msg .= $this->box_actions($action,$parent,$content,$child_id,$parent_path); - break; - - case 'row_menu': - $msg .= $this->row_actions($action,$parent,$child_id); - break; - - case 'column_menu': - $msg .= $this->column_actions($action,$parent,$child_id); - break; - - case '': // reload, eg. by changing the type - $widget = $content['cell']; - break; - - default: - // all menu's are (only) working on the parent, referencing widget is unnecessary - // and gives unexpected results, if parent is changed (eg. content gets copied) - $widget =& $this->etemplate->get_widget_by_path($path); - break; - } - switch ($action) - { - case 'goto': - case 'goto2': - $content['cell'] = $widget; - $this->fix_set_onclick($widget,$content['cell'],true); - $this->fix_set_onchange($widget,$content['cell'],true); - break; - - case '': - case 'save': case 'apply': - // initialise the children arrays if type is changed to a widget with children - //echo "

$content[path]: $widget[type] --> ".$content['cell']['type']."

\n"; - if (isset($this->etemplate->widgets_with_children[$content['cell']['type']])) - { - $this->change_widget_type($content['cell'],$widget); - } - if (!$action) break; - // save+apply only - $widget = $content['cell']; - if ($content['cell']['onclick_type'] || $content['cell']['onclick']) - { - $this->fix_set_onclick($widget,$content['cell'],false); - } - if ($content['cell']['onchange_type'] || $content['cell']['onchange']) - { - $this->fix_set_onchange($widget,$content['cell'],false); - } - // row- and column-attr for a grid - if ($parent['type'] == 'grid' && preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) - { - list(,$row,$col) = $matches; - $parent['data'][0]['h'.$row] = $content['grid_row']['height']. - ($content['grid_row']['disabled']?','.$content['grid_row']['disabled']:''); - $parent['data'][0]['c'.$row] = $content['grid_row']['class']. - ($content['grid_row']['valign']?','.$content['grid_row']['valign']:''); - $parent['data'][0][$col] = $content['grid_column']['width']. - ($content['grid_column']['disabled']?','.$content['grid_column']['disabled']:''); - } - // fall-through - case 'save-no-merge': - case 'apply-no-merge': - //$this->etemplate->echo_tmpl(); - $ok = $this->etemplate->save($content); - $msg .= $ok ? lang('Template saved') : lang('Error: while saving !!!'); - - // if necessary fix the version of our opener - if ($content['opener']['name'] == $content['name'] && - $content['opener']['template'] == $content['template'] && - $content['opener']['group'] == $content['group'] && - $content['opener']['lang'] == $content['lang']) - { - $content['opener']['version'] = $content['version']; - } - $js = "opener.location.href='".$GLOBALS['egw']->link('/index.php',array( - 'menuaction' => 'etemplate.editor.edit', - )+$content['opener'])."';"; - if ($action == 'apply' || $action == 'apply-no-merge') break; - // fall through - case 'cancel': - $js .= 'window.close();'; - echo "\n"; - $GLOBALS['egw']->common->egw_exit(); - break; - } - if ($js) - { - $content['java_script'] = ""; - } - } - else - { - $widget =& $this->etemplate->get_widget_by_path($path); - - $content = $this->etemplate->as_array(-1); - $content['cell'] = $widget; - $this->fix_set_onclick($widget,$content['cell'],true); - $this->fix_set_onchange($widget,$content['cell'],true); - - foreach($this->etemplate->db_key_cols as $var) - { - if (isset($_GET[$var])) - { - $content['opener'][$var] = $_GET[$var]; - } - } - } - unset($content['cell']['obj']); // just in case it contains a template-object - - if ($parent['type'] == 'grid' && preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) - { - list(,$row,$col) = $matches; - - $grid_row =& $content['grid_row']; - list($grid_row['height'],$grid_row['disabled']) = explode(',',$parent['data'][0]['h'.$row]); - list($grid_row['class'],$grid_row['valign']) = explode(',',$parent['data'][0]['c'.$row]); - - $grid_column =& $content['grid_column']; - list($grid_column['width'],$grid_column['disabled']) = explode(',',$parent['data'][0][$col]); - //echo "

grid_row($row)=".print_r($grid_row,true).", grid_column($col)=".print_r($grid_column,true)."

\n"; - } - else - { - unset($content['grid_row']); - unset($content['grid_column']); - } - $content['path'] = ($parent_path!='/'?$parent_path:'').'/'.$child_id; - $content['msg'] = $msg; - $content['goto'] = $this->path_components($content['path']); - $content['goto2'] = $this->parent_navigation($parent,$parent_path,$child_id,$widget); - - $content['cell']['options'] = explode(',',$content['cell']['size']); - - $editor =& new etemplate('etemplate.editor.widget'); - $type_tmpl =& new etemplate; - - list($ext_type) = explode('-',$widget['type']); - if ($type_tmpl->read('etemplate.editor.widget.'.$widget['type']) || - $type_tmpl->read('etemplate.editor.widget.'.$ext_type)) - { - $editor->set_cell_attribute('etemplate.editor.widget.generic','obj',$type_tmpl); - } - if ($parent['type'] == 'grid') - { - $editor->disable_cells('box_menu'); - } - else - { - $editor->disable_cells('row_menu'); - $editor->disable_cells('column_menu'); - } - $preserv = $this->etemplate->as_array()+array( - 'path' => $content['path'], - 'old_version' => $this->etemplate->version, - 'opener' => $content['opener'], - 'cell' => $content['cell'], - 'goto' => $content['goto'], - ); - unset($preserv['cell']['options']); // otherwise we never know if content is returned via options array or size - - $GLOBALS['egw_info']['flags']['java_script'] = "\n"; - $GLOBALS['egw_info']['flags']['app_header'] = lang('Editable Templates - Editor'); - $editor->exec('etemplate.editor.widget',$content,array( - 'type' => array_merge($this->etemplate->types,$this->extensions), - 'align' => &$this->aligns, - 'valign' => &$this->valigns, - 'edit_menu' => &$this->edit_menu, - 'box_menu' => &$this->box_menu, - 'row_menu' => &$this->row_menu, - 'column_menu'=> &$this->column_menu, - 'onclick_type'=>&$this->onclick_types, - 'onchange_type'=>&$this->onchange_types, - 'options[6]' => &$this->overflows, - ),'',$preserv,2); - } - - /** - * edit dialog for the styles of a templat or app - * - * @param array $content the submitted content of the etemplate::exec function, default null - * @param string $msg msg to display, default '' - */ - function styles($content=null,$msg='') - { - if (!is_array($content)) - { - foreach($this->etemplate->db_key_cols as $var) - { - if (isset($_GET[$var])) $content[$var] = $_GET[$var]; - } - } - //_debug_array($content); - // security check for content[from] - if ($content['from'] && !preg_match('/^[A-Za-z0-9_-]+\/templates\/[A-Za-z0-9_-]+\/app.css$/',$content['from'])) - { - $content['from'] = ''; // someone tried to trick us reading a file we are not suppost to read - } - if (!$this->etemplate->read($content)) - { - $msg .= lang('Error: Template not found !!!'); - } - if ($content['save'] || $content['apply']) - { - if ($content['from']) - { - $path = EGW_SERVER_ROOT.'/'.$content['from']; - if (is_writable(dirname($path)) && file_exists($path)) - { - rename($path,str_replace('.css','.old.css',$path)); - } - if (file_exists($path) && !is_writable(dirname($path))) - { - $msg .= lang("Error: webserver is not allowed to write into '%1' !!!",dirname($path)); - } - else - { - $fp = fopen($path,'w'); - if (!$fp || !fwrite($fp,$content['styles'])) - { - $msg .= lang('Error: while saving !!!'); + $parent[$num=1] = etemplate::empty_cell(); } else { - $msg .= lang("File writen",$path); + for($n = $child_id; $n < $num; ++$n) + { + $parent[$n] = $parent[1+$n]; + } + unset($parent[$num--]); } - @fclose($fp); + $parent['size'] = $num . ($options ? ','.$options : ''); } - } - else // the templates own embeded styles; - { - $this->etemplate->style = $content['styles']; - $ok = $this->etemplate->save(); - $msg = $ok ? lang('Template saved') : lang('Error: while saving !!!'); + else // template itself + { + if (count($this->etemplate->children) <= 1) // cant delete last child + { + $this->etemplate->children[0] = etemplate::empty_cell(); + } + else + { + unset($parent[$child_id]); + $this->etemplate->children = array_values($this->etemplate->children); + } + } + $action = 'save-no-merge'; } - $js = "opener.location.href='".$GLOBALS['egw']->link('/index.php',array( - 'menuaction' => 'etemplate.editor.edit', - )+$this->etemplate->as_array(-1))."';"; - } - if ($content['save'] || $content['cancel']) - { - $js .= 'window.close();'; - echo "\n"; - $GLOBALS['egw']->common->egw_exit(); - } - $content = array( - 'from' => $content['from'], - 'java_script' => $js ? '' : '', - 'msg' => $msg - ); - $tmpl =& new etemplate('etemplate.editor.styles'); + else + { + $content['cell'] = etemplate::empty_cell(); + return lang('cant delete a single widget from a grid !!!'); + } + break; + } + return ''; + } + /** + * functions of the box-menu: insert-before, -behind und swap + * + * @internal + * @param string &$action row_delete, row_insert_above, row_insert_below, row_swap, row_prefs + * @param array &$parent referece to the parent + * @param array &$content reference to the content-array + * @param string &$child_id id of a cell, may change to the next cell if inserting behind + * @param string $parent_path path of parent + * @return string msg to display + */ + function box_actions(&$action,&$parent,&$content,&$child_id,$parent_path) + { + switch ($action) + { + case 'box_insert_before': + case 'box_insert_behind': + $n = $child_id + (int)($action == 'box_insert_behind'); + if (!$parent['type']) // template + { + $num = count($parent)-1; // 0..count()-1 + } + else // boxes + { + list($num,$options) = explode(',',$parent['size'],2); + } + for($i = $num; $i >= $n; --$i) + { + $parent[1+$i] = $parent[$i]; + } + $parent[$n] = $content['cell'] = etemplate::empty_cell(); + $child_id = $n; + if ($parent['type']) $parent['size'] = (1+$num) . ($options ? ','.$options : ''); + break; + + case 'box_swap_next': + if (!$parent['type']) // template + { + $num = count($parent)-1; // 0..count()-1 + } + else // boxes + { + list($num) = explode(',',$parent['size'],2); + } + if ($child_id == $num) // if on last cell, swap with the one before + { + $this->swap($parent[$child_id],$parent[$child_id-1]); + --$child_id; + } + else + { + $this->swap($parent[$child_id],$parent[$child_id+1]); + ++$child_id; + } + break; + } + $action = 'apply-no-merge'; + + return ''; + } + + /** + * functions of the row-menu: insert, deleting & swaping of rows + * + * @internal + * @param string &$action row_delete, row_insert_above, row_insert_below, row_swap_next, row_prefs + * @param array &$grid grid + * @param string $child_id id of a cell + * @return string msg to display + */ + function row_actions(&$action,&$grid,$child_id) + { + $data =& $grid['data']; + $rows =& $grid['rows']; + $cols =& $grid['cols']; + $opts =& $data[0]; + + if (preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) list(,$r,$c) = $matches; + + if (!$c || !$r || $r > $rows) return "wrong child_id='$child_id' => r='$r', c='$c'"; + + switch($action) + { + case 'row_swap_next': + if ($r > $rows-1) + { + if ($r != $rows) return lang('no row to swap with !!!'); + --$r; // in last row swap with row above + } + $this->swap($data[$r],$data[1+$r]); + $this->swap($opts['c'.$r],$opts['c'.(1+$r)]); + $this->swap($opts['h'.$r],$opts['h'.(1+$r)]); + break; + + case 'row_delete': + if ($rows <= 1) // one row only => delete whole grid + { + return lang('cant delete the only row in a grid !!!'); + // todo: delete whole grid instead + } + for($i = $r; $i < $rows; ++$i) + { + $opts['c'.$i] = $opts['c'.(1+$i)]; + $opts['h'.$i] = $opts['h'.(1+$i)]; + $data[$i] = $data[1+$i]; + } + unset($opts['c'.$rows]); + unset($opts['h'.$rows]); + unset($data[$rows--]); + break; + + case 'row_insert_above': + --$r; + // fall-through + case 'row_insert_below': + //echo "row_insert_below($r) rows=$rows, cols=$cols"; _debug_array($grid); + // move height + class options of rows + for($i = $rows; $i > $r; --$i) + { + echo ($i+1)."=$i
\n"; + $data[1+$i] = $data[$i]; + $opts['c'.(1+$i)] = $opts['c'.$i]; + $opts['h'.(1+$i)] = $opts['h'.$i]; + } + for($i = 0; $i < $cols; ++$i) + { + echo (1+$r).":$i=".etemplate::num2chrs($i)."=empty_cell()
\n"; + $data[1+$r][etemplate::num2chrs($i)] = etemplate::empty_cell(); + } + $opts['c'.(1+$r)] = $opts['h'.(1+$r)] = ''; + ++$rows; + //_debug_array($grid); return ''; + break; + } + $action = 'save-no-merge'; + + return ''; + } + + /** + * functions of the column-menu: insert, deleting & swaping of columns + * + * @internal + * @param string &$action column_delete, column_insert_before, column_insert_behind, column_swap_next, column_prefs + * @param array &$grid grid + * @param string $child_id id of a cell + * @return string msg to display + */ + function column_actions(&$action,&$grid,$child_id) + { + $data =& $grid['data']; + $rows =& $grid['rows']; + $cols =& $grid['cols']; + $opts =& $data[0]; + + if (preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) list(,$r,$c) = $matches; + // find the column-number (base 0) for $c (A, B, C, ...) + for($col = 0; etemplate::num2chrs($col) != $c && $col < 100; ++$col) ; + + if (!$c || !$r || $r > $rows || $col >= $cols) return "wrong child_id='$child_id' => r='$r', c='$c', col=$col"; + + switch($action) + { + case 'column_swap_next': + if ($col >= $cols-1) + { + if ($col != $cols-1) return lang('no column to swap with !!!'); + $c = etemplate::num2chrs(--$col); // in last column swap with the one before + } + $c_next = etemplate::num2chrs(1+$col); + for($row = 1; $row <= $rows; ++$row) + { + $this->swap($data[$row][$c],$data[$row][$c_next]); + } + $this->swap($opts[$c],$opts[$c_next]); + //_debug_array($grid); return ''; + break; + + case 'column_insert_behind': + ++$col; + case 'column_insert_before': + //echo "

column_insert_before: col=$col

\n"; + // $col is where the new column data goes + for ($row = 1; $row <= $rows; ++$row) + { + for ($i = $cols; $i > $col; --$i) + { + $data[$row][etemplate::num2chrs($i)] = $data[$row][etemplate::num2chrs($i-1)]; + } + $data[$row][etemplate::num2chrs($col)] = etemplate::empty_cell(); + } + for ($i = $cols; $i > $col; --$i) + { + $opts[etemplate::num2chrs($i)] = $opts[etemplate::num2chrs($i-1)]; + } + unset($opts[etemplate::num2chrs($col)]); + ++$cols; + //_debug_array($grid); return ''; + break; + + case 'column_delete': + if ($cols <= 1) + { + return lang('cant delete the only column of a grid !!!'); + // todo: delete whole grid instead + } + for ($row = 1; $row <= $rows; ++$row) + { + for ($i = $col; $i < $cols-1; ++$i) + { + $data[$row][etemplate::num2chrs($i)] = $data[$row][etemplate::num2chrs($i+1)]; + } + unset($data[$row][etemplate::num2chrs($cols-1)]); + } + for ($i = $col; $i < $cols-1; ++$i) + { + $opts[etemplate::num2chrs($i)] = $opts[etemplate::num2chrs($i+1)]; + } + unset($opts[etemplate::num2chrs(--$cols)]); + break; + } + $action = 'save-no-merge'; + + return ''; + } + + /** + * converts onclick selectbox and onclick text to one javascript call + * + * @param array &$widget reference into the widget-tree + * @param array &$cell_content cell array in content + * @param boolean $widget2content=true copy from widget to content or other direction + */ + function fix_set_onclick(&$widget,&$cell_content,$widget2content=true) + { + if ($widget2content) + { + if (preg_match('/^return confirm\(["\']{1}?(.*)["\']{1}\);?$/',$widget['onclick'],$matches)) + { + $cell_content['onclick'] = $matches[1]; + $cell_content['onclick_type'] = 'confirm'; + } + elseif (preg_match('/^window.open\(egw::link\(\'\/index.php\',\'([^\']+)\'\)(\+values2url\(.*\))?,\'([^\']+)\',\'dependent=yes,width=([0-9]+),height=([0-9]+),scrollbars=yes,status=yes\'\); return false;$/',$widget['onclick'],$matches)) + { + $cell_content['onclick'] = $matches[1].($matches[2] ? str_replace('+values2url(this.form,','&values2url(',$matches[2]) : ''); + if ($matches[3] != '_blank') + { + $cell_content['onclick'] .= ','.$matches[3]; + } + if ($matches[4] != '600') + { + $cell_content['onclick'] .= ($matches[3]=='_blank' ? ',':'').','.$matches[4]; + } + if ($matches[5] != '450') + { + $cell_content['onclick'] .= ($matches[4]=='600' ? ','.($matches[3]=='_blank' ? ',':'') : ''). + ','.$matches[5]; + } + $cell_content['onclick_type'] = 'popup'; + } + else + { + $cell_content['onclick_type'] = !$widget['onclick'] ? '' : 'custom'; + } + } + else // content --> widget + { + if (preg_match('/^return confirm\(["\']{1}?(.*)["\']{1}\);?$/',$cell_content['onclick'],$matches) || + $cell_content['onclick_type'] == 'confirm' && $cell_content['onclick']) + { + $cell_content['onclick_type'] = 'confirm'; + $cell_content['onclick'] = is_array($matches) && $matches[1] ? $matches[1] : $cell_content['onclick']; + $widget['onclick'] = "return confirm('".$cell_content['onclick']."');"; + } + elseif ($cell_content['onclick_type'] == 'popup' && $cell_content['onclick']) + { + // eg: menuaction=calendar.uiforms.freetimesearch&values2url('start,end,participants'),ft_search,700,500 + if (($values2url = preg_match('/&values2url\((\'[^\']+\')\)/',$cell_content['onclick'],$matches))) + { + $values2url = $matches[1]; + $onclick = str_replace('&values2url('.$values2url.')','',$cell_content['onclick']); + } + list($get,$target,$width,$height) = explode(',',$values2url ? $onclick : $cell_content['onclick']); + if (!$target) $target = '_blank'; + if (!$width) $width = 600; + if (!$height) $height = 450; + $widget['onclick'] = "window.open(egw::link('/index.php','$get')".($values2url ? "+values2url(this.form,$values2url)" : ''). + ",'$target','dependent=yes,width=$width,height=$height,scrollbars=yes,status=yes'); return false;"; + } + elseif ($cell_content['onclick']) + { + $wiget['onclick'] = $cell_content['onclick']; + $cell_content['onclick_type'] = 'custom'; + } + else + { + $cell_content['onclick_type'] = ''; + } + unset($widget['onclick_type']); + } + //echo "

editor::fix_set_onclick(,,widget2content=".(int)$widget2content.") widget="; _debug_array($widget); echo "content="; _debug_array($cell_content); + } + + /** + * converts onchange selectbox and onchange text to one javascript call + * + * @param array &$widget reference into the widget-tree + * @param array &$cell_content cell array in content + * @param boolean $widget2content=true copy from widget to content or other direction + */ + function fix_set_onchange(&$widget,&$cell_content,$widget2content=true) + { + if ($widget2content) + { + if (!$widget['onchange']) + { + $cell_content['onchange_type'] = $cell_content['onchange'] = ''; + } + elseif ($widget['onchange'] == 1 || $widget['onchange'] == 'this.form.submit();') + { + $cell_content['onchange'] = ''; + $cell_content['onchange_type'] = 'submit'; + } + else + { + $cell_content['onchange_type'] = 'custom'; + } + } + else // content --> widget + { + if ($cell_content['onchange_type'] == 'submit' || $cell_content['onchange'] == 'this.form.submit();') + { + $widget['onchange'] = 1; + } + elseif(!$cell_content['onchange']) + { + $widget['onchange'] = 0; + } + unset($widget['onchange_type']); + } + //echo "

editor::fix_set_onchange(,,widget2content=".(int)$widget2content.") widget="; _debug_array($widget); echo "content="; _debug_array($cell_content); + } + + /** + * edit dialog for a widget + * + * @param array $content the submitted content of the etemplate::exec function, default null + * @param string $msg msg to display, default '' + */ + function widget($content=null,$msg='') + { + if (is_array($content)) + { + $path = $content['goto'] ? $content['goto'] : ($content['goto2'] ? $content['goto2'] : $content['path']); + $Ok = $this->etemplate->read($content['name'],$content['template'],$content['lang'],0,$content['goto'] || $content['goto2'] ? $content['version'] : $content['old_version']); + + // build size from options array, if applicable + if (is_array($content['cell']['options'])) + { + $size = ''; + for ($n = max(array_keys($content['cell']['options'])); $n >= 0; --$n) + { + if (strlen($content['cell']['options'][$n]) || strlen($size)) + { + $size = $content['cell']['options'][$n].(strlen($size) ? ','.$size : ''); + } + } + $content['cell']['size'] = $size; + } + } + else + { + //echo "

".($_GET['path']).":

\n"; + list($name,$version,$path) = explode(':',$_GET['path'],3); // :: + $Ok = $this->etemplate->read(array( + 'name' => $name, + 'version' => $version, + )); + } + if (!$Ok && !$content['cancel']) + { + $msg .= lang('Error: Template not found !!!'); + } + $path_parts = explode('/',$path); + $child_id = array_pop($path_parts); + $parent_path = implode('/',$path_parts); + //echo "

path='$path': child_id='$child_id', parent_path='$parent_path'

\n"; + $parent =& $this->etemplate->get_widget_by_path($parent_path); + + if (is_array($content)) + { + foreach(array('save','apply','cancel','goto','goto2','edit_menu','box_menu','row_menu','column_menu') as $n => $name) + { + if (($action = $content[$name] ? ($n < 5 ? $name : $content[$name]) : false)) break; + $name = ''; + } + unset($content[$name]); + + //echo "

name='$name', parent-type='$parent[type]', action='$action'

\n"; + if (($name == 'row_menu' || $name == 'column_menu') && $parent['type'] != 'grid' || + $name == 'box_menu' && $parent['type'] == 'grid') + { + $msg .= lang("parent is a '%1' !!!",lang($parent['type'] ? $parent['type'] : 'template')); + $action = false; + } + switch($name) + { + case 'edit_menu': + $msg .= $this->edit_actions($action,$parent,$content,$child_id); + break; + + case 'box_menu': + $msg .= $this->box_actions($action,$parent,$content,$child_id,$parent_path); + break; + + case 'row_menu': + $msg .= $this->row_actions($action,$parent,$child_id); + break; + + case 'column_menu': + $msg .= $this->column_actions($action,$parent,$child_id); + break; + + case '': // reload, eg. by changing the type + $widget = $content['cell']; + break; + + default: + // all menu's are (only) working on the parent, referencing widget is unnecessary + // and gives unexpected results, if parent is changed (eg. content gets copied) + $widget =& $this->etemplate->get_widget_by_path($path); + break; + } + switch ($action) + { + case 'goto': + case 'goto2': + $content['cell'] = $widget; + $this->fix_set_onclick($widget,$content['cell'],true); + $this->fix_set_onchange($widget,$content['cell'],true); + break; + + case '': + case 'save': case 'apply': + // initialise the children arrays if type is changed to a widget with children + //echo "

$content[path]: $widget[type] --> ".$content['cell']['type']."

\n"; + if (isset(etemplate::$widgets_with_children[$content['cell']['type']])) + { + $this->change_widget_type($content['cell'],$widget); + } + if (!$action) break; + // save+apply only + $widget = $content['cell']; + if ($content['cell']['onclick_type'] || $content['cell']['onclick']) + { + $this->fix_set_onclick($widget,$content['cell'],false); + } + if ($content['cell']['onchange_type'] || $content['cell']['onchange']) + { + $this->fix_set_onchange($widget,$content['cell'],false); + } + // row- and column-attr for a grid + if ($parent['type'] == 'grid' && preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) + { + list(,$row,$col) = $matches; + $parent['data'][0]['h'.$row] = $content['grid_row']['height']. + ($content['grid_row']['disabled']?','.$content['grid_row']['disabled']:''); + $parent['data'][0]['c'.$row] = $content['grid_row']['class']. + ($content['grid_row']['valign']?','.$content['grid_row']['valign']:''); + $parent['data'][0][$col] = $content['grid_column']['width']. + ($content['grid_column']['disabled']?','.$content['grid_column']['disabled']:''); + } + // fall-through + case 'save-no-merge': + case 'apply-no-merge': + //$this->etemplate->echo_tmpl(); + $ok = $this->etemplate->save($content); + $msg .= $ok ? lang('Template saved') : lang('Error: while saving !!!'); + + // if necessary fix the version of our opener + if ($content['opener']['name'] == $content['name'] && + $content['opener']['template'] == $content['template'] && + $content['opener']['group'] == $content['group'] && + $content['opener']['lang'] == $content['lang']) + { + $content['opener']['version'] = $content['version']; + } + $js = "opener.location.href='".$GLOBALS['egw']->link('/index.php',array( + 'menuaction' => 'etemplate.editor.edit', + )+$content['opener'])."';"; + if ($action == 'apply' || $action == 'apply-no-merge') break; + // fall through + case 'cancel': + $js .= 'window.close();'; + echo "\n"; + $GLOBALS['egw']->common->egw_exit(); + break; + } + if ($js) + { + $content['java_script'] = ""; + } + } + else + { + $widget =& $this->etemplate->get_widget_by_path($path); + + $content = $this->etemplate->as_array(-1); + $content['cell'] = $widget; + $this->fix_set_onclick($widget,$content['cell'],true); + $this->fix_set_onchange($widget,$content['cell'],true); + + foreach(etemplate::$db_key_cols as $var) + { + if (isset($_GET[$var])) + { + $content['opener'][$var] = $_GET[$var]; + } + } + } + unset($content['cell']['obj']); // just in case it contains a template-object + + if ($parent['type'] == 'grid' && preg_match('/^([0-9]+)([A-Z]+)$/',$child_id,$matches)) + { + list(,$row,$col) = $matches; + + $grid_row =& $content['grid_row']; + list($grid_row['height'],$grid_row['disabled']) = explode(',',$parent['data'][0]['h'.$row]); + list($grid_row['class'],$grid_row['valign']) = explode(',',$parent['data'][0]['c'.$row]); + + $grid_column =& $content['grid_column']; + list($grid_column['width'],$grid_column['disabled']) = explode(',',$parent['data'][0][$col]); + //echo "

grid_row($row)=".print_r($grid_row,true).", grid_column($col)=".print_r($grid_column,true)."

\n"; + } + else + { + unset($content['grid_row']); + unset($content['grid_column']); + } + $content['path'] = ($parent_path!='/'?$parent_path:'').'/'.$child_id; + $content['msg'] = $msg; + $content['goto'] = $this->path_components($content['path']); + $content['goto2'] = $this->parent_navigation($parent,$parent_path,$child_id,$widget); + + $content['cell']['options'] = explode(',',$content['cell']['size']); + + $editor =& new etemplate('etemplate.editor.widget'); + $type_tmpl =& new etemplate; + + list($ext_type) = explode('-',$widget['type']); + if ($type_tmpl->read('etemplate.editor.widget.'.$widget['type']) || + $type_tmpl->read('etemplate.editor.widget.'.$ext_type)) + { + $editor->set_cell_attribute('etemplate.editor.widget.generic','obj',$type_tmpl); + } + if ($parent['type'] == 'grid') + { + $editor->disable_cells('box_menu'); + } + else + { + $editor->disable_cells('row_menu'); + $editor->disable_cells('column_menu'); + } + $preserv = $this->etemplate->as_array()+array( + 'path' => $content['path'], + 'old_version' => $this->etemplate->version, + 'opener' => $content['opener'], + 'cell' => $content['cell'], + 'goto' => $content['goto'], + ); + unset($preserv['cell']['options']); // otherwise we never know if content is returned via options array or size + + $GLOBALS['egw_info']['flags']['java_script'] = "\n"; + $GLOBALS['egw_info']['flags']['app_header'] = lang('Editable Templates - Editor'); + $editor->exec('etemplate.editor.widget',$content,array( + 'type' => array_merge($this->etemplate->types,$this->extensions), + 'align' => &$this->aligns, + 'valign' => &$this->valigns, + 'edit_menu' => &$this->edit_menu, + 'box_menu' => &$this->box_menu, + 'row_menu' => &$this->row_menu, + 'column_menu'=> &$this->column_menu, + 'onclick_type'=>&$this->onclick_types, + 'onchange_type'=>&$this->onchange_types, + 'options[6]' => &$this->overflows, + ),'',$preserv,2); + } + + /** + * edit dialog for the styles of a templat or app + * + * @param array $content the submitted content of the etemplate::exec function, default null + * @param string $msg msg to display, default '' + */ + function styles($content=null,$msg='') + { + if (!is_array($content)) + { + foreach(etemplate::$db_key_cols as $var) + { + if (isset($_GET[$var])) $content[$var] = $_GET[$var]; + } + } + //_debug_array($content); + // security check for content[from] + if ($content['from'] && !preg_match('/^[A-Za-z0-9_-]+\/templates\/[A-Za-z0-9_-]+\/app.css$/',$content['from'])) + { + $content['from'] = ''; // someone tried to trick us reading a file we are not suppost to read + } + if (!$this->etemplate->read($content)) + { + $msg .= lang('Error: Template not found !!!'); + } + if ($content['save'] || $content['apply']) + { if ($content['from']) { $path = EGW_SERVER_ROOT.'/'.$content['from']; - $content['styles'] = file_exists($path) && is_readable($path) ? implode('',file($path)) : ''; - if (!is_writable(dirname($path)) && (!file_exists($path) || !is_writable($path))) - { - $tmpl->set_cell_attribute('styles','readonly',true); - } - } - else - { - $content['styles'] = $this->etemplate->style; - } - // generate list of style-sources - $keys = $this->etemplate->as_array(-1); unset($keys['group']); - $sources[''] = lang('eTemplate').': '.implode(':',$keys); - list($app) = explode('.',$this->etemplate->name); - $app_templates = @opendir(EGW_SERVER_ROOT.'/'.$app.'/templates'); - while (($template = @readdir($app_templates)) !== false) - { - $dir = EGW_SERVER_ROOT.'/'.$app.'/templates/'.$template; - if ($template[0] == '.' || $template == 'CVS' || !is_dir($dir.'/images')) continue; // not a template-dir - $exists = file_exists($dir.'/app.css'); - $writable = is_writable($dir) || $exists && is_writable($dir.'/app.css'); - if (!$exists && !$writable) continue; // nothing to show - $rel_path = $app.'/templates/'.$template.'/app.css'; - $sources[$rel_path] = lang('file').': '.$rel_path.($exists && !$writable ? ' ('.lang('readonly').')' : ''); - } - $GLOBALS['egw_info']['flags']['java_script'] = "\n"; - $GLOBALS['egw_info']['flags']['app_header'] = lang('etemplate').' - '.lang('CSS-styles'); - $tmpl->exec('etemplate.editor.styles',$content,array('from'=>$sources),'',$keys,2); - } - - /** - * search the inc-dirs of etemplate and the app whichs template is edited for extensions / custom widgets - * - * extensions are class-files in $app/inc/class.${name}_widget.inc.php - * the extensions found will be saved in a class-var and in the session - * - * @param string $app='etemplate' app to scan - * @return string comma delimited list of new found extensions - */ - function scan_for_extensions($app='etemplate') - { - if (!is_array($this->extensions)) $this->extensions = array(); - - if (isset($this->extensions['**loaded**'][$app])) return ''; // already loaded - - $labels = array(); - $dir = @opendir(EGW_SERVER_ROOT.'/'.$app.'/inc'); - while ($dir && ($file = readdir($dir))) - { - if (ereg('class\\.([a-zA-Z0-9_]*)_widget.inc.php',$file,$regs) && - ($regs[1] != 'xslt' || $this->etemplate->xslt) && - ($ext = $this->etemplate->loadExtension($regs[1].'.'.$app,$this->etemplate))) + if (is_writable(dirname($path)) && file_exists($path)) { - if (is_array($ext)) + rename($path,str_replace('.css','.old.css',$path)); + } + if (file_exists($path) && !is_writable(dirname($path))) + { + $msg .= lang("Error: webserver is not allowed to write into '%1' !!!",dirname($path)); + } + else + { + $fp = fopen($path,'w'); + if (!$fp || !fwrite($fp,$content['styles'])) { - $this->extensions += $ext; - $labels += $ext; + $msg .= lang('Error: while saving !!!'); } else { - $this->extensions[$regs[1]] = $ext; - $labels[] = $ext; + $msg .= lang("File writen",$path); } + @fclose($fp); + } + } + else // the templates own embeded styles; + { + $this->etemplate->style = $content['styles']; + $ok = $this->etemplate->save(); + $msg = $ok ? lang('Template saved') : lang('Error: while saving !!!'); + } + $js = "opener.location.href='".$GLOBALS['egw']->link('/index.php',array( + 'menuaction' => 'etemplate.editor.edit', + )+$this->etemplate->as_array(-1))."';"; + } + if ($content['save'] || $content['cancel']) + { + $js .= 'window.close();'; + echo "\n"; + $GLOBALS['egw']->common->egw_exit(); + } + $content = array( + 'from' => $content['from'], + 'java_script' => $js ? '' : '', + 'msg' => $msg + ); + $tmpl =& new etemplate('etemplate.editor.styles'); + + if ($content['from']) + { + $path = EGW_SERVER_ROOT.'/'.$content['from']; + $content['styles'] = file_exists($path) && is_readable($path) ? implode('',file($path)) : ''; + if (!is_writable(dirname($path)) && (!file_exists($path) || !is_writable($path))) + { + $tmpl->set_cell_attribute('styles','readonly',true); + } + } + else + { + $content['styles'] = $this->etemplate->style; + } + // generate list of style-sources + $keys = $this->etemplate->as_array(-1); unset($keys['group']); + $sources[''] = lang('eTemplate').': '.implode(':',$keys); + list($app) = explode('.',$this->etemplate->name); + $app_templates = @opendir(EGW_SERVER_ROOT.'/'.$app.'/templates'); + while (($template = @readdir($app_templates)) !== false) + { + $dir = EGW_SERVER_ROOT.'/'.$app.'/templates/'.$template; + if ($template[0] == '.' || $template == 'CVS' || !is_dir($dir.'/images')) continue; // not a template-dir + $exists = file_exists($dir.'/app.css'); + $writable = is_writable($dir) || $exists && is_writable($dir.'/app.css'); + if (!$exists && !$writable) continue; // nothing to show + $rel_path = $app.'/templates/'.$template.'/app.css'; + $sources[$rel_path] = lang('file').': '.$rel_path.($exists && !$writable ? ' ('.lang('readonly').')' : ''); + } + $GLOBALS['egw_info']['flags']['java_script'] = "\n"; + $GLOBALS['egw_info']['flags']['app_header'] = lang('etemplate').' - '.lang('CSS-styles'); + $tmpl->exec('etemplate.editor.styles',$content,array('from'=>$sources),'',$keys,2); + } + + /** + * search the inc-dirs of etemplate and the app whichs template is edited for extensions / custom widgets + * + * extensions are class-files in $app/inc/class.${name}_widget.inc.php + * the extensions found will be saved in a class-var and in the session + * + * @param string $app='etemplate' app to scan + * @return string comma delimited list of new found extensions + */ + function scan_for_extensions($app='etemplate') + { + if (!is_array($this->extensions)) $this->extensions = array(); + + if (isset($this->extensions['**loaded**'][$app])) return ''; // already loaded + + $labels = array(); + $dir = @opendir(EGW_SERVER_ROOT.'/'.$app.'/inc'); + while ($dir && ($file = readdir($dir))) + { + if (ereg('class\\.([a-zA-Z0-9_]*)_widget.inc.php',$file,$regs) && + ($regs[1] != 'xslt' || $this->etemplate->xslt) && + ($ext = $this->etemplate->loadExtension($regs[1].'.'.$app,$this->etemplate))) + { + if (is_array($ext)) + { + $this->extensions += $ext; + $labels += $ext; + } + else + { + $this->extensions[$regs[1]] = $ext; + $labels[] = $ext; } } - // store the information in the session, our constructor loads it from there - $GLOBALS['egw']->session->appsession('extensions','etemplate',$this->extensions); - $apps_loaded = $GLOBALS['egw']->session->appsession('apps_loaded','etemplate'); - $apps_loaded[$app] = true; - $GLOBALS['egw']->session->appsession('apps_loaded','etemplate',$apps_loaded); - //_debug_array($this->extensions); _debug_array($apps_loaded); - - return implode(', ',$labels); } + // store the information in the session, our constructor loads it from there + $GLOBALS['egw']->session->appsession('extensions','etemplate',$this->extensions); + $apps_loaded = $GLOBALS['egw']->session->appsession('apps_loaded','etemplate'); + $apps_loaded[$app] = true; + $GLOBALS['egw']->session->appsession('apps_loaded','etemplate',$apps_loaded); + //_debug_array($this->extensions); _debug_array($apps_loaded); - /** - * swap the values of $a and $b - * - * @param mixed &$a - * @param mixed &$b - */ - function swap(&$a,&$b) - { - $h = $a; - $a = $b; - $b = $h; - } - } \ No newline at end of file + return implode(', ',$labels); + } + + /** + * swap the values of $a and $b + * + * @param mixed &$a + * @param mixed &$b + */ + function swap(&$a,&$b) + { + $h = $a; + $a = $b; + $b = $h; + } +} \ No newline at end of file diff --git a/etemplate/inc/class.soetemplate.inc.php b/etemplate/inc/class.soetemplate.inc.php index 0f2c58a9e7..200b8365a8 100644 --- a/etemplate/inc/class.soetemplate.inc.php +++ b/etemplate/inc/class.soetemplate.inc.php @@ -1,36 +1,33 @@ - * @version $Id$ - */ + * eGroupWare EditableTemplates - Storage Objects + * + * @link http://www.egroupware.org + * @author Ralf Becker + * @copyright 2002-8 by RalfBecker@outdoor-training.de + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @version $Id$ + */ /** - * Storage Objects: Everything to store and retrive and eTemplate. - * - * eTemplates are stored in the db in table 'phpgw_etemplate' and gets distributed - * through the file 'etemplates.inc.php' in the setup dir of each app. That file gets - * automatically imported in the db, whenever you show a eTemplate of the app. For - * performace reasons the timestamp of the file is stored in the db, so 'new' - * eTemplates need to have a newer file. The distribution-file is generated with the - * function dump, usually by pressing a button in the editor. - * writeLangFile writes an lang-file with all Labels, incorporating an existing one. - * Beside a name eTemplates use the following keys to find the most suitable template - * for an user (in order of precedence): - * 1) User-/Group-Id (not yet implemented) - * 2) preferd languages of the user (templates for all langs have $lang='') - * 3) selected template: verdilak, ... (the default is called '' in the db, not default) - * 4) a version-number of the form, eg: '0.9.13.001' (filled up with 0 same size) - * - * @package etemplate - * @subpackage api - * @author RalfBecker-AT-outdoor-training.de - * @license GPL - */ + * Storage Objects: Everything to store and retrive and eTemplate. + * + * eTemplates are stored in the db in table 'phpgw_etemplate' and gets distributed + * through the file 'etemplates.inc.php' in the setup dir of each app. That file gets + * automatically imported in the db, whenever you show a eTemplate of the app. For + * performace reasons the timestamp of the file is stored in the db, so 'new' + * eTemplates need to have a newer file. The distribution-file is generated with the + * function dump, usually by pressing a button in the editor. + * writeLangFile writes an lang-file with all Labels, incorporating an existing one. + * Beside a name eTemplates use the following keys to find the most suitable template + * for an user (in order of precedence): + * 1) User-/Group-Id (not yet implemented) + * 2) preferd languages of the user (templates for all langs have $lang='') + * 3) selected template: verdilak, ... (the default is called '' in the db, not default) + * 4) a version-number of the form, eg: '0.9.13.001' (filled up with 0 same size) + */ class soetemplate { var $debug; // =1 show some debug-messages, = 'app.name' show messages only for eTemplate 'app.name' diff --git a/etemplate/inc/class.uietemplate.inc.php b/etemplate/inc/class.uietemplate.inc.php index b7728125df..6a0ef4c766 100644 --- a/etemplate/inc/class.uietemplate.inc.php +++ b/etemplate/inc/class.uietemplate.inc.php @@ -81,6 +81,13 @@ class etemplate extends boetemplate * @var boolean */ var $sitemgr=false; + /** + * Javascript to be called, when a widget get's double-clicked (used only by the editor) + * A '%p' gets replace with the colon ':' separated template-name, -version and path of the clicked widget. + * + * @var string + */ + var $onclick_handler; /** * constructor of etemplate class, reads an eTemplate if $name is given @@ -955,7 +962,7 @@ class etemplate extends boetemplate { if ($this->debug && (is_int($this->debug) && $this->debug >= 3 || $this->debug == $cell['type'])) { - echo "

etemplate.show_cell($this->name,name='${cell['name']}',type='${cell['type']}',cname='$cname')

\n"; + echo "

etemplate.show_cell($this->name,name='${cell['name']}',type='${cell['type']}',cname='$cname',...,'$path')

\n"; } list($span) = explode(',',$cell['span']); // evtl. overriten later for type template @@ -1742,7 +1749,7 @@ class etemplate extends boetemplate } // generate an extra div, if we have an onclick handler and NO children or it's an extension //echo "

$this->name($this->onclick_handler:$this->no_onclick:$this->onclick_proxy): $cell[type]/$cell[name]

\n"; - if ($this->onclick_handler && !isset($this->widgets_with_children[$cell['type']])) + if ($this->onclick_handler && !isset(self::$widgets_with_children[$cell['type']])) { $handler = str_replace('%p',$this->no_onclick ? $this->onclick_proxy : $this->name.':'.$this->version.':'.$path, $this->onclick_handler);