From b7d62c7b09ee9f18a590df071ce32dc6e00fdaa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cornelius=20Wei=C3=9F?= Date: Fri, 10 Nov 2006 15:30:01 +0000 Subject: [PATCH] initial import of my importexport work, export is quite clean at the moment, but import needs lots of work, all the stuff is not really for production yet, but i import it now, so that the guy from metaways can also work on it. most pending tasks: - implement conversions based on regular expressions - implement options of plugin - rework import part (mostly ui) --- importexport/doc/README.developers | 37 ++ importexport/importexport_cli.php | 159 ++++++ .../inc/.class.bodefinitions.inc.php.swp | Bin 0 -> 16384 bytes importexport/inc/class.bodefinitions.inc.php | 208 ++++++++ importexport/inc/class.definition.inc.php | 302 ++++++++++++ importexport/inc/class.export_csv.inc.php | 200 ++++++++ .../inc/class.iface_egw_record.inc.php | 123 +++++ .../inc/class.iface_export_plugin.inc.php | 92 ++++ .../inc/class.iface_export_record.inc.php | 71 +++ .../inc/class.iface_import_record.inc.php | 76 +++ importexport/inc/class.import_csv.inc.php | 299 ++++++++++++ ...ass.import_export_helper_functions.inc.php | 287 +++++++++++ ...rtexport_admin_prefs_sidebox_hooks.inc.php | 117 +++++ importexport/inc/class.uidefinitions.inc.php | 461 ++++++++++++++++++ importexport/inc/class.uiexport.inc.php | 285 +++++++++++ importexport/setup/default_records.inc.php | 53 ++ importexport/setup/etemplates.inc.php | 67 +++ importexport/setup/phpgw_de.lang | 7 + importexport/setup/phpgw_en.lang | 8 + importexport/setup/setup.inc.php | 46 ++ importexport/setup/tables_current.inc.php | 33 ++ .../templates/default/export_dialog.old.xet | 120 +++++ .../templates/default/export_dialog.xet | 169 +++++++ .../templates/default/images/export.png | Bin 0 -> 1593 bytes .../templates/default/images/fileexport.png | Bin 0 -> 626 bytes .../templates/default/images/fileimport.png | Bin 0 -> 851 bytes .../templates/default/images/import.png | Bin 0 -> 1567 bytes .../templates/default/images/importexport.jpg | Bin 0 -> 24636 bytes .../templates/default/images/importexport.xcf | Bin 0 -> 5190 bytes .../templates/default/images/navbar.png | Bin 0 -> 2319 bytes 30 files changed, 3220 insertions(+) create mode 100644 importexport/doc/README.developers create mode 100755 importexport/importexport_cli.php create mode 100644 importexport/inc/.class.bodefinitions.inc.php.swp create mode 100644 importexport/inc/class.bodefinitions.inc.php create mode 100644 importexport/inc/class.definition.inc.php create mode 100644 importexport/inc/class.export_csv.inc.php create mode 100644 importexport/inc/class.iface_egw_record.inc.php create mode 100644 importexport/inc/class.iface_export_plugin.inc.php create mode 100644 importexport/inc/class.iface_export_record.inc.php create mode 100644 importexport/inc/class.iface_import_record.inc.php create mode 100755 importexport/inc/class.import_csv.inc.php create mode 100755 importexport/inc/class.import_export_helper_functions.inc.php create mode 100644 importexport/inc/class.importexport_admin_prefs_sidebox_hooks.inc.php create mode 100644 importexport/inc/class.uidefinitions.inc.php create mode 100644 importexport/inc/class.uiexport.inc.php create mode 100644 importexport/setup/default_records.inc.php create mode 100644 importexport/setup/etemplates.inc.php create mode 100644 importexport/setup/phpgw_de.lang create mode 100644 importexport/setup/phpgw_en.lang create mode 100644 importexport/setup/setup.inc.php create mode 100644 importexport/setup/tables_current.inc.php create mode 100644 importexport/templates/default/export_dialog.old.xet create mode 100644 importexport/templates/default/export_dialog.xet create mode 100755 importexport/templates/default/images/export.png create mode 100644 importexport/templates/default/images/fileexport.png create mode 100644 importexport/templates/default/images/fileimport.png create mode 100755 importexport/templates/default/images/import.png create mode 100644 importexport/templates/default/images/importexport.jpg create mode 100755 importexport/templates/default/images/importexport.xcf create mode 100644 importexport/templates/default/images/navbar.png diff --git a/importexport/doc/README.developers b/importexport/doc/README.developers new file mode 100644 index 0000000000..34f88d32ff --- /dev/null +++ b/importexport/doc/README.developers @@ -0,0 +1,37 @@ +=importexport= + +Importexport is a framework for egroupware to handle imports and exports. +The idea behind importexport is to have a common userinterface in all apps regarding +import and export stuff AND to have common backends whitch handle the stuff. +Importexport can nothing without the plugins of the applications. + +Attending importeport framework with you application is pretty easy. + +You just need to have your plugins in files which start with +class.import_ or +class.export_ +in +EGW_INCLUDE_ROOT/YourApp/inc/ + +== definitions == +The bases of all imports and exports is the '''definition'''. +A definition defines all nessesary parameters to perform the desired action. +Moreover definitions can be stored and thus the same import / export can be redone +by loading the definition. Definitions are also reachable by the importexport +'''command line interface'''. + +== export == +Starting an export is as easy as just putting a button in your app with: +onClick="importexport.uiexport.export_dialog&appname=&have_selection=<{true|false}>" +If parameter "have_selection" if true, export askes (javascript) opener.get_selection();to retreave +a list of identifiers of selected records. + +NOTE: javascript function get_selection() is the only function which is not part of an interface yet. + +==Discussion of interfaces== +To make live easy there are several general plugins which can be found +EGW_INCLUDE_ROOT/importexport/inc/import_... +EGW_INCLUDE_ROOT/importexport/inc/export_... + + + diff --git a/importexport/importexport_cli.php b/importexport/importexport_cli.php new file mode 100755 index 0000000000..ca3e831127 --- /dev/null +++ b/importexport/importexport_cli.php @@ -0,0 +1,159 @@ +#!/usr/bin/php -q + + * @version $Id: $ + */ + + $path_to_egroupware = realpath(dirname(__FILE__).'/..'); + + $usage = "usage: + --definition + --file + --user + --password + --domain \n"; + + if (php_sapi_name() != 'cli') + { + die('This script only runs form command line'); + } + + // Include PEAR::Console_Getopt + require_once 'Console/Getopt.php'; + + // Define exit codes for errors + define('HEADER_NOT_FOUND',9); + define('NO_ARGS',10); + define('INVALID_OPTION',11); + + // Reading the incoming arguments - same as $argv + $args = Console_Getopt::readPHPArgv(); + + // Make sure we got them (for non CLI binaries) + if (PEAR::isError($args)) { + fwrite(STDERR,"importexport_cli: ".$args->getMessage()."\n".$usage); + exit(NO_ARGS); + } + + // Short options + $short_opts = 'f:d:'; + + // Long options + $long_opts = array( + 'definition=', + 'file=', + 'user=', + 'password=', + 'domain=' + ); + + // Convert the arguments to options - check for the first argument + if ( realpath($_SERVER['argv'][0]) == __FILE__ ) { + $options = Console_Getopt::getOpt($args,$short_opts,$long_opts); + } else { + $options = Console_Getopt::getOpt2($args,$short_opts,$long_opts); + } + + // Check the options are valid + if (PEAR::isError($options)) { + fwrite(STDERR,"importexport_cli: ".$options->getMessage()."\n".$usage."\n"); + exit(INVALID_OPTION); + } + + $domain = 'default'; + foreach ($options[0] as $option) + { + switch ($option[0]) + { + case '--file' : + $file = $option[1]; + break; + case '--definition' : + $definition = $option[1]; + break; + case '--domain' : + $domain = $option[1]; + break; + case '--user' : + $user = $option[1]; + break; + case '--password' : + $password = $option[1]; + break; + default : + fwrite (STDERR,$usage."\n"); + exit(INVALID_OPTION); + } + } + // check file + if (!$user || !$password) + { + fwrite(STDERR,'importexport_cli: You have to supply a username / password'."\n".$usage); + exit(INVALID_OPTION); + } + + $GLOBALS['egw_info']['flags'] = array( + 'disable_Template_class' => True, + 'noheader' => True, + 'nonavbar' => True, + 'currentapp' => 'importexport', + 'autocreate_session_callback' => 'import_export_access', + 'login' => $user, + 'passwd' => $password, + 'noapi' => True, + ); + if (!is_readable($path_to_egroupware.'/header.inc.php')) + { + fwrite(STDERR,"importexport.php: Could not find '$path_to_egroupware/header.inc.php', exiting !!!\n"); + exit(HEADER_NOT_FOUND); + } + include($path_to_egroupware.'/header.inc.php'); + unset($GLOBALS['egw_info']['flags']['noapi']); + + // check domain + $db_type = $GLOBALS['egw_domain'][$domain]['db_type']; + if (!isset($GLOBALS['egw_domain'][$domain]) || empty($db_type)) + { + fwrite(STDERR,"importexport_cli: ". $domain. ' is not a valid domain name'."\n"); + exit(INVALID_OPTION); + } + $GLOBALS['egw_info']['server']['sessions_type'] = 'db'; // no php4-sessions availible for cgi + + include(PHPGW_API_INC.'/functions.inc.php'); + + // check file + if (!is_readable($file)) + { + fwrite(STDERR,"importexport_cli: ". $file. ' is not readable'."\n"); + exit(INVALID_OPTION); + } + + require_once('./inc/class.definition.inc.php'); + try { + $definition = new definition($definition); + } + catch (Exception $e) { + fwrite(STDERR,"importexport_cli: ". $e->getMessage(). "\n"); + exit(INVALID_OPTION); + } + + require_once("$path_to_egroupware/$definition->application/inc/class.$definition->plugin.inc.php"); + $po = new $definition->plugin; + $type = $definition->type; + $po->$type($definition,array('file' => $file)); + + $GLOBALS['egw']->common->phpgw_exit(); + + function import_export_access(&$account) + { + $account['login'] = $GLOBALS['egw_info']['flags']['login']; + $account['passwd'] = $GLOBALS['egw_info']['flags']['passwd']; + $account['passwd_type'] = 'text'; + return true; + } diff --git a/importexport/inc/.class.bodefinitions.inc.php.swp b/importexport/inc/.class.bodefinitions.inc.php.swp new file mode 100644 index 0000000000000000000000000000000000000000..68896c89eee5848fe16c13f50427e0d88e5fff43 GIT binary patch literal 16384 zcmeI3OKc=Z8OJ*h0uEtW2tp{l%CkeJXJgOUp7mqfYqL()u}r+Xme>0bmep#{)Xa3; z)7|On_SiAY0dasE2o5N$$N_~#2^Rz+2RI=V5e~eBB31+;1X4J0;1He?{J*MhPtSPG z<`h9xqy5cnSJzkHqiTBUt154eU0FWQrYbc9uj7XCf zIK12VH(a|Ll)zmn_u$h4<@6dK`lTbFBcLOoBcLOoBcLOoBcLOoBcLOoBcLOoBk+G3 z0jFUY_rBdQz6)sopVk2W@ovL-9oz!1fTw{2=D~6BuVaSsH}F^R6Yw&)37!LcpbK`u zHESOLr6A@Bk4%LkN=lUSJe6>R+l_!;;f z_zw6INI(QkaN|D1_$ugv7zAJ*Sl~Wz7x?4b4CD9U>!1m2a18wEUc>kScoBRVY=Rl^ zQSch{e*>(5B`^g(28O{$z=yyG!Edmjatk~UZi3GO2W*0APy>I#V#}YwOW=p#d2kav z2d==rgV%Lq2>*s69w(8{imo5?NbouxjhIC_-5csF-6(GJy-ha9icSh6PP z5OJs8HtUY(*@@th-Aw$tEvuR(`C%XED2-5CqRbq@=zrw{g;cyjN|Xd$nx_1JnbejC z)k!5S9z#(}n5sdlhX(tMC$p)stYwp0g54xfE~okqEVNPCOdTCjpA%(c-*B7E9L|Jn zzUzuu9CD9@OXc!VwaRe5WU?~OG6};-!!Ev-7B~GghJLa<3tHSeO;oxK$XT&uObr#~B#GV_7<86rytZi_hLWUp(TqC2O1cj`gn$I+hebohZ) zXe<*90}(r39cOvy?i2}6_3nBBhM*fumD9?Kn0t0Nh~Tzs&7cU;qN1j=OC_h%22Sko z2qxickaJABjBuXduD|{1PbAIMVe%M}JJI2tpfGzKCg7^=2)oUl1_r<9`mk6HkI@T%bVMHk23#DtM9G)RivKycTO$`KJ2}B7591Fc9B^)`X z$vLtUE#mYOBE5x6mfFv|&CT0d-3zXlq8(gmeq zW#PiP+iR#Pi|9)t>nj&lS3Wsdohb#W2Q)x)E?-<*Kcar-GL71t2kVO~M+l6k8fZ-C z1Rh^J%H>RehI|d3Jf>W}vbeT>=<#Ff2hjiA1IW?OANk{_pFi}-eM>!m1|M1S5qeGi zX3+0o?)kR3eBp_~C!%`d*2dCb8T#4FNj$oEeo!ELMo*Ch{lFoj^2g1N9bb43O$rOM z7iO}$8DO!A&w~fv1MV#+%R)O zTAbzWzzw5%@+8_}aloWeob9`vPx0OBbjn-EskJIMOUvh1@jJVCjo$5r z)m1o_Z;TvC=kiLTaT}2`wy7ijRN_V)^VK>g;pz~1pUv4Ja%%CweoQ0< z`s5^WJBZk0LF9AKO$58jT_M;Rb#=Dhjd{IYY4CYj8`H;a){f)Qs#d$*Zl!m$y%Iz% zS>=czv9q3A=SUqg@gT%ikVJJ(qSf@SsJ23HG`*lx@N8*yg^jYM3zyjvM>^nmY&F?J z1-7EvgHqDVm*|lFRg7TN&c~E`$2jwGI#5sf|JUAO7|-C$Kjr_l=l^Tu_%DN(z>mNS z;G5uc;7QN|9|m7U9{&a40T(nt4O9W;_pgIn;1%!{umRS=J>a*<=f4fU1wIefz$kbh zc=esg-N6Dl10Denf%k(4!F#}Q@bmi(`sF`yNh8gsicQz!%Hg3yR+DR? zk32aR8{?aqR&y9iNz9bOR0(lo6?SQH74NFWGQ;Y6?D`2;Vp%(=jlT!>kZ_u%3O1m6 z>i5aGZX4|~QzqVQcy5jz=%Bni?H(lPRF`u2Vgn7HrQ}N8b0ggB(=2V9V~a~y?MsVm zSYx%w`xI& zdEesp;iVb$WN)iYTHV{KRL=zJSS;C$sGCjC4g13{zj|qH zus*fO)7Y}>fhSZ;S&ydQpF_(>*%u3HiifPjn8L` + * @copyright Cornelius Weiss + * @version $Id: $ + */ + +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.definition.inc.php'); +require_once(EGW_INCLUDE_ROOT.'/etemplate/inc/class.so_sql.inc.php'); + +/** bo to define {im|ex}ports + * + * @todo make this class an egw_record_pool! + */ +class bodefinitions { + + const _appname = 'importexport'; + const _defintion_talbe = 'egw_importexport_definitions'; + + /** + * holds so_sql + * + * @var so_sql + */ + private $so_sql; + private $definitions; + + public function __construct($_query=false) + { + $this->so_sql = new so_sql(self::_appname, self::_defintion_talbe ); + if ($_query) { + $definitions = $this->so_sql->search($_query, true); + foreach ((array)$definitions as $definition) { + $this->definitions[] = $definition['definition_id']; + } + } + } + + public function get_definitions() { + return $this->definitions; + } + + /** + * reads a definition from database + * + * @param mixed &$definition + * @return bool success or not + */ + public function read(&$definition) + { + if(is_int($definition)) $definition = array('definition_id' => $definition); + elseif(is_string($definition)) $definition = array('name' => $definition); + if(!$definition = $this->so_sql->read($definition)) return false; + $definition += (array)unserialize($definition['plugin_options']); + unset($definition['plugin_options']); + return true; + } + + public function save($content) + { + $plugin = $content['plugin']; + if (!$plugin) return false; + + $definition = array_intersect_key($content,array_flip($this->so_sql->db_cols)); + + if(is_object($this->plugin) && $this->plugin->plugin_info['plugin'] == $plugin) + { + $plugin_options = array_intersect_key($content,array_flip($this->plugin->plugin_options)); + } + else + { + // we come eg. from definition import + $file = EGW_SERVER_ROOT . SEP . $definition['application'] . SEP . 'inc' . SEP . 'importexport'. SEP . 'class.'.$definition['plugin'].'.inc.php'; + if (is_file($file)) + { + @include_once($file); + $obj = new $plugin; + $plugin_options = array_intersect_key($content,array_flip($obj->plugin_options)); + unset($obj); + } + else + { + foreach ($this->so_sql->db_cols as $col) unset($content[$col]); + $plugin_options = $content; + + } + } + $definition['plugin_options'] = serialize($plugin_options); + $this->so_sql->data = $definition; + //print_r($definition); + return $this->so_sql->save(); + } + + public function delete($keys) + { + $this->so_sql->delete(array('definition_id' => $keys)); + } + + /** + * checkes if user if permitted to access given definition + * + * @param array $_definition + * @return bool + */ + static public function is_permitted($_definition) { + $allowed_user = explode(',',$_definition['allowed_users']); + $this_user_id = $GLOBALS['egw_info']['user']['userid']; + $this_membership = $GLOBALS['egw']->accounts->membership($this_user_id); + $this_membership[] = array('account_id' => $this_user_id); + //echo $this_user_id; + //echo ' '.$this_membership; + foreach ((array)$this_membership as $account) + { + $this_membership_array[] = $account['account_id']; + } + $alluser = array_intersect($allowed_user,$this_membership_array); + return in_array($this_user_id,$alluser) ? true : false; + } + + /** + * searches and registers plugins from all apps + * + * @deprecated see import_export_helperfunctions::get_plugins + * @return array $info info about existing plugins + */ + static public function plugins() + { + if (!key_exists('apps',$GLOBALS['egw_info'])) return false; + foreach (array_keys($GLOBALS['egw_info']['apps']) as $appname) + { + $dir = EGW_INCLUDE_ROOT . "/$appname/inc"; + if(!$d = @opendir($dir)) continue; + while (false !== ($file = readdir($d))) + { + //echo $file."\n"; + $pnparts = explode('.',$file); + if(!is_file($file = "$dir/$file") || substr($pnparts[1],0,7) != 'wizzard' || $pnparts[count($pnparts)-1] != 'php') continue; + $plugin_classname = $pnparts[1]; + include_once($file); + if (!is_object($GLOBALS['egw']->$plugin_classname)) + $GLOBALS['egw']->$plugin_classname = new $plugin_classname; + $info[$appname][$GLOBALS['egw']->$plugin_classname->plugin_info['plugin']] = $GLOBALS['egw']->$plugin_classname->plugin_info; + } + closedir($d); + } + return $info; + } + + + /** + * exports definitions + * + * @param array $keys to export + */ + public function export($keys) + { + $export_data = array('metainfo' => array( + 'type' => 'importexport definitions', + 'charset' => 'bal', + 'entries' => count($keys), + )); + + foreach ($keys as $definition_id) + { + $definition = array('definition_id' => $definition_id); + $this->read($definition); + unset($definition['definition_id']); + $export_data[$definition['name']] = $definition; + } + /* This is no fun as import -> export cycle in xmltools results in different datas :-( + $xml =& CreateObject('etemplate.xmltool','root'); + $xml->import_var('importexport.definitions', $export_data); + $xml_data = $xml->export_xml(); + + we export serialised arrays in the meantime + */ + return serialize($export_data); + } + + public function import($import_file) + { + if (!is_file($import_file['tmp_name'])) return false; + $f = fopen($import_file['tmp_name'],'r'); + $data = fread($f,100000); + fclose($f); + if (($data = unserialize($data)) === false) return false; + $metainfo = $data['metainfo']; + unset($data['metainfo']); + + foreach ($data as $name => $definition) + { + error_log(print_r($definition,true)); + //if (($ext = $this->search(array('name' => $name),'definition_id')) !== false) + //{ + // error_log(print_r($ext,true)); + // $definition['definition_id'] = $ext[0]['definition_id']; + //} + $this->save($definition); + } + } + +} + diff --git a/importexport/inc/class.definition.inc.php b/importexport/inc/class.definition.inc.php new file mode 100644 index 0000000000..edb1f6280f --- /dev/null +++ b/importexport/inc/class.definition.inc.php @@ -0,0 +1,302 @@ + + * @copyright Cornelius Weiss + * @version $Id: $ + */ + +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.iface_egw_record.inc.php'); +require_once(EGW_INCLUDE_ROOT.'/etemplate/inc/class.so_sql.inc.php'); + +/** + * class definition + * + * definitions are ojects with all nessesary information to do + * complete import or export. All options needed for an explicit {Im|Ex}port + * are in one assiozative array which is complely managed by {Im|Ex}port plugins + * @todo testing + */ +class definition implements iface_egw_record { + + const _appname = 'importexport'; + const _defintion_talbe = 'egw_importexport_definitions'; + + private $attributes = array( + 'definition_id' => 'string', + 'name' => 'string', + 'application' => 'string', + 'plugin' => 'string', + 'type' => 'string', + 'allowed_users' => 'array', + 'options' => 'array', + 'owner' => 'int', + ); + + /** + * holds so_sql object + * + * @var so_sql + */ + private $so_sql; + + /** + * internal representation of definition + * + * @var unknown_type + */ + private $definition = array(); + + /** + * holds current user + * + * @var int + */ + private $user; + + /** + * is current user an admin? + * + * @var bool + */ + private $is_admin; + + /** + * constructor + * reads record from backend if identifier is given. + * + * @param string $_identifier + */ + public function __construct( $_identifier='' ) { + $this->so_sql = new so_sql(self::_appname ,self::_defintion_talbe); + $this->user = $GLOBALS['egw_info']['user']['user_id']; + $this->is_admin = $GLOBALS['egw_info']['user']['apps']['admin'] ? true : false; + // compability to string identifiers + if (is_string($_identifier) && strlen($_identifier) > 3) $_identifier = $this->name2identifier($_identifier); + + if ((int)$_identifier != 0) { + $this->so_sql->read(array('definition_id' => $_identifier)); + if (empty($this->so_sql->data)) { + throw new Exception('Error: No such definition with identifier :"'.$_identifier.'"!'); + } + if (!(in_array($this->user,$this->get_allowed_users()) || $this->get_owner() == $this->user || $this->is_admin)) { + throw new Exception('Error: User "'.$this->user.'" is not permitted to get definition with identifier "'.$_identifier.'"!'); + $this->definition = $this->so_sql->data; + } + $this->definition = $this->so_sql->data; + } + } + + /** + * compability function for string identifiers e.g. used by cli + * + * @param string $_name + * @return int + */ + private function name2identifier($_name) { + $identifiers = $this->so_sql->search(array('name' => $_name),true); + if (isset($identifiers[1])) { + throw new Exception('Error: Definition: "'.$_name. '" is not unique! Can\'t convert to identifier'); + } + return $identifiers[0]['definition_id']; + } + + public function __get($_attribute_name) { + if (!array_key_exists($_attribute_name,$this->attributes)) { + throw new Exception('Error: "'. $_attribute_name. '" is not an attribute defintion'); + } + switch ($_attribute_name) { + case 'allowed_users' : + return $this->get_allowed_users(); + case 'options' : + return $this->get_options(); + default : + return $this->definition[$_attribute_name]; + } + } + + public function __set($_attribute_name,$_data) { + if (!array_key_exists($_attribute_name,$this->attributes)) { + throw new Exception('Error: "'. $_attribute_name. '" is not an attribute defintion'); + } + switch ($_attribute_name) { + case 'allowed_users' : + return $this->set_allowed_users($_data); + case 'options' : + return $this->set_options($_data); + default : + $this->definition[$_attribute_name] = $_data; + return; + } + } + + /** + * gets users who are allowd to use this definition + * + * @return array + */ + private function get_allowed_users() { + return explode(',',$this->definition['allowed_users']); + } + + /** + * sets allowed users + * + * @param array $_allowed_users + */ + private function set_allowed_users($_allowed_users) { + $this->definition['allowed_users'] = implode(',',(array)$_allowed_users); + } + + /** + * gets options + * + * @return array + */ + private function get_options() { + // oh compat funct to be removed! + if(array_key_exists('plugin_options',$this->definition)) { + $this->definition['options'] = $this->definition['plugin_options']; + unset($this->definition['plugin_options']); + } + return unserialize($this->definition['options']); + } + + /** + * sets options + * + * @param array $options + */ + private function set_options(array $_options) { + $this->definition['options'] = serialize($_options); + } + + /** + * converts this object to array. + * @abstract We need such a function cause PHP5 + * dosn't allow objects do define it's own casts :-( + * once PHP can deal with object casts we will change to them! + * + * @return array complete record as associative array + */ + public function get_record_array() { + $definition = $this->definition; + $definition['allowed_users'] = $this->get_allowed_users(); + $definition['options'] = $this->get_options(); + return $definition; + } + + /** + * gets title of record + * + *@return string tiltle + */ + public function get_title() { + return $this->definition['name']; + } + + /** + * sets complete record from associative array + * + * @return void + */ + public function set_record(array $_record) { + $this->definition = $_record; + } + + /** + * gets identifier of this record + * + * @return string identifier of this record + */ + public function get_identifier() { + return $this->definition['definition_id']; + } + + + /** + * saves record into backend + * + * @return string identifier + */ + public function save ( $_dst_identifier ) { + if ($owner = $this->get_owner() && $owner != $this->user && !$this->is_admin) { + throw ('Error: User '. $this->user. ' is not allowed to save this definition!'); + } + if ($this->so_sql->save($_dst_identifier)) { + throw('Error: so_sql was not able to save definition: '.$this->get_identifier()); + } + return $this->definition['definition_id']; + } + + /** + * copys current record to record identified by $_dst_identifier + * + * @param string $_dst_identifier + * @return string dst_identifier + */ + public function copy ( $_dst_identifier ) { + $dst_object = clone $this; + try { + $dst_object->set_owner($this->user); + $dst_identifier = $dst_object->save($_dst_identifier); + } + catch(exception $Exception) { + unset($dst_object); + throw $Exception; + } + unset ($dst_object); + return $dst_identifier; + } + + /** + * moves current record to record identified by $_dst_identifier + * $this will become moved record + * + * @param string $_dst_identifier + * @return string dst_identifier + */ + public function move ( $_dst_identifier ) { + if ($this->user != $this->get_owner() && !$this->is_admin) { + throw('Error: User '. $this->user. 'does not have permissions to move definition '.$this->get_identifier()); + } + $old_object = clone $this; + try { + $dst_identifier = $this->save($_dst_identifier); + $old_object->delete(); + } + catch(exception $Exception) { + unset($old_object); + throw $Exception; + } + unset($old_object); + return $dst_identifier; + } + + /** + * delets current record from backend + * @return void + * + */ + public function delete () { + if($this->user != $this->get_owner() && !$this->is_admin) { + throw('Error: User '. $this->user. 'does not have permissions to delete definition '.$this->get_identifier()); + } + if(!$this->so_sql->delete()) { + throw('Error: so_sql was not able to delete definition: '.$this->get_identifier()); + } + } + + /** + * destructor + * + */ + public function __destruct() { + unset($this->so_sql); + } + +} \ No newline at end of file diff --git a/importexport/inc/class.export_csv.inc.php b/importexport/inc/class.export_csv.inc.php new file mode 100644 index 0000000000..c10722c24e --- /dev/null +++ b/importexport/inc/class.export_csv.inc.php @@ -0,0 +1,200 @@ + + * @copyright Cornelius Weiss + * @version $Id: $ + */ + +require_once('class.iface_export_record.inc.php'); +require_once('class.import_export_helper_functions.inc.php'); +require_once('class.iface_egw_record.inc.php'); + +/** + * class export_csv + * This an record exporter. + * An record is e.g. a single address or or single event. + * No mater where the records come from, at the end export_entry + * stores it into the stream + */ +class export_csv implements iface_export_record +{ + + /** Aggregations: */ + + /** Compositions: */ + + /** + * array with field mapping in form egw_field_name => exported_field_name + * @var array + */ + protected $mapping = array(); + + /** + * array with conversions to be done in form: egw_field_name => conversion_string + * @var array + */ + protected $conversion = array(); + + /** + * array holding the current record + * @access protected + */ + protected $record = array(); + + /** + * holds (charset) translation object + * @var object + */ + protected $translation; + + /** + * charset of csv file + * @var string + */ + protected $csv_charset; + + /** + * holds number of exported records + * @var unknown_type + */ + protected $num_of_records = 0; + + /** + * stream resource of csv file + * @var resource + */ + protected $handle; + + /** + * csv specific options + * + * @var array + */ + protected $csv_options = array( + 'delimiter' => ';', + 'enclosure' => '"', + ); + + /** + * constructor + * + * @param object _handle resource where records are exported to. + * @param string _charset charset the records are exported to. + * @param array _options options for specific backends + * @return bool + * @access public + */ + public function __construct( $_handle, $_charset, array $_options=array() ) { + $this->translation &= $GLOBALS['egw']->translation; + $this->handle = $_handle; + $this->csv_charset = $_charset; + if (!empty($_options)) { + $this->csv_options = array_merge($this->csv_options,$_options); + } + } + + /** + * sets field mapping + * + * @param array $_mapping egw_field_name => csv_field_name + */ + public function set_mapping( array $_mapping) { + if ($this->num_of_records > 0) { + throw new Exception('Error: Field mapping can\'t be set during ongoing export!'); + } + foreach ($_mapping as $egw_filed => $csv_field) { + $this->mapping[$egw_filed] = $this->translation->convert($csv_field, $this->csv_charset); + } + } + + /** + * Sets conversion. + * See import_export_helper_functions::conversion. + * + * @param array $_conversion + */ + public function set_conversion( array $_conversion) { + $this->conversion = $_conversion; + } + + /** + * exports a record into resource of handle + * + * @param iface_egw_record record + * @return bool + * @access public + */ + public function export_record( iface_egw_record $_record ) { + $record_data = $_record->get_record_array(); + + if (empty($this->mapping)) { + $this->mapping = array_combine(array_keys($record_data),array_keys($record_data)); + } + + if ($this->num_of_records == 0 && $this->csv_options['begin_with_fieldnames'] && !empty($this->mapping)) { + fputcsv($this->handle,array_values($this->mapping),$this->csv_options['delimiter'],$this->csv_options['enclosure']); + } + + // do conversions + if ($this->conversion[$egw_field]) { + $record_data[$egw_field] = import_export_helper_functions::conversion($record_data,$this->conversion); + } + + // do fieldmapping + foreach ($this->mapping as $egw_field => $csv_field) { + $this->record[$csv_field] = $record_data[$egw_field]; + } + + $this->fputcsv($this->handle,$this->record,$this->csv_options['delimiter'],$this->csv_options['enclosure']); + $this->num_of_records++; + $this->record = array(); + } + + /** + * Retruns total number of exported records. + * + * @return int + * @access public + */ + public function get_num_of_records() { + return $this->num_of_records; + } + + /** + * destructor + * + * @return + * @access public + */ + public function __destruct() { + + } + + /** + * The php build in fputcsv function is buggy, so we need an own one :-( + * + * @param resource $filePointer + * @param array $dataArray + * @param char $delimiter + * @param char $enclosure + */ + protected function fputcsv($filePointer, $dataArray, $delimiter, $enclosure){ + $string = ""; + $writeDelimiter = false; + foreach($dataArray as $dataElement) { + if($writeDelimiter) $string .= $delimiter; + $string .= $enclosure . $dataElement . $enclosure; + $writeDelimiter = true; + } + $string .= "\n"; + + fwrite($filePointer, $string); + + } +} // end export_csv_record +?> diff --git a/importexport/inc/class.iface_egw_record.inc.php b/importexport/inc/class.iface_egw_record.inc.php new file mode 100644 index 0000000000..73c3213d35 --- /dev/null +++ b/importexport/inc/class.iface_egw_record.inc.php @@ -0,0 +1,123 @@ + + * @copyright Cornelius Weiss + * @version $Id: $ + */ + +/** + * class iface_egw_record + * This a the abstract interface of an egw record. + * A record is e.g. a single address or or single event. + * The idea behind is that we can have metaoperation over differnt apps by + * having a common interface. + * A record is identified by a identifier. As we are a Webapp and want to + * deal with the objects in the browser, identifier should be a string! + * + * @todo lots! of discussion with other developers + * @todo move to api once developers accepted it! + * @todo functions for capabilities of object + * @todo caching. e.g. only read name of object + */ +interface iface_egw_record +{ + + /** + * constructor + * reads record from backend if identifier is given. + * + * @param string $_identifier + */ + public function __construct( $_identifier ); + + /** + * magic method to set attributes of record + * + * @param string $_attribute_name + */ + public function __get($_attribute_name); + + /** + * magig method to set attributes of record + * + * @param string $_attribute_name + * @param data $data + */ + public function __set($_attribute_name, $data); + + /** + * converts this object to array. + * @abstract We need such a function cause PHP5 + * dosn't allow objects do define it's own casts :-( + * once PHP can deal with object casts we will change to them! + * + * @return array complete record as associative array + */ + public function get_record_array(); + + /** + * gets title of record + * + *@return string tiltle + */ + public function get_title(); + + /** + * sets complete record from associative array + * + * @return void + */ + public function set_record(array $_record); + + /** + * gets identifier of this record + * + * @return string identifier of this record + */ + public function get_identifier(); + + + /** + * saves record into backend + * + * @return string identifier + */ + public function save ( $_dst_identifier ); + + /** + * copys current record to record identified by $_dst_identifier + * + * @param string $_dst_identifier + * @return string dst_identifier + */ + public function copy ( $_dst_identifier ); + + /** + * moves current record to record identified by $_dst_identifier + * $this will become moved record + * + * @param string $_dst_identifier + * @return string dst_identifier + */ + public function move ( $_dst_identifier ); + + /** + * delets current record from backend + * @return void + * + */ + public function delete (); + + /** + * destructor + * + */ + public function __destruct(); + +} // end of iface_egw_record +?> diff --git a/importexport/inc/class.iface_export_plugin.inc.php b/importexport/inc/class.iface_export_plugin.inc.php new file mode 100644 index 0000000000..c1f0f33ade --- /dev/null +++ b/importexport/inc/class.iface_export_plugin.inc.php @@ -0,0 +1,92 @@ + + * @copyright Cornelius Weiss + * @version $Id: $ + */ + +//require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.iface_egw_record.inc.php'); + +/** + * class iface_export_plugin + * This a the abstract interface for an export plugin of importexport + * + * You need to implement this class in + * EGW_INCLUDE_ROOT/appname/inc/class.export_.inc.php + * to attend the importexport framwork with your export. + * + * NOTE: This is an easy interface, cause plugins live in theire own + * space. Means that they are respnsible for generationg a defintion AND + * working on that definition. + * So this interface just garanties the interaction with userinterfaces. It + * has nothing to do with datatypes. + * + * JS: + * required function in opener: + * + * + * // returns array of identifiers + * // NOTE: identifiers need to b + * get_selection(); + * + * get_selector(); //returns array + */ +interface iface_export_plugin { + + /** + * exports entries according to given definition object. + * + * @param definition $_definition + */ + public static function export($_stream, $_charset, definition $_definition); + + /** + * returns translated name of plugin + * + * @return string name + */ + public static function get_name(); + + /** + * returns translated (user) description of plugin + * + * @return string descriprion + */ + public static function get_description(); + + /** + * retruns file suffix for exported file (e.g. csv) + * + * @return string suffix + */ + public static function get_filesuffix(); + + /** + * return etemplate components for options. + * @abstract We can't deal with etemplate objects here, as an uietemplate + * objects itself are scipt orientated and not "dialog objects" + * + * @return array ( + * name => string, + * content => array, + * sel_options => array, + * preserv => array, + * ) + */ + + public static function get_options_etpl(); + + /** + * returns etemplate name for slectors of this plugin + * + * @return string etemplate name + */ + public static function get_selectors_etpl(); + +} // end of iface_export_plugin +?> diff --git a/importexport/inc/class.iface_export_record.inc.php b/importexport/inc/class.iface_export_record.inc.php new file mode 100644 index 0000000000..5c5aefc940 --- /dev/null +++ b/importexport/inc/class.iface_export_record.inc.php @@ -0,0 +1,71 @@ + + * @copyright Cornelius Weiss + * @version $Id: $ + */ + +/** + * class iface_export_record + * This a the abstract interface for an record exporter. + * An record is e.g. a single address or or single event. + * No mater where the records come from, at the end export_entry + * stores it into the stream + * NOTE: we don't give records the type "egw_reocrd". Thats becuase + * PHP5 dosn't allow objects do define it's own casts :-( + * + * NOTE: You are not forced to implement this interface to attend importexport + * framework. However if you plugin implements this interface it might also be + * usable for other tasks. + * + */ +interface iface_export_record +{ + + /** Aggregations: */ + + /** Compositions: */ + + /** + * constructor + * + * @param object _handle resource where records are exported to. + * @param string _charset charset the records are exported to. + * @param array _options options for specific backends + * @return bool + * @access public + */ + public function __construct( $_handle, $_charset, array $_options ); + + /** + * exports a record into resource of handle + * + * @param object of interface egw_record _record + * @return bool + * @access public + */ + public function export_record( iface_egw_record $_record ); + + /** + * Retruns total number of exported records. + * + * @return int + * @access public + */ + public function get_num_of_records( ); + + /** + * destructor + * + * @return + * @access public + */ + public function __destruct( ); + +} // end of iface_export_record +?> diff --git a/importexport/inc/class.iface_import_record.inc.php b/importexport/inc/class.iface_import_record.inc.php new file mode 100644 index 0000000000..d4b0472f17 --- /dev/null +++ b/importexport/inc/class.iface_import_record.inc.php @@ -0,0 +1,76 @@ + + * @copyright Cornelius Weiss + * @version $Id: $ + */ + + + +/** + * class iface_import_record + * This a the abstract interface for an record importer. + * An record is e.g. a single address or or single event. + * No mater where the records come from, at the end the get_entry method comes out + */ +interface iface_import_record +{ + + /** Aggregations: */ + + /** Compositions: */ + + /** + * Opens resource, returns false if something fails + * + * @param string _resource resource containing data. Differs according to the implementations + * @param array _options options for the resource + * @return bool + * @access public + */ + public function __construct( $_resource, $_options ); + + /** + * cleanup + * + * @return + * @access public + */ + public function __destruct( ); + + /** + * Returns array with the record found at position and updates the position + * + * @param string _position may be: {first|last|next|previous|somenumber} + * @return bool + * @access public + */ + public function get_record( $_position = 'next' ); + + /** + * Retruns total number of records for the open resource. + * + * @return int + * @access public + */ + public function get_num_of_records( ); + + /** + * Returns pointer of current position + * + * @return int + * @access public + */ + public function get_current_position( ); + + + + + +} // end of iface_import_record +?> diff --git a/importexport/inc/class.import_csv.inc.php b/importexport/inc/class.import_csv.inc.php new file mode 100755 index 0000000000..1fb2d0cfcf --- /dev/null +++ b/importexport/inc/class.import_csv.inc.php @@ -0,0 +1,299 @@ + + * @copyright Cornelius Weiss + * @version $Id: $ + */ + +require_once('class.iface_import_record.inc.php'); +require_once('class.import_export_helper_functions.inc.php'); + +/** + * class import_csv + * This a an abstract implementation of interface iface_import_record + * An record is e.g. a single address or or single event. + * No mater where the records come from, at the end the get_record method comes out + * @todo whipe out the bool returns and use exeptions instead!!! + * @todo do we realy need the stepping? otherwise we also could deal with stream resources. + * The advantage of resources is, that we don't need to struggle whth stream options! + * @todo just checked out iterators, but not shure if we can use it. + * they iterate over public functions and not over datastructures :-( + */ +class import_csv implements iface_import_record { //, Iterator { + + const csv_max_linelength = 8000; + + /** Aggregations: */ + + /** Compositions: */ + + /** + * @static import_export_helper_functions + */ + + /*** Attributes: ***/ + + /** + * array holding the current record + * @access protected + */ + protected $record = array(); + + /** + * current position counter + * @access protected + */ + protected $current_position = 0; + + /** + * holds total number of records + * @access private + * @var int + */ + protected $num_of_records = 0; + + /** + * array with field mapping in form column number => new_field_name + * @access protected + */ + protected $mapping = array(); + + /** + * array with conversions to be done in form: new_field_name => conversion_string + * @access protected + */ + protected $conversion = array(); + + /** + * csv resource + * @access private + */ + private $resource; + + /** + * holds the string of the resource + * needed e.g. to reopen resource + * @var string + * @access private + */ + private $csv_resourcename = ''; + + /** + * fieldseperator for csv file + * @access private + * @var char + */ + private $csv_fieldsep; + + /** + * charset of csv file + * @var string + * @access privat + */ + private $csv_charset; + + /** + * Opens resource, returns false if something fails + * + * @param string _resource resource containing data. May be each valid php-stream + * @param array _options options for the resource array with keys: charset and fieldsep + * @return bool + * @access public + */ + public function __construct( $_resource, $_options = array() ) { + $this->csv_resourcename = $_resource; + $this->csv_fieldsep = $_options['fieldsep']; + $this->csv_charset = $_options['charset']; + + //return $this->open_resource(); + } // end of member function __construct + + /** + * cleanup + * + * @return + * @access public + */ + public function __destruct( ) { + //$this->close_resource(); + } // end of member function __destruct + + /** + * Returns array with the record found at position and updates the position + * + * @param mixed _position may be: {current|first|last|next|previous|somenumber} + * @return array + * @access public + */ + public function get_record( $_position = 'next' ) { + $this->get_raw_record( $_position ); + $this->do_fieldmapping(); + $this->do_conversions(); + return $this->record; + } // end of member function get_record + + + /** + * updates $this->record + * + * @param mixed $_position + * @return bool + */ + private function get_raw_record( $_position = 'next' ) { + switch ($_position) { + case 'current' : + if ($this->current_position == 0) { + return false; + } + break; + case 'first' : + if (!$this->current_position == 0) { + $this->close_resource(); + $this->open_resource(); + } + + case 'next' : + $csv_data = fgetcsv( $this->resource, self::csv_max_linelength, $this->csv_fieldsep); + if (!is_array($csv_data)) { + return false; + } + $this->current_position++; + $this->record = $csv_data; + //$this->record = $GLOBALS['egw']->translation->convert($csv_data, $this->csv_charset); + break; + + case 'previous' : + if ($this->current_position < 2) { + return false; + } + $final_position = --$this->current_position; + $this->close_resource(); + $this->open_resource(); + while ($this->current_position !== $final_position) { + $this->get_raw_record(); + } + break; + + case 'last' : + while ($this->get_raw_record()) {} + break; + + default: //somenumber + if (!is_int($_position)) return false; + if ($_position == $this->current_position) { + break; + } + elseif ($_position < $this->current_position) { + $this->close_resource(); + $this->open_resource(); + } + while ($this->current_position !== $_position) { + $this->get_raw_record(); + } + break; + } + return true; + } // end of member function get_raw_record + + /** + * Retruns total number of records for the open resource. + * + * @return int + * @access public + */ + public function get_num_of_records( ) { + if ($this->num_of_records > 0) { + return $this->num_of_records; + } + $current_position = $this->current_position; + while ($this->get_raw_record()) {} + $this->num_of_records = $this->current_position; + $this->get_record($current_position); + return $this->num_of_records; + } // end of member function get_num_of_records + + /** + * Returns pointer of current position + * + * @return int + * @access public + */ + public function get_current_position( ) { + + return $this->current_position; + + } // end of member function get_current_position + + + /** + * does fieldmapping according to $this->mapping + * + * @return + * @access protected + */ + protected function do_fieldmapping( ) { + foreach ($this->mapping as $cvs_idx => $new_idx) { + $record = $this->record; + $this->record = array(); + $this->record[$new_idx] = $record[$cvs_idx]; + return true; + } + } // end of member function do_fieldmapping + + /** + * does conversions according to $this->conversion + * + * @return bool + * @access protected + */ + protected function do_conversions( ) { + if ( $record = import_export_helper_functions::conversion( $this->record, $this->conversion )) { + $this->record = $record; + return true; + } + else return false; + } // end of member function do_conversions + + + /** + * opens the csv resource (php-stream) + * + * @param string _resource resource containing data. May be each valid php-stream + * @param array _options options for the resource array with keys: charset and fieldsep + * @return + * @access private + */ + private function open_resource() { + if ( !is_readable ( $this->csv_resourcename )) { + error_log('error: file '. $this->csv_resourcename .' is not readable by webserver '.__FILE__.__LINE__); + return false; + } + + $this->resource = fopen ($this->csv_resourcename, 'rb'); + + if (!is_resource($this->resource)) { + // some error handling + return false; + } + $this->current_position = 0; + return true; + } // end of member function open_resource + + /** + * closes the csv resource (php-stream) + * + * @return bool + * @access private + */ + private function close_resource() { + return fclose( $this->resource ); + } // end of member function close_resource + +} // end of import_csv +?> diff --git a/importexport/inc/class.import_export_helper_functions.inc.php b/importexport/inc/class.import_export_helper_functions.inc.php new file mode 100755 index 0000000000..edd2916e80 --- /dev/null +++ b/importexport/inc/class.import_export_helper_functions.inc.php @@ -0,0 +1,287 @@ + + * @copyright Cornelius Weiss + * @version $Id: $ + */ + + + +/** + * class import_export_helper_functions (only static methods) + * use import_export_helper_functions::method + */ +class import_export_helper_functions +{ + + /** Aggregations: */ + + /** Compositions: */ + + /*** Attributes: ***/ + + /** + * nothing to construct here, only static functions! + * + * @return bool false + */ + public function __construct() { + return false; + } + /** + * converts accound_lid to account_id + * + * @param string _account_lid comma seperated list + * @return string comma seperated list + * @static + * @access public + */ + public static function account_lid2id( $_account_lids ) { + $account_lids = explode( ',', $_account_lids ); + foreach ( $account_lids as $account_lid ) { + if ( $account_id = $GLOBALS['egw']->accounts->name2id( $account_lid )) { + $account_ids[] = $account_id; + } + } + return implode( ',', $account_ids ); + + } // end of member function account_lid2id + + /** + * converts account_ids to account_lids + * + * @param int _account_ids comma seperated list + * @return string comma seperated list + * @static + * @access public + */ + public static function account_id2lid( $_account_id ) { + $account_ids = explode( ',', $_account_id ); + foreach ( $account_ids as $account_id ) { + if ( $account_lid = $GLOBALS['egw']->accounts->id2name( $account_id )) { + $account_lids[] = $account_lid; + } + } + return implode( ',', $account_lids ); + } // end of member function account_id2lid + + /** + * converts cat_id to a cat_name + * + * @param int _cat_ids comma seperated list + * @return mixed string cat_name + * @static + * @access public + */ + public static function cat_id2name( $_cat_ids ) { + if ( !is_object($GLOBALS['egw']->categories) ) { + $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories'); + } + $cat_ids = explode( ',', $_cat_id ); + foreach ( $cat_ids as $cat_id ) { + $cat_names[] = $GLOBALS['egw']->categories->id2name( (int)$cat_id ); + } + return implode(',',$cat_names); + } // end of member function category_id2name + + /** + * converts cat_name to a cat_id. + * If a cat isn't found, it will be created. + * + * @param string _cat_names comma seperated list. + * @return mixed int / string (comma seperated cat id's) + * @static + * @access public + */ + public static function cat_name2id( $_cat_names, $_create = true ) { + if (!is_object($GLOBALS['egw']->categories)) { + $GLOBALS['egw']->categories =& CreateObject( 'phpgwapi.categories' ); + } + $cat_names = explode( ',', $_cat_names ); + foreach ( $cat_names as $cat_name ) { + if ( $cat_id = $GLOBALS['egw']->categories->name2id( addslashes( $cat_name ))) { } + elseif ($_create) $cat_id = $GLOBALS['egw']->categories->add( array( 'name' => $cat_name,'descr' => $cat_name )); + else continue; + $cat_ids[] = $cat_id; + } + return implode( ',', $cat_ids ); + + } // end of member function category_name2id + + /** + * conversion + * + * Conversions enable you to change / adapt the content of each _record field for your needs. + * General syntax is: pattern1 |> replacement1 || ... || patternN |> replacementN + * If the pattern-part of a pair is ommited it will match everything ('^.*$'), which + * is only usefull for the last pair, as they are worked from left to right. + * Example: 1|>private||public + * This will translate a '1' in the _record field to 'privat' and everything else to 'public'. + * + * In addintion to the fields assign by the pattern of the reg.exp. + * you can use all other _record fields, with the syntax |[FIELDNAME]. + * Example: + * .+|>|[Company]: |[NFamily], |[NGiven]|||[NFamily], |[NGiven] + * It is used on the _record field 'Company' and constructs a something like + * Company: FamilyName, GivenName or FamilyName, GivenName if 'Company' is empty. + * + * Moreover the helper function of this class can be used using the '@' operator. + * @cat_name2id(Cat1,...,CatN) returns a (','-separated) list with the cat_id's. If a + * category isn't found, it will be automaticaly added. + * + * Patterns as well as the replacement can be regular expressions (the replacement is done + * via ereg_replace). + * + * If, after all replacements, the value starts with an '@' the whole + * value is eval()'ed, so you may use all php, phpgw plus your own functions. This is quiet + * powerfull, but circumvents all ACL. Therefor this feature is only availible to + * Adminstrators. + * + * Example using regular expressions and '@'-eval(): + * ||0?([0-9]+)[ .:-]+0?([0-9]*)[ .:-]+0?([0-9]*)[ .:-]+0?([0-9]*)[ .:-]+0?([0-9]*)[ .:-]+0?([0-9]*).*|>@mktime(|#4,|#5,|#6,|#2,|#3,|#1) + * It will read a date of the form '2001-05-20 08:00:00.00000000000000000' (and many more, + * see the regular expr.). The [ .:-]-separated fields are read and assigned in different + * order to @mktime(). Please note to use |# insted of a backslash (I couldn't get backslash + * through all the involved templates and forms.) plus the field-number of the pattern. + * + * @param array _record reference with record to do the conversion with + * @param array _conversion array with conversion description + * @return bool + * @static + * @access public + */ + public static function conversion( $_record, $_conversion ) { + + $PSep = '||'; // Pattern-Separator, separats the pattern-replacement-pairs in conversion + $ASep = '|>'; // Assignment-Separator, separats pattern and replacesment + $VPre = '|#'; // Value-Prefix, is expanded to \ for ereg_replace + $CPre = '|['; $CPreReg = '\|\['; // |{_record-fieldname} is expanded to the value of the _record-field + $CPos = ']'; $CPosReg = '\]'; // if used together with @ (replacement is eval-ed) value gets autom. quoted + + foreach ( $_record as $record_idx => $record_value ) { + $pat_reps = explode($PSep,stripslashes($_conversion[$record_idx])); + $replaces = ''; $rvalues = ''; + if($pat_reps[0] != '') + { + foreach($pat_reps as $k => $pat_rep) + { + list($pattern,$replace) = explode($ASep,$pat_rep,2); + if($replace == '') + { + $replace = $pattern; $pattern = '^.*$'; + } + $rvalues[$pattern] = $replace; // replace two with only one, added by the form + $replaces .= ($replaces != '' ? $PSep : '') . $pattern . $ASep . $replace; + } + //$_conversion[$record_idx] = $rvalues; + $conv_record = $rvalues; + } + else + { + //unset($_conversion[$record_idx] ); + } + + if(!empty($_conversion[$record_idx])) + { + //$conv_record = $_conversion[$record_idx]; + while(list($pattern,$replace) = each($conv_record)) + { + if(ereg((string) $pattern,$val)) + { + $val = ereg_replace((string) $pattern,str_replace($VPre,'\\',$replace),(string) $val); + + $reg = $CPreReg.'([a-zA-Z_0-9]+)'.$CPosReg; + while(ereg($reg,$val,$vars)) + { // expand all _record fields + $val = str_replace($CPre . $vars[1] . $CPos, $val[0] == '@' ? "'" + . addslashes($fields[array_search($vars[1], array_keys($_record))]) + . "'" : $fields[array_search($vars[1], array_keys($_record))], $val); + } + if($val[0] == '@') + { + if (!$GLOBALS['egw_info']['user']['apps']['admin']) + { + error_log(__FILE__.__LINE__. lang('@-eval() is only availible to admins!!!')); + } + else + { + // removing the $ to close security hole of showing vars, which contain eg. passwords + $val = substr(str_replace('$','',$val),1).';'; + $val = 'return '. (substr($val,0,6) == 'cat_id' ? '$this->'.$val : $val); + // echo "

eval('$val')="; + $val = eval($val); + // echo "'$val'

"; + } + } + if($pattern[0] != '@' || $val) + { + break; + } + } + } + } + $values[$record_idx] = $val; + } + return $values; + } // end of member function conversion + + /** + * returns a list of importexport plugins + * + * @param string $_tpye {import | export | all} + * @param string $_appname { | all} + * @return array( => array( => array( => ))) + */ + public static function get_plugins($_appname, $_type){ + $appnames = $_appname == 'all' ? array_keys($GLOBALS['egw_info']['apps']) : (array)$_appname; + $types = $_types == 'all' ? array('import','export') : (array)$_type; + $plugins = array(); + + foreach ($appnames as $appname) { + $appdir = EGW_INCLUDE_ROOT. "/$appname/inc"; + if(!is_dir($appdir)) continue; + $d = dir($appdir); + + // step through each file in appdir + while (false !== ($entry = $d->read())) { + list( ,$classname, ,$extension) = explode('.',$entry); + $file = $appdir. '/'. $entry; + + foreach ($types as $type) { + if(!is_file($file) || substr($classname,0,7) != $type.'_' || $extension != 'php') continue; + require_once($file); + + try { + $plugin_object = @new $classname; + } + catch (Exception $exception) { + continue; + } + if (is_a($plugin_object,'iface_'.$type.'_plugin')) { + $plugins[$appname][$type][$classname] = $plugin_object->get_name(); + } + unset ($plugin_object); + } + } + $d->close(); + } + return $plugins; + } + + /** + * returns list of apps which have plugins of given type. + * + * @param string $_type + * @return array $num => $appname + */ + public static function get_apps($_type) { + return array_keys(self::get_plugins('all',$_type)); + } + +} // end of import_export_helper_functions +?> diff --git a/importexport/inc/class.importexport_admin_prefs_sidebox_hooks.inc.php b/importexport/inc/class.importexport_admin_prefs_sidebox_hooks.inc.php new file mode 100644 index 0000000000..2b5c80717a --- /dev/null +++ b/importexport/inc/class.importexport_admin_prefs_sidebox_hooks.inc.php @@ -0,0 +1,117 @@ +<?php +/** + * eGroupWare - importexport + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package importexport + * @link http://www.egroupware.org + * @copyright Cornelius Weiss <nelius@cwtech.de> + * @version $Id: $ + */ + +if (!defined('IMPORTEXPORT_APP')) +{ + define('IMPORTEXPORT_APP','importexport'); +} + +class importexport_admin_prefs_sidebox_hooks +{ + var $config = array(); + + function importexport_admin_prefs_sidebox_hooks() + { + $config =& CreateObject('phpgwapi.config',IMPORTEXPORT_APP); + $config->read_repository(); + $this->config =& $config->config_data; + unset($config); + } + + function all_hooks($args) + { + $appname = IMPORTEXPORT_APP; + $location = is_array($args) ? $args['location'] : $args; + + if ($location == 'sidebox_menu') + { + $file = array( + 'Import definitions' => $GLOBALS['egw']->link('/index.php','menuaction=importexport.uidefinitions.import_definition'), + ); + display_sidebox($appname,$GLOBALS['egw_info']['apps'][$appname]['title'].' '.lang('Menu'),$file); + } + + if ($GLOBALS['egw_info']['user']['apps']['preferences'] && $location != 'admin') + { + $file = array( +// 'Preferences' => $GLOBALS['egw']->link('/index.php','menuaction=preferences.uisettings.index&appname='.$appname), +// 'Grant Access' => $GLOBALS['egw']->link('/index.php','menuaction=preferences.uiaclprefs.index&acl_app='.$appname), +// 'Edit Categories' => $GLOBALS['egw']->link('/index.php','menuaction=preferences.uicategories.index&cats_app=' . $appname . '&cats_level=True&global_cats=True') + ); + if ($location == 'preferences') + { + display_section($appname,$file); + } + else + { + display_sidebox($appname,lang('Preferences'),$file); + } + } + + if ($GLOBALS['egw_info']['user']['apps']['admin'] && $location != 'preferences') + { + $file = Array( + 'Define {im|ex}ports' => $GLOBALS['egw']->link('/index.php',array( + 'menuaction' => 'importexport.uidefinitions.index', + )), + ); + if ($location == 'admin') + { + display_section($appname,$file); + } + else + { + display_sidebox($appname,lang('Admin'),$file); + } + } + } + + /** + * populates $GLOBALS['settings'] for the preferences + */ + function settings() + { + $this->check_set_default_prefs(); + + return true; // otherwise prefs say it cant find the file ;-) + } + + /** + * Check if reasonable default preferences are set and set them if not + * + * It sets a flag in the app-session-data to be called only once per session + */ + function check_set_default_prefs() + { + if ($GLOBALS['egw']->session->appsession('default_prefs_set',IMPORTEXPORT_APP)) + { + return; + } + $GLOBALS['egw']->session->appsession('default_prefs_set',IMPORTEXPORT_APP,'set'); + + $default_prefs =& $GLOBALS['egw']->preferences->default[IMPORTEXPORT_APP]; + + $defaults = array( + ); + foreach($defaults as $var => $default) + { + if (!isset($default_prefs[$var]) || $default_prefs[$var] === '') + { + $GLOBALS['egw']->preferences->add(IMPORTEXPORT_APP,$var,$default,'default'); + $need_save = True; + } + } + if ($need_save) + { + $GLOBALS['egw']->preferences->save_repository(False,'default'); + } + } +} \ No newline at end of file diff --git a/importexport/inc/class.uidefinitions.inc.php b/importexport/inc/class.uidefinitions.inc.php new file mode 100644 index 0000000000..e81938e5eb --- /dev/null +++ b/importexport/inc/class.uidefinitions.inc.php @@ -0,0 +1,461 @@ +<?php +/** + * eGroupWare - importexport + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package importexport + * @link http://www.egroupware.org + * @author Cornelius Weiss <nelius@cwtech.de> + * @copyright Cornelius Weiss <nelius@cwtech.de> + * @version $Id: $ + */ + +require_once(EGW_INCLUDE_ROOT.'/etemplate/inc/class.uietemplate.inc.php'); +require_once('class.bodefinitions.inc.php'); + +if (!defined('IMPORTEXPORT_APP')) +{ + define('IMPORTEXPORT_APP','importexport'); +} + +/** + * Userinterface to define {im|ex}ports + * + * @package importexport + */ +class uidefinitions +{ + const _debug = true; + + public $public_functions = array( + 'edit' => true, + 'index' => true, + 'wizzard' => true, + 'import_definition' => true, + ); + + /** + * holds all available plugins + * + * @var array + */ + var $plugins; + + /** + * holds user chosen plugin after step20 + * + * @var object + */ + var $plugin; + + /** + * xajax response object + * + * @var object + */ + var $response = true; + + function uidefinitions() + { + // we cant deal with notice and warnings, as we are on ajax! + error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); + $GLOBALS['egw']->translation->add_app(IMPORTEXPORT_APP); + $GLOBALS['egw_info']['flags']['currentapp'] = IMPORTEXPORT_APP; + + $GLOBALS['egw_info']['flags']['include_xajax'] = true; + if(!@is_object($GLOBALS['egw']->js)) + { + $GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript'); + } + $this->etpl =& new etemplate(); + $this->clock = $GLOBALS['egw']->html->image(IMPORTEXPORT_APP,'clock'); + $this->steps = array( + 'wizzard_step10' => lang('Choose an application'), + 'wizzard_step20' => lang('Choose a plugin'), + 'wizzard_step80' => lang('Which useres are allowed for this definition'), + 'wizzard_step90' => lang('Choose a name for this definition'), + 'wizzard_finish' => '', + ); + //register plugins (depricated) + $this->plugins = bodefinitions::plugins(); + } + + /** + * List defined {im|ex}ports + * + * @param array $content=null + */ + function index($content = null,$msg='') + { + $bodefinitions = new bodefinitions(array('name' => '*')); + if (is_array($content)) + { + if (isset($content['delete'])) + { + $bodefinitions->delete(array_keys($content['delete'],'pressed')); + } + elseif(($button = array_search('pressed',$content)) !== false) + { + $selected = array_keys($content['selected'],1); + if(count($selected) < 1 || !is_array($selected)) exit(); + switch ($button) + { + case 'delete_selected' : + $bodefinitions->delete($selected); + break; + + case 'export_selected' : + $mime_type = ($GLOBALS['egw']->html->user_agent == 'msie' || $GLOBALS['egw']->html->user_agent == 'opera') ? + 'application/octetstream' : 'application/octet-stream'; + $name = 'importexport.definition'; + header('Content-Type: ' . $mime_type); + header('Content-Disposition: attachment; filename="'.$name.'"'); + echo $bodefinitions->export($selected); + exit(); + + break; + + default: + } + } + + } + $etpl =& new etemplate(IMPORTEXPORT_APP.'.definition_index'); + + // we need an offset because of autocontinued rows in etemplate ... + $definitions = array('row0'); + + foreach ($bodefinitions->get_definitions() as $identifier) { + $definition = new definition($identifier); + $definitions[] = $definition->get_record_array(); + unset($definition); + } + $content = $definitions; + return $etpl->exec(IMPORTEXPORT_APP.'.uidefinitions.index',$content,array(),$readonlys,$preserv); + } + + function edit() + { + if(!$_definition = $_GET['definition']) + { + //close window + } + $definition = array('name' => $_definition); + $bodefinitions = new bodefinitions(); + $bodefinitions->read($definition); + $definition['edit'] = true; + $this->wizzard($definition); + } + + function wizzard($content = null, $msg='') + { + $GLOBALS['egw_info']['flags']['java_script'] .= + "<script LANGUAGE='JavaScript'> + function xajax_eT_wrapper_init() { + window.resizeTo(document.documentElement.scrollWidth+20,document.documentElement.offsetHeight+40); + window.moveTo(screen.availWidth/2 - window.outerWidth/2, + screen.availHeight/2 - window.outerHeight/2); + } + </script>"; + + $this->etpl->read('importexport.wizzardbox'); + $this->wizzard_content_template =& $this->etpl->children[0]['data'][1]['A'][2][1]['name']; + + if(is_array($content) &&! $content['edit']) + { + if(self::_debug) error_log('importexport.wizzard->$content '. print_r($content,true)); + // fetch plugin object + if($content['plugin'] && $content['application']) + { + // we need to deal with the wizzard object if exists + if (file_exists(EGW_SERVER_ROOT . '/'. $content['application'].'/inc/class.wizzard_'. $content['plugin'].'.inc.php')) + { + $wizzard_plugin = 'wizzard_'.$content['plugin']; + } + else + { + $wizzard_plugin = $content['plugin']; + } + $this->plugin = is_object($GLOBALS['egw']->$wizzard_plugin) ? $GLOBALS['egw']->$wizzard_plugin : new $wizzard_plugin; + if(!is_object($GLOBALS['egw']->uidefinitions)) $GLOBALS['egw']->uidefinitions =& $this; + } + // deal with buttons even if we are not on ajax + if(isset($content['button']) && array_search('pressed',$content['button']) === false && count($content['button']) == 1) + { + $button = array_keys($content['button']); + $content['button'] = array($button[0] => 'pressed'); + } + + // post process submitted step + if(!key_exists($content['step'],$this->steps)) + $next_step = $this->plugin->$content['step']($content); + else + $next_step = $this->$content['step']($content); + + // pre precess next step + $sel_options = $readonlys = $preserv = array(); + if(!key_exists($next_step,$this->steps)) + { + $this->wizzard_content_template = $this->plugin->$next_step($content,$sel_options,$readonlys,$preserv); + } + else + { + $this->wizzard_content_template = $this->$next_step($content,$sel_options,$readonlys,$preserv); + } + + $html = $this->etpl->exec(IMPORTEXPORT_APP.'.uidefinitions.wizzard',$content,$sel_options,$readonlys,$preserv,1); + } + else + { + // initial content + $GLOBALS['egw']->js->set_onload("xajax_eT_wrapper_init();"); + $GLOBALS['egw']->js->set_onload("disable_button('exec[button][previous]');"); + + $sel_options = $readonlys = $preserv = array(); + if($content['edit']) + unset ($content['edit']); + + $this->wizzard_content_template = $this->wizzard_step10($content, $sel_options, $readonlys, $preserv); + $html = $this->etpl->exec(IMPORTEXPORT_APP.'.uidefinitions.wizzard',$content,$sel_options,$readonlys,$preserv,1); + } + + if(class_exists('xajaxResponse')) + { + $this->response =& new xajaxResponse(); + + if ($content['closewindow']) + { + $this->response->addScript("window.close();"); + $this->response->addScript("opener.location.reload();"); + // If Browser can't close window we display a "close" buuton and + // need to disable normal buttons + $this->response->addAssign('exec[button][previous]','style.display', 'none'); + $this->response->addAssign('exec[button][next]','style.display', 'none'); + $this->response->addAssign('exec[button][finish]','style.display', 'none'); + $this->response->addAssign('exec[button][cancel]','style.display', 'none'); + } + $this->response->addAssign('contentbox', 'innerHTML', $html); + if (is_object($GLOBALS['egw']->js) && $GLOBALS['egw']->js->body['onLoad']) + { + $this->response->addScript($GLOBALS['egw']->js->body['onLoad']); + } + $this->response->addAssign('picturebox', 'style.display', 'none'); + $this->response->addScript("set_style_by_class('div','popupManual','display','inline');"); + + return $this->response->getXML(); + } + else + { + $GLOBALS['egw']->js->set_onload("document.getElementById('picturebox').style.display = 'none';"); + $GLOBALS['egw']->common->egw_header(); + echo '<div id="divMain">'."\n"; + echo '<div><h3>{Im|Ex}port Wizzard</h3></div>'; + // adding a manual icon to every popup + if ($GLOBALS['egw_info']['user']['apps']['manual']) + { + $manual =& new etemplate('etemplate.popup.manual'); + echo $manual->exec(IMPORTEXPORT_APP.'.uidefinitions.wizzard',$content,$sel_options,$readonlys,$preserv,1); + unset($manual); + } + + echo '<div id="contentbox">'; + echo $html; + echo '</div></div>'."\n"; + echo '<style type="text/css">#picturebox { position: absolute; right: 27px; top: 24px; }</style>'."\n"; + echo '<div id="picturebox">'. $this->clock. '</div>'; + return; + } + } + + /** + * gets name of next step + * + * @param string $curr_step + * @param int $step_width + * @return string containing function name of next step + */ + function get_step ($curr_step, $step_width) + { + /*if($content['plugin'] && $content['application']&& !is_object($this->plugin)) + { + $plugin_definition = $this->plugins[$content['application']][$content['plugin']]['definition']; + if($plugin_definition) $this->plugin =& new $plugin_definition; + }*/ + if(is_object($this->plugin)) + { + $steps = array_merge($this->steps,$this->plugin->steps); + $steps = array_flip($steps); asort($steps); $steps = array_flip($steps); + } + else + { + $steps = $this->steps; + } + $step_keys = array_keys($steps); + $nn = array_search($curr_step,$step_keys)+(int)$step_width; + return (key_exists($nn,$step_keys)) ? $step_keys[$nn] : false; + } + + + function wizzard_step10(&$content, &$sel_options, &$readonlys, &$preserv) + { + if(self::_debug) error_log('addressbook.importexport.addressbook_csv_import::wizzard_step10->$content '.print_r($content,true)); + + // return from step10 + if ($content['step'] == 'wizzard_step10') + { + switch (array_search('pressed', $content['button'])) + { + case 'next': + return $this->get_step($content['step'],1); + case 'finish': + return 'wizzard_finish'; + default : + return $this->wizzard_step10($content,$sel_options,$readonlys,$preserv); + } + + } + // init step10 + else + { + $content['msg'] = $this->steps['wizzard_step10']; + foreach ($this->plugins as $appname => $options) $sel_options['application'][$appname] = lang($appname); + $GLOBALS['egw']->js->set_onload("disable_button('exec[button][previous]');"); + $content['step'] = 'wizzard_step10'; + $preserv = $content; + unset ($preserv['button']); + return 'importexport.wizzard_chooseapp'; + } + + } + + // get plugin + function wizzard_step20(&$content, &$sel_options, &$readonlys, &$preserv) + { + if(self::_debug) error_log('addressbook.importexport.addressbook_csv_import::wizzard_step20->$content '.print_r($content,true)); + + // return from step20 + if ($content['step'] == 'wizzard_step20') + { + switch (array_search('pressed', $content['button'])) + { + case 'next': + $content['type'] = $this->plugin->plugin_info['type']; + return $this->get_step($content['step'],1); + case 'previous' : + unset ($content['plugin']); + $this->response->addScript("disable_button('exec[button][previous]');"); + return $this->get_step($content['step'],-1); + case 'finish': + return 'wizzard_finish'; + default : + return $this->wizzard_step20($content,$sel_options,$readonlys,$preserv); + } + } + // init step20 + else + { + $content['msg'] = $this->steps['wizzard_step20']; + foreach ($this->plugins[$content['application']] as $plugin => $info) $sel_options['plugin'][$plugin] = $info['name']; + $content['step'] = 'wizzard_step20'; + $preserv = $content; + unset ($preserv['button']); + return 'importexport.wizzard_chooseplugin'; + } + + + } + + // allowed users + function wizzard_step80(&$content, &$sel_options, &$readonlys, &$preserv) + { + if(self::_debug) error_log('importexport.uidefinitions::wizzard_step80->$content '.print_r($content,true)); + + // return from step80 + if ($content['step'] == 'wizzard_step80') + { + $content['allowed_users'] = implode(',',$content['allowed_users']); + + switch (array_search('pressed', $content['button'])) + { + case 'next': + return $this->get_step($content['step'],1); + case 'previous' : + return $this->get_step($content['step'],-1); + case 'finish': + return 'wizzard_finish'; + default : + return $this->wizzard_step80($content,$sel_options,$readonlys,$preserv); + } + } + // init step80 + else + { + $content['msg'] = $this->steps['wizzard_step80']; + $content['step'] = 'wizzard_step80'; + $preserv = $content; + unset ($preserv['button']); + return 'importexport.wizzard_chooseallowedusers'; + } + } + + // name + function wizzard_step90(&$content, &$sel_options, &$readonlys, &$preserv) + { + if(self::_debug) error_log('importexport.uidefinitions::wizzard_step90->$content '.print_r($content,true)); + + // return from step90 + if ($content['step'] == 'wizzard_step90') + { + // workaround for some ugly bug related to readonlys; + unset($content['button']['next']); + switch (array_search('pressed', $content['button'])) + { + case 'previous' : + return $this->get_step($content['step'],-1); + case 'finish': + return 'wizzard_finish'; + default : + return $this->wizzard_step90($content,$sel_options,$readonlys,$preserv); + } + } + // init step90 + else + { + $content['msg'] = $this->steps['wizzard_step90']; + $content['step'] = 'wizzard_step90'; + $preserv = $content; + unset ($preserv['button']); + $GLOBALS['egw']->js->set_onload("disable_button('exec[button][next]');"); + return 'importexport.wizzard_choosename'; + } + + + } + + function wizzard_finish(&$content) + { + if(self::_debug) error_log('importexport.uidefinitions::wizzard_finish->$content '.print_r($content,true)); + $bodefinitions = new bodefinitions(); + $bodefinitions->save($content); + // This message is displayed if browser cant close window + $content['msg'] = lang('ImportExport wizard finished successfully!'); + $content['closewindow'] = true; + return 'importexport.wizzard_close'; + } + + function import_definition($content='') + { + $bodefinitions = new bodefinitions(); + if (is_array($content)) + { + $bodefinitions->import($content['import_file']); + // TODO make redirect here! + return $this->index(); + } + else + { + $etpl =& new etemplate(IMPORTEXPORT_APP.'.import_definition'); + return $etpl->exec(IMPORTEXPORT_APP.'.uidefinitions.import_definition',$content,array(),$readonlys,$preserv); + } + } +} diff --git a/importexport/inc/class.uiexport.inc.php b/importexport/inc/class.uiexport.inc.php new file mode 100644 index 0000000000..2d04aaed54 --- /dev/null +++ b/importexport/inc/class.uiexport.inc.php @@ -0,0 +1,285 @@ +<?php +/** + * eGroupWare + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package importexport + * @link http://www.egroupware.org + * @author Cornelius Weiss <nelius@cwtech.de> + * @copyright Cornelius Weiss <nelius@cwtech.de> + * @version $Id: $ + */ + +require_once(EGW_INCLUDE_ROOT. '/etemplate/inc/class.etemplate.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.import_export_helper_functions.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.bodefinitions.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.definition.inc.php'); + + +/** + * userinterface for exports + * + */ +class uiexport { + const _appname = 'importexport'; + + public $public_functions = array( + 'export_dialog' => true, + 'download' => true, + ); + + private $js; + private $user; + + /** + * holds all export plugins from all apps + * + * @var array + */ + private $export_plugins; + + public function __construct() { + $this->js = $GLOBALS['egw']->js = is_object($GLOBALS['egw']->js) ? $GLOBALS['egw']->js : CreateObject('phpgwapi.javascript'); + $this->user = $GLOBALS['egw_info']['user']['user_id']; + $this->export_plugins = import_export_helper_functions::get_plugins('all','export'); + $GLOBALS['egw_info']['flags']['include_xajax'] = true; + + } + + public function export_dialog($_content=array()) { + $tabs = 'general_tab|selection_tab|options_tab|templates_tab'; + $content = array(); + $sel_options = array(); + $readonlys = array(); + $preserv = array(); + + if(empty($_content)) { + $et = new etemplate(self::_appname. '.export_dialog'); + $_appname = $_GET['appname']; + $_plugin = $_GET['plugin']; + $_selection = $_GET['selection']; + + // if appname is given and valid, list available plugins (if no plugin is given) + if (!empty($_appname) && $GLOBALS['egw']->acl->check('run',1,$_appname)) { + $content['appname'] = $_appname; + $preserv['appname'] = $_appname; + $readonlys['appname'] = true; + $this->js->set_onload("set_style_by_class('tr','select_appname','display','none');"); + + (array)$plugins = $this->export_plugins[$_appname]['export']; + if(isset($plugins[$_plugin])) { + $content['plugin'] = $_plugin; + $selected_plugin = $_plugin; + $this->js->set_onload("set_style_by_class('tr','select_plugin','display','none');"); + } + else { + $plugins_classnames = array_keys($plugins); + $selected_plugin = $plugins_classnames[0]; + $sel_options['plugin'] = $plugins; + } + + if (!empty($selected_plugin)) { + $plugin_object = new $selected_plugin; + + $content['plugin_description'] = $plugin_object->get_description(); + + // fill options tab + // TODO: do we need all options templates online? + // NO, we can manipulate the session array of template id on xajax request + // however, there might be other solutions... we solve this in 1.3 + $content['plugin_options_html'] = $plugin_object->get_options_etpl(); + + // fill selection tab + if ($_selection) { + $readonlys[$tabs]['selection_tab'] = true; + $content['selection'] = $_selection; + $preserv['selection'] = $_selection; + } + else { + // ToDo: I need to think abaout it... + // are selectors abstracted in the iface_egw_record_entity ? + // if so, we might not want to have html here ? + $content['plugin_selectors_html'] = $plugin_object->get_selectors_html(); + } + unset ($plugin_object); + } + } + // if no appname is supplied, list apps which can export + else { + (array)$apps = import_export_helper_functions::get_apps('export'); + $sel_options['appname'] = array('' => lang('Select one')) + array_combine($apps,$apps); + $this->js->set_onload("set_style_by_class('tr','select_plugin','display','none');"); + $content['plugin_selectors_html'] = $content['plugin_options_html'] = + lang('You need to select an app and format first!'); + } + + if (!$_selection) { + $this->js->set_onload(" + disable_button('exec[preview]'); + disable_button('exec[export]'); + "); + } + + // fill templates_tab + $sel_options['templates'] = array(); + $definitions = new bodefinitions(array( + 'type' => 'export', + 'application' => isset($content['appname']) ? $content['appname'] : '%' + )); + foreach ((array)$definitions->get_definitions() as $identifier) { + $definition = new definition($identifier); + if ($title = $definition->get_title()) { + $sel_options['templates'][$title] = $title; + } + unset($definition); + } + unset($definitions); + if (empty($sel_options['templates'])) { + $readonlys[$tabs]['templates_tab'] = true; + } + // disable preview box + $this->js->set_onload("set_style_by_class('tr','preview-box','display','none');"); + } + //xajax_eT_wrapper submit + elseif(class_exists('xajaxResponse')) + { + //error_log(__LINE__.__FILE__.'$_content: '.print_r($_content,true)); + $response =& new xajaxResponse(); + + $definition = new definition(); + $definition->definition_id = $_content['definition_id'] ? $_content['definition_id'] : ''; + $definition->name = $_content['name'] ? $_content['name'] : ''; + $definition->application = $_content['appname']; + $definition->plugin = $_content['plugin']; + $definition->type = 'export'; + $definition->allowed_users = $_content['allowed_users'] ? $_content['allowed_users'] : $this->user; + $definition->owner = $_content['owner'] ? $_content['owner'] : $this->user; + + //$definition->options = parse(...) + $definition->options = array( + 'selection' => $_content['selection'], + ); + $tmpfname = tempnam('/tmp','export'); + $file = fopen($tmpfname, "w+"); + if (! $charset = $definition->options['charset']) { + $charset = $GLOBALS['egw']->translation->charset(); + } + $plugin_object = new $_content['plugin']; + $plugin_object->export($file, $charset, $definition); + + if($_content['export'] == 'pressed') { + fclose($file); + $response->addScript("xajax_eT_wrapper();"); + $response->addScript("opener.location.href='". $GLOBALS['egw']->link('/index.php','menuaction=importexport.uiexport.download&_filename='. $tmpfname.'&_appname='. $definition->application). "&_suffix=". $plugin_object->get_filesuffix(). "';"); + $response->addScript('window.setTimeout("window.close();", 100);'); + return $response->getXML(); + } + elseif($_content['preview'] == 'pressed') { + fseek($file, 0); + $item_count = 1; + $preview = ''; + $search = array('[\016]','[\017]', + '[\020]','[\021]','[\022]','[\023]','[\024]','[\025]','[\026]','[\027]', + '[\030]','[\031]','[\032]','[\033]','[\034]','[\035]','[\036]','[\037]'); + $replace = $preview = ''; + + while(!feof($file) && $item_count < 10) { + $preview .= preg_replace($search,$replace,fgets($file,1024)); + $item_count++; + } + + fclose($file); + unlink($tmpfname); + $response->addAssign('exec[preview-box]','innerHTML',$preview); + $response->addAssign('divPoweredBy','style.display','none'); + $response->addAssign('exec[preview-box]','style.display','inline'); + $response->addAssign('exec[preview-box-buttons]','style.display','inline'); + + $response->addScript("xajax_eT_wrapper();"); + return $response->getXML(); + } + //nothing else expected! + throw new Exception('Error: unexpected submit in export_dialog!'); + } + return $et->exec(self::_appname. '.uiexport.export_dialog',$content,$sel_options,$readonlys,$preserv,2); + } + + public function ajax_get_plugins($_appname) { + $response = new xajaxResponse(); + if (!$_appname) { + $response->addScript("set_style_by_class('tr','select_plugin','display','none');"); + return $response->getXML(); + } + + (array)$plugins = import_export_helper_functions::get_plugins($_appname,'export'); + $sel_options['plugin'] = ''; + foreach ($plugins[$_appname]['export'] as $plugin => $plugin_name) { + if (!$selected_plugin) $selected_plugin = $plugin; + $sel_options['plugin'] .= '<option value="'. $plugin. '" >'. $plugin_name. '</option>'; + } + + $this->ajax_get_plugin_description($selected_plugin,$response); + $response->addAssign('exec[plugin]','innerHTML',$sel_options['plugin']); + $response->addScript("set_style_by_class('tr','select_plugin','display','table-row');"); + return $response->getXML(); + } + + public function ajax_get_plugin_description($_plugin,$_response=false) { + $response = $_response ? $_response : new xajaxResponse(); + if (!$_plugin) return $response->getXML(); + + $plugin_object = new $_plugin; + if (is_a($plugin_object, 'iface_export_plugin')) { + $description = $plugin_object->get_description(); + $_response->addAssign('plugin_description','style.width','300px'); + $_response->addAssign('plugin_description','innerHTML',$description); + } + unset ($plugin_object); + + return $_response ? '' : $response->getXML(); + } + + public function ajax_get_plugin_options($_plugin,$_response=false) { + $response = $_response ? $_response : new xajaxResponse(); + if (!$_plugin) return $response->getXML(); + + $plugin_object = new $_plugin; + if (is_a($plugin_object, 'iface_export_plugin')) { + $options = $plugin_object->get_options_etpl(); + } + unset ($plugin_object); + + return $_response ? '' : $response->getXML(); + } + + /** + * downloads file to client and deletes it. + * + * @param sting $_tmpfname + * @todo we need a suffix atibute in plugins e.g. .csv + */ + public function download($_tmpfname = '') { + $tmpfname = $_tmpfname ? $_tmpfname : $_GET['_filename']; + if (!is_readable($tmpfname)) die(); + + $appname = $_GET['_appname']; + $nicefname = 'egw_export_'.$appname.'-'.date('y-m-d'); + + header('Content-type: application/text'); + header('Content-Disposition: attachment; filename="'.$nicefname.'.'.$_GET['_suffix'].'"'); + $file = fopen($tmpfname,'r'); + while(!feof($file)) + echo fgets($file,1024); + fclose($file); + + unlink($tmpfname); + } + + public function ajax_get_plugin_selectors() { + + } + + public function ajax_get_template($_name) { + + } +} // end class uiexport \ No newline at end of file diff --git a/importexport/setup/default_records.inc.php b/importexport/setup/default_records.inc.php new file mode 100644 index 0000000000..50f1a13242 --- /dev/null +++ b/importexport/setup/default_records.inc.php @@ -0,0 +1,53 @@ +<?php + /** + * eGroupWare - importexport + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package importexport + * @link http://www.egroupware.org + * @author Cornelius Weiss <nelius@cwtech.de> + * @version $Id: $ + */ + + $definition_table = 'egw_importexport_definitions'; + + // Add two rooms to give user an idea of what resources is... + $plugin_options = serialize(array( + 'fieldsep' => ',', + 'charset' => 'ISO-8859-1', + 'addressbook' => 'n', + 'owner' => 5, + 'field_mapping' => array( + 0 => '#kundennummer', + 1 => 'n_given', + 2 => 'n_family', + 3 => 'adr_one_street', + 4 => 'adr_one_countryname', + 5 => 'adr_one_postalcode', + 6 => 'adr_one_locality', + 7 => 'tel_work', + 8 => '', + 9 => '', + 10 => '', + 11 => '', + ), + 'field_tanslation' => array(), + 'has_header_line' => false, + 'max' => false, + 'conditions' => array( + 0 => array( + 'type' => 0, // exists + 'string' => '#kundennummer', + 'true' => array( + 'action' => 1, // update + 'last' => true, + ), + 'false' => array( + 'action' => 2, // insert + 'last' => true, + ), + + )), + )); + $oProc->query("INSERT INTO {$definition_table } (name,application,plugin,type,allowed_users,plugin_options) VALUES ( 'oelheld','addressbook','addressbook_csv_import','import','5','$plugin_options')"); + diff --git a/importexport/setup/etemplates.inc.php b/importexport/setup/etemplates.inc.php new file mode 100644 index 0000000000..5df6d0689e --- /dev/null +++ b/importexport/setup/etemplates.inc.php @@ -0,0 +1,67 @@ +<?php + /** + * eGroupWare - eTemplates for Application importexport + * http://www.egroupware.org + * generated by soetemplate::dump4setup() 2006-11-10 16:25 + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package importexport + * @subpackage setup + * @version $Id: class.soetemplate.inc.php 22413 2006-09-12 07:32:34Z ralfbecker $ + */ + +$templ_version=1; + +$templ_data[] = array('name' => 'importexport.definition_index','template' => '','lang' => '','group' => '0','version' => '0.0.1','data' => 'a:1:{i:0;a:5:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:1:{s:2:"h1";s:6:",!@msg";}i:1;a:1:{s:1:"A";a:4:{s:4:"span";s:13:"all,redItalic";s:7:"no_lang";s:1:"1";s:4:"name";s:3:"msg";s:4:"type";s:5:"label";}}i:2;a:1:{s:1:"A";a:4:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:2:{s:2:"c1";s:2:"th";s:2:"c2";s:7:"row,top";}i:1;a:6:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:5:"label";s:4:"Type";s:4:"span";s:11:",lr_padding";}s:1:"B";a:3:{s:4:"type";s:5:"label";s:5:"label";s:4:"Name";s:4:"span";s:11:",lr_padding";}s:1:"C";a:3:{s:4:"type";s:5:"label";s:5:"label";s:11:"Application";s:4:"span";s:11:",lr_padding";}s:1:"D";a:4:{s:5:"align";s:6:"center";s:4:"type";s:5:"label";s:5:"label";s:13:"Allowed users";s:4:"span";s:11:",lr_padding";}s:1:"E";a:5:{s:5:"label";s:3:"Add";s:5:"align";s:6:"center";s:4:"type";s:6:"button";s:4:"span";s:11:",lr_padding";s:7:"onclick";s:213:"window.open(egw::link(\'/index.php\',\'menuaction=importexport.uidefinitions.wizzard\'),\'\',\'dependent=yes,width=400,height=400,location=no,menubar=no,toolbar=no,scrollbars=yes,status=yes\'); return false; return false;";}s:1:"F";a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";i:1;a:6:{s:5:"label";s:6:"Delete";s:4:"name";s:15:"delete_selected";s:4:"type";s:6:"button";s:4:"help";s:31:"delete ALL selected definitions";s:4:"size";s:6:"delete";s:7:"onclick";s:65:"return confirm(\'Do you really want to DELETE this definitions?\');";}i:2;a:5:{s:5:"label";s:6:"Export";s:4:"name";s:15:"export_selected";s:4:"type";s:6:"button";s:4:"help";s:31:"export ALL selected definitions";s:4:"size";s:10:"fileexport";}}}i:2;a:6:{s:1:"A";a:4:{s:7:"no_lang";s:1:"1";s:4:"type";s:5:"image";s:4:"span";s:11:",lr_padding";s:4:"name";s:12:"${row}[type]";}s:1:"B";a:4:{s:7:"no_lang";s:1:"1";s:4:"name";s:12:"${row}[name]";s:4:"type";s:5:"label";s:4:"span";s:11:",lr_padding";}s:1:"C";a:4:{s:7:"no_lang";s:1:"1";s:4:"name";s:19:"${row}[application]";s:4:"type";s:5:"label";s:4:"span";s:11:",lr_padding";}s:1:"D";a:6:{s:7:"no_lang";s:1:"1";s:4:"type";s:14:"select-account";s:4:"span";s:11:",lr_padding";s:8:"readonly";s:1:"1";s:4:"name";s:21:"${row}[allowed_users]";s:4:"size";s:1:"5";}s:1:"E";a:5:{s:5:"align";s:6:"center";s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";i:1;a:4:{s:5:"label";s:4:"Edit";s:4:"type";s:6:"button";s:4:"size";s:4:"edit";s:7:"onclick";s:237:"window.open(egw::link(\'/index.php\',\'menuaction=importexport.uidefinitions.edit&definition=$row_cont[name]\'),\'\',\'dependent=yes,width=400,height=400,location=no,menubar=no,toolbar=no,scrollbars=yes,status=yes\'); return false; return false;";}i:2;a:6:{s:5:"label";s:6:"Delete";s:7:"onclick";s:41:"return confirm(\'Delete this definition\');";s:4:"name";s:32:"delete[$row_cont[definition_id]]";s:4:"type";s:6:"button";s:4:"size";s:6:"delete";s:4:"help";s:21:"Delete this eTemplate";}}s:1:"F";a:4:{s:5:"align";s:6:"center";s:4:"name";s:34:"selected[$row_cont[definition_id]]";s:4:"type";s:8:"checkbox";s:4:"help";s:34:"select this eTemplate to delete it";}}}s:4:"cols";i:6;s:4:"rows";i:2;}}}s:4:"cols";i:1;s:4:"rows";i:2;s:4:"size";s:4:"100%";}}','size' => '100%','style' => '.redItalic { color:red; font-style:italic;} td.lr_padding { padding-left: 5px; padding-right: 5px; }','modified' => '1145972373',); + +$templ_data[] = array('name' => 'importexport.export_dialog','template' => '','lang' => '','group' => '0','version' => '','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:7:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:4:"name";s:3:"msg";}}i:2;a:1:{s:1:"A";a:3:{s:4:"type";s:3:"tab";s:5:"label";s:35:"General|Selection|Options|Templates";s:4:"name";s:51:"general_tab|selection_tab|options_tab|templates_tab";}}i:3;a:1:{s:1:"A";a:3:{s:4:"type";s:8:"checkbox";s:5:"label";s:16:"Save as template";s:4:"name";s:16:"save_as_template";}}i:4;a:1:{s:1:"A";a:5:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";s:4:"span";s:3:"all";i:1;a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";i:1;a:4:{s:4:"type";s:6:"button";s:5:"label";s:6:"Export";s:4:"name";s:6:"export";s:7:"onclick";s:36:"xajax_eT_wrapper(this);return false;";}i:2;a:4:{s:4:"type";s:6:"button";s:5:"label";s:7:"Preview";s:4:"name";s:7:"preview";s:7:"onclick";s:36:"xajax_eT_wrapper(this);return false;";}}i:2;a:5:{s:4:"type";s:6:"button";s:5:"label";s:6:"Cancel";s:5:"align";s:5:"right";s:4:"name";s:6:"cancel";s:7:"onclick";s:29:"window.close(); return false;";}}}i:5;a:1:{s:1:"A";a:6:{s:4:"type";s:3:"box";s:4:"size";s:1:"1";s:4:"name";s:11:"preview-box";s:6:"needed";s:1:"1";i:1;a:1:{s:4:"type";s:5:"label";}s:4:"span";s:12:",preview-box";}}i:6;a:1:{s:1:"A";a:7:{s:4:"type";s:3:"box";s:4:"size";s:1:"1";s:4:"span";s:20:",preview-box-buttons";s:4:"name";s:19:"preview-box-buttons";i:1;a:4:{s:4:"type";s:6:"button";s:5:"label";s:2:"OK";s:5:"align";s:6:"center";s:7:"onclick";s:230:"document.getElementById(\'divPoweredBy\').style.display=\'block\'; document.getElementById(form::name(\'preview-box\')).style.display=\'none\'; document.getElementById(form::name(\'preview-box-buttons\')).style.display=\'none\'; return false;";}s:6:"needed";s:1:"1";s:5:"align";s:6:"center";}}}s:4:"rows";i:6;s:4:"cols";i:1;s:4:"size";s:4:"100%";s:7:"options";a:1:{i:0;s:4:"100%";}}}','size' => '100%','style' => '.preview-box { + position: absolute; + top: 0px; + left: 0px; + width: 400px; + height: 360px; + overflow: scroll; + background-color: white; + z-index: 999; + display: none; +} +.preview-box-buttons { + position: absolute; + top: 365px; + left: 0px; + width: 400px; + height: 20px; + + z-index: 999; + display: none; +}','modified' => '1158220473',); + +$templ_data[] = array('name' => 'importexport.export_dialog.general_tab','template' => '','lang' => '','group' => '0','version' => '','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:2:{i:0;a:1:{s:2:"c1";s:4:",top";}i:1;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"image";s:4:"name";s:6:"export";}s:1:"B";a:2:{s:4:"type";s:8:"template";s:4:"name";s:46:"importexport.export_dialog.general_tab_content";}}}s:4:"rows";i:1;s:4:"cols";i:2;s:4:"size";s:6:",200px";s:7:"options";a:1:{i:1;s:5:"200px";}}}','size' => ',200px','style' => '','modified' => '1158223670',); + +$templ_data[] = array('name' => 'importexport.export_dialog.general_tab_content','template' => '','lang' => '','group' => '0','version' => '','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:5:{i:0;a:2:{s:2:"c2";s:14:"select_appname";s:2:"c3";s:13:"select_plugin";}i:1;a:2:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:4:"span";s:3:"all";s:5:"label";s:14:"some nice text";}s:1:"B";a:1:{s:4:"type";s:5:"label";}}i:2;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:18:"Select application";}s:1:"B";a:4:{s:4:"type";s:6:"select";s:7:"no_lang";s:1:"1";s:4:"name";s:7:"appname";s:8:"onchange";s:69:"xajax_doXMLHTTP(\'importexport.uiexport.ajax_get_plugins\',this.value);";}}i:3;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:13:"Select format";}s:1:"B";a:4:{s:4:"type";s:6:"select";s:7:"no_lang";s:1:"1";s:4:"name";s:6:"plugin";s:8:"onchange";s:76:"xajax_doXMLHTTP(\'importexport.uiexport.ajax_get_plugin_options\',this.value);";}}i:4;a:2:{s:1:"A";a:6:{s:4:"type";s:3:"box";s:4:"span";s:3:"all";s:4:"name";s:18:"plugin_description";s:6:"needed";s:1:"1";s:4:"size";s:1:"1";i:1;a:7:{s:4:"type";s:5:"label";s:4:"span";s:3:"all";s:6:"needed";s:1:"1";i:1;a:4:{s:4:"type";s:5:"label";s:4:"span";s:3:"all";s:6:"needed";s:1:"1";s:5:"label";s:11:"Description";}i:2;a:2:{s:4:"type";s:5:"label";s:4:"name";s:18:"plugin_description";}s:4:"name";s:18:"plugin_description";s:7:"no_lang";s:1:"1";}}s:1:"B";a:1:{s:4:"type";s:5:"label";}}}s:4:"rows";i:4;s:4:"cols";i:2;}}','size' => '','style' => '','modified' => '1158223021',); + +$templ_data[] = array('name' => 'importexport.export_dialog.options_tab','template' => '','lang' => '','group' => '0','version' => '','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:2:{i:0;a:1:{s:2:"c1";s:4:",top";}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:4:"html";s:4:"name";s:19:"plugin_options_html";s:7:"no_lang";s:1:"1";}}}s:4:"rows";i:1;s:4:"cols";i:1;s:4:"size";s:6:",200px";s:7:"options";a:1:{i:1;s:5:"200px";}}}','size' => ',200px','style' => '','modified' => '1158223824',); + +$templ_data[] = array('name' => 'importexport.export_dialog.selection_tab','template' => '','lang' => '','group' => '0','version' => '','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:2:{i:0;a:1:{s:2:"c1";s:4:",top";}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:4:"html";s:4:"name";s:21:"plugin_selectors_html";s:7:"no_lang";s:1:"1";}}}s:4:"rows";i:1;s:4:"cols";i:1;s:4:"size";s:6:",200px";s:7:"options";a:1:{i:1;s:5:"200px";}}}','size' => ',200px','style' => '','modified' => '1158223796',); + +$templ_data[] = array('name' => 'importexport.export_dialog.templates_tab','template' => '','lang' => '','group' => '0','version' => '','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:6:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:17:"Select a template";}}i:2;a:1:{s:1:"A";a:4:{s:4:"type";s:6:"select";s:7:"no_lang";s:1:"1";s:4:"name";s:9:"templates";s:4:"size";s:1:"5";}}i:3;a:1:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:11:"Description";}}i:4;a:1:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:4:"name";s:11:"description";}}i:5;a:1:{s:1:"A";a:3:{s:4:"type";s:6:"button";s:5:"label";s:4:"Load";s:4:"name";s:4:"load";}}}s:4:"rows";i:5;s:4:"cols";i:1;s:4:"size";s:6:",200px";s:7:"options";a:1:{i:1;s:5:"200px";}}}','size' => ',200px','style' => '','modified' => '1158223945',); + +$templ_data[] = array('name' => 'importexport.import_definition','template' => '','lang' => '','group' => '0','version' => '0.0.1','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:4:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:92:"Import definitions (Attension: Existing definitions with equal names will be overwritten!!!)";}}i:2;a:1:{s:1:"A";a:2:{s:4:"type";s:4:"file";s:4:"name";s:11:"import_file";}}i:3;a:1:{s:1:"A";a:3:{s:4:"type";s:6:"button";s:5:"label";s:6:"Import";s:4:"name";s:6:"import";}}}s:4:"rows";i:3;s:4:"cols";i:1;}}','size' => '','style' => '','modified' => '1150533844',); + +$templ_data[] = array('name' => 'importexport.wizzardbox','template' => '','lang' => '','group' => '0','version' => '0.0.1','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:3:{s:2:"c2";s:7:",bottom";s:2:"c1";s:4:",top";s:1:"A";s:4:"100%";}i:1;a:1:{s:1:"A";a:5:{s:4:"type";s:4:"hbox";s:7:"no_lang";s:1:"1";s:4:"size";s:1:"2";i:1;a:4:{s:4:"type";s:4:"hbox";s:7:"no_lang";s:1:"1";s:4:"size";s:1:"1";i:1;a:3:{s:4:"type";s:5:"image";s:7:"no_lang";s:1:"1";s:4:"name";s:12:"importexport";}}i:2;a:4:{s:4:"type";s:8:"groupbox";s:4:"size";s:1:"1";i:1;a:2:{s:4:"type";s:8:"template";s:4:"name";s:15:"wizzard_content";}s:4:"span";s:16:",wizzard_content";}}}i:2;a:1:{s:1:"A";a:6:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";s:4:"span";s:3:"all";i:1;a:5:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"3";i:1;a:4:{s:4:"type";s:6:"button";s:4:"name";s:16:"button[previous]";s:5:"label";s:8:"previous";s:7:"onclick";s:37:"xajax_eT_wrapper(this); return false;";}i:2;a:4:{s:4:"type";s:6:"button";s:4:"name";s:12:"button[next]";s:5:"label";s:4:"next";s:7:"onclick";s:37:"xajax_eT_wrapper(this); return false;";}i:3;a:4:{s:4:"type";s:6:"button";s:4:"name";s:14:"button[finish]";s:5:"label";s:6:"finish";s:7:"onclick";s:37:"xajax_eT_wrapper(this); return false;";}}i:2;a:5:{s:4:"type";s:6:"button";s:4:"name";s:14:"button[cancel]";s:5:"label";s:6:"cancel";s:7:"onclick";s:29:"window.close(); return false;";s:5:"align";s:5:"right";}s:5:"align";s:5:"right";}}}s:4:"rows";i:2;s:4:"cols";i:1;s:4:"size";s:4:",400";s:7:"options";a:1:{i:1;s:3:"400";}}}','size' => ',400','style' => '.wizzard_content fieldset { + height: 347px; + width: 250px; + max-height:347px; + overflow:auto; +}','modified' => '1145975378',); + +$templ_data[] = array('name' => 'importexport.wizzard_chooseallowedusers','template' => '','lang' => '','group' => '0','version' => '0.0.1','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:7:"no_lang";s:1:"1";s:4:"name";s:3:"msg";}}i:2;a:1:{s:1:"A";a:3:{s:4:"type";s:14:"select-account";s:4:"name";s:13:"allowed_users";s:4:"size";s:8:"5,groups";}}}s:4:"rows";i:2;s:4:"cols";i:1;}}','size' => '','style' => '','modified' => '1146312041',); + +$templ_data[] = array('name' => 'importexport.wizzard_chooseapp','template' => '','lang' => '','group' => '0','version' => '0.0.1','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:7:"no_lang";s:1:"1";s:4:"name";s:3:"msg";}}i:2;a:1:{s:1:"A";a:3:{s:4:"type";s:6:"select";s:7:"no_lang";s:1:"1";s:4:"name";s:11:"application";}}}s:4:"rows";i:2;s:4:"cols";i:1;}}','size' => '','style' => '','modified' => '1145976439',); + +$templ_data[] = array('name' => 'importexport.wizzard_choosename','template' => '','lang' => '','group' => '0','version' => '0.0.1','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:7:"no_lang";s:1:"1";s:4:"name";s:3:"msg";}}i:2;a:1:{s:1:"A";a:2:{s:4:"type";s:4:"text";s:4:"name";s:4:"name";}}}s:4:"rows";i:2;s:4:"cols";i:1;}}','size' => '','style' => '','modified' => '1146317310',); + +$templ_data[] = array('name' => 'importexport.wizzard_chooseplugin','template' => '','lang' => '','group' => '0','version' => '0.0.1','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:7:"no_lang";s:1:"1";s:4:"name";s:3:"msg";}}i:2;a:1:{s:1:"A";a:3:{s:4:"type";s:6:"select";s:7:"no_lang";s:1:"1";s:4:"name";s:6:"plugin";}}}s:4:"rows";i:2;s:4:"cols";i:1;}}','size' => '','style' => '','modified' => '1145976573',); + +$templ_data[] = array('name' => 'importexport.wizzard_close','template' => '','lang' => '','group' => '0','version' => '0.0.1','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:2:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:7:"no_lang";s:1:"1";s:4:"name";s:3:"msg";}}}s:4:"rows";i:1;s:4:"cols";i:1;}}','size' => '','style' => '','modified' => '1146648383',); + diff --git a/importexport/setup/phpgw_de.lang b/importexport/setup/phpgw_de.lang new file mode 100644 index 0000000000..8787d2781a --- /dev/null +++ b/importexport/setup/phpgw_de.lang @@ -0,0 +1,7 @@ +choose a name for this definition importexport de Wählen sie einen Namen für diese Definition. +choose a plugin importexport de Wählen sie ein Plugin. +choose an application importexport de Wählen sei eine Anwendung. +finish importexport de Fertig +next importexport de Weiter +previous importexport de Zurück +which useres are allowed for this definition importexport de Welche Benutzer dürden diese Definition verwenden? diff --git a/importexport/setup/phpgw_en.lang b/importexport/setup/phpgw_en.lang new file mode 100644 index 0000000000..865235a5e8 --- /dev/null +++ b/importexport/setup/phpgw_en.lang @@ -0,0 +1,8 @@ +allowed users importexport en Allowed users +choose a name for this definition importexport en Choose a name for this definition +choose a plugin importexport en Choose a plugin +choose an application importexport en Choose an application +finish importexport en finish +next importexport en next +previous importexport en previous +which useres are allowed for this definition importexport en Which useres are allowed for this definition diff --git a/importexport/setup/setup.inc.php b/importexport/setup/setup.inc.php new file mode 100644 index 0000000000..557a62469b --- /dev/null +++ b/importexport/setup/setup.inc.php @@ -0,0 +1,46 @@ +<?php + /** + * importexport + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package importexport + * @author Cornelius Weiss <nelius@cwtech.de> + * @version $Id: $ + */ + + $setup_info['importexport']['name'] = 'importexport'; + $setup_info['importexport']['version'] = '0.002'; + $setup_info['importexport']['app_order'] = 2; + $setup_info['importexport']['enable'] = 2; + $setup_info['importexport']['tables'] = array('egw_importexport_definitions'); + + $setup_info['importexport']['author'] = + $setup_info['importexport']['maintainer'] = array( + 'name' => 'Cornelius Weiss', + 'email' => 'nelius@cwtech.de' + ); + $setup_info['importexport']['license'] = 'GPL'; + $setup_info['importexport']['description'] = + ''; + $setup_info['importexport']['note'] = + ''; + + /* The hooks this app includes, needed for hooks registration */ + //$setup_info['importexport']['hooks']['preferences'] = 'importexport'.'.admin_prefs_sidebox_hooks.all_hooks'; + //$setup_info['importexport']['hooks']['settings'] = 'importexport'.'.admin_prefs_sidebox_hooks.settings'; + $setup_info['importexport']['hooks']['admin'] = 'importexport'.'.importexport_admin_prefs_sidebox_hooks.all_hooks'; + $setup_info['importexport']['hooks']['sidebox_menu'] = 'importexport'.'.importexport_admin_prefs_sidebox_hooks.all_hooks'; + //$setup_info['importexport']['hooks']['search_link'] = 'importexport'.'.bomyterra.search_link'; + + /* Dependencies for this app to work */ + $setup_info['importexport']['depends'][] = array( + 'appname' => 'phpgwapi', + 'versions' => Array('1.2','1.3') + ); + $setup_info['importexport']['depends'][] = array( + 'appname' => 'etemplate', + 'versions' => Array('1.2','1.3') + ); + + + diff --git a/importexport/setup/tables_current.inc.php b/importexport/setup/tables_current.inc.php new file mode 100644 index 0000000000..dca6a3388d --- /dev/null +++ b/importexport/setup/tables_current.inc.php @@ -0,0 +1,33 @@ +<?php + /**************************************************************************\ + * eGroupWare - Setup * + * http://www.eGroupWare.org * + * Created by eTemplates DB-Tools written by ralfbecker@outdoor-training.de * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + \**************************************************************************/ + + /* $Id: class.db_tools.inc.php,v 1.33 2005/12/19 04:27:19 ralfbecker Exp $ */ + + + $phpgw_baseline = array( + 'egw_importexport_definitions' => array( + 'fd' => array( + 'definition_id' => array('type' => 'auto'), + 'name' => array('type' => 'varchar','precision' => '255'), + 'application' => array('type' => 'varchar','precision' => '50'), + 'plugin' => array('type' => 'varchar','precision' => '100'), + 'type' => array('type' => 'varchar','precision' => '20'), + 'allowed_users' => array('type' => 'varchar','precision' => '255'), + 'plugin_options' => array('type' => 'longtext'), + 'owner' => array('type' => 'int','precision' => '20') + ), + 'pk' => array('definition_id'), + 'fk' => array(), + 'ix' => array('name'), + 'uc' => array('name') + ) + ); diff --git a/importexport/templates/default/export_dialog.old.xet b/importexport/templates/default/export_dialog.old.xet new file mode 100644 index 0000000000..bcaf38418d --- /dev/null +++ b/importexport/templates/default/export_dialog.old.xet @@ -0,0 +1,120 @@ +<?xml version="1.0"?> +<!-- $Id$ --> +<overlay> + <template id="importexport.export_dialog.general_tab_content" template="" lang="" group="0" version=""> + <grid> + <columns> + <column/> + <column/> + </columns> + <rows> + <row> + <description span="all" value="some nice text"/> + </row> + <row class="select_appname" disabled="@appname"> + <description value="Select application"/> + <menulist> + <menupopup no_lang="1" id="appname"/> + </menulist> + </row> + <row class="select_plugin"> + <description value="Select format"/> + <menulist> + <menupopup no_lang="1" id="plugin"/> + </menulist> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog.general_tab" template="" lang="" group="0" version=""> + <grid> + <columns> + <column/> + </columns> + <rows> + <row valign="top"> + <hbox> + <image options="export_dialog"/> + <template id="importexport.export_dialog.general_tab_content"/> + </hbox> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog.selection_tab" template="" lang="" group="0" version=""> + <grid> + <columns> + <column/> + </columns> + <rows> + <row> + <description/> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog.options_tab" template="" lang="" group="0" version=""> + <grid> + <columns> + <column/> + </columns> + <rows> + <row> + <description/> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog.templates_tab" template="" lang="" group="0" version=""> + <grid> + <columns> + <column/> + </columns> + <rows> + <row> + <description/> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog" template="" lang="" group="0" version=""> + <grid> + <columns> + <column/> + </columns> + <rows> + <row> + <description id="msg"/> + </row> + <row> + <tabbox> + <tabs> + <tab label="General" statustext=""/> + <tab label="Selection" statustext=""/> + <tab label="Options" statustext=""/> + <tab label="Templates" statustext=""/> + </tabs> + <tabpanels> + <template id="importexport.export_dialog.general_tab"/> + <template id="importexport.export_dialog.selection_tab"/> + <template id="importexport.export_dialog.options_tab"/> + <template id="importexport.export_dialog.templates_tab"/> + </tabpanels> + </tabbox> + </row> + <row> + <checkbox label="Save as template" id="save_as_template"/> + </row> + <row> + <hbox span="all"> + <hbox> + <button label="Export" id="export"/> + <button label="Preview" id="preview"/> + </hbox> + <button label="Cancel" align="right" id="cancel"/> + </hbox> + </row> + </rows> + </grid> + </template> +</overlay> \ No newline at end of file diff --git a/importexport/templates/default/export_dialog.xet b/importexport/templates/default/export_dialog.xet new file mode 100644 index 0000000000..326c6f4ea4 --- /dev/null +++ b/importexport/templates/default/export_dialog.xet @@ -0,0 +1,169 @@ +<?xml version="1.0"?> +<!-- $Id$ --> +<overlay> + <template id="importexport.export_dialog.general_tab_content" template="" lang="" group="0" version=""> + <grid height="200px"> + <columns> + <column/> + <column/> + </columns> + <rows> + <row> + <description span="all" value="some nice text"/> + </row> + <row class="select_appname"> + <description value="Select application"/> + <menulist> + <menupopup no_lang="1" id="appname" onchange="xajax_doXMLHTTP('importexport.uiexport.ajax_get_plugins',this.value);"/> + </menulist> + </row> + <row class="select_plugin"> + <description value="Select format"/> + <menulist> + <menupopup no_lang="1" id="plugin" onchange="xajax_doXMLHTTP('importexport.uiexport.ajax_get_plugin_options',this.value);"/> + </menulist> + </row> + <row> + <box span="all" id="plugin_description" needed="1"> + <description span="all" needed="1" id="plugin_description" no_lang="1"/> + </box> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog.general_tab" template="" lang="" group="0" version=""> + <grid> + <columns> + <column/> + <column/> + </columns> + <rows> + <row valign="top"> + <image src="export"/> + <template id="importexport.export_dialog.general_tab_content"/> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog.selection_tab" template="" lang="" group="0" version=""> + <grid height="200px"> + <columns> + <column/> + </columns> + <rows> + <row valign="top"> + <html id="plugin_selectors_html" no_lang="1"/> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog.options_tab" template="" lang="" group="0" version=""> + <grid height="200px"> + <columns> + <column/> + </columns> + <rows> + <row valign="top"> + <html id="plugin_options_html" no_lang="1"/> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog.templates_tab" template="" lang="" group="0" version=""> + <grid> + <columns> + <column/> + </columns> + <rows> + <row> + <description value="Select a template"/> + </row> + <row> + <listbox no_lang="1" id="templates" rows="5"/> + </row> + <row> + <description value="Description"/> + </row> + <row> + <description id="description"/> + </row> + <row> + <button label="Load" id="load"/> + </row> + </rows> + </grid> + </template> + <template id="importexport.export_dialog" template="" lang="" group="0" version=""> + <grid width="100%"> + <columns> + <column/> + </columns> + <rows> + <row> + <description id="msg"/> + </row> + <row> + <tabbox> + <tabs> + <tab label="General" statustext=""/> + <tab label="Selection" statustext=""/> + <tab label="Options" statustext=""/> + <tab label="Templates" statustext=""/> + </tabs> + <tabpanels> + <template id="importexport.export_dialog.general_tab"/> + <template id="importexport.export_dialog.selection_tab"/> + <template id="importexport.export_dialog.options_tab"/> + <template id="importexport.export_dialog.templates_tab"/> + </tabpanels> + </tabbox> + </row> + <row> + <checkbox label="Save as template" id="save_as_template"/> + </row> + <row> + <hbox span="all"> + <hbox> + <button label="Export" id="export" onclick="xajax_eT_wrapper(this);return false;"/> + <button label="Preview" id="preview" onclick="xajax_eT_wrapper(this);return false;"/> + </hbox> + <button label="Cancel" align="right" id="cancel" onclick="window.close(); return false;"/> + </hbox> + </row> + <row> + <box id="preview-box" needed="1" class="preview-box"> + <description/> + </box> + </row> + <row> + <box class="preview-box-buttons" id="preview-box-buttons" needed="1" align="center"> + <button label="OK" align="center" onclick="document.getElementById('divPoweredBy').style.display='block'; document.getElementById(form::name('preview-box')).style.display='none'; document.getElementById(form::name('preview-box-buttons')).style.display='none'; return false;"/> + </box> + </row> + </rows> + </grid> + <styles> + .preview-box { + position: absolute; + top: 0px; + left: 0px; + width: 400px; + height: 360px; + overflow: scroll; + background-color: white; + z-index: 999; + display: none; +} +.preview-box-buttons { + position: absolute; + top: 365px; + left: 0px; + width: 400px; + height: 20px; + + z-index: 999; + display: none; +} + </styles> + </template> +</overlay> \ No newline at end of file diff --git a/importexport/templates/default/images/export.png b/importexport/templates/default/images/export.png new file mode 100755 index 0000000000000000000000000000000000000000..d3b500a83bd1530c6c33b55c4402add77e1ebf32 GIT binary patch literal 1593 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 literal 0 HcmV?d00001 diff --git a/importexport/templates/default/images/fileexport.png b/importexport/templates/default/images/fileexport.png new file mode 100644 index 0000000000000000000000000000000000000000..381bfc05392cc6a760e17a0f3a7f9bfc8bcadaa6 GIT binary patch literal 626 zcmV-&0*(ENP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3labT3lag+-G2N4000McNliru(+C<D1Q<EP(7XTu0sl!v zK~#90rIWpCltC1RpWRIiL?I#=B1DTcrm?p(7h&y<*sPs}SKt+h#Mai0g;*L1F;Ngr zVgk$l<@=d4bI$CH#cr|*Y$LvKm{ZJoc;Dd+{HGch&D-srwt7R=5DADV&Ur)>5s!0< z_ksSP<m9B++5Pgmb4l6W{@6Y`?dSfe%I`ny=I5{8<<94WJWpQ_j`NF`Z}QsO(=C7| zfD@&1fe?(n?|<kGN^;I~EmyTC0j*YJ3t$02#1n!MmzUVMf0wnpE3DtUGZ)~4(e3sa zkH^daUh%;wD&fiFO>)YloH*;AlX4=ZOwJjR!2Y*i6h%#pX$EjE5H!;L-DUsBDZwH^ zBdU?0M(qNlVL@?TA`+-;M@m-!UIIQC<8fl4)g+}BMkJacRgJ5JWJ(K&rqs1u2RNBT z*m$tYqlfF%wNO<a@0AdY5ESnx)%mo?(a&RIjMpQ0HGEJG56|#^GBhc>goL0t@rcYE zG$)T35U&h}6=snbjTj>_M$95+Ha#OwJl^~35yUCyqmo-UTFCP9)UsW1FtM*{h?@hb zYDZBxZr@y@*DF!A%Yh5PMen^QgaBLt)TL1LhxD3})s-b04W-eDfC2_6IU6|}qrm_v zOQr^9pJ{XRSzFauTx^kZA|*&EO)pNCDWyzR)l`)u#+~-uRsC1}1-gaal@pe+p8x;= M07*qoM6N<$f=Un=0{{R3 literal 0 HcmV?d00001 diff --git a/importexport/templates/default/images/fileimport.png b/importexport/templates/default/images/fileimport.png new file mode 100644 index 0000000000000000000000000000000000000000..32baf9c5493b7d38327b9f49909dae5ff68453ec GIT binary patch literal 851 zcmV-Z1FZasP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3labT3lag+-G2N4000McNliru(+C_AAvL#8!5IJm0^mtR zK~#9!y^~95TxAr;fA{7#6DJR1h{n9Crj3nh)hH4~x=?DQVwHesHzI{>R0=-8r4p-E z5Fd+z3$aokt5REqVi#5^#U`{gi<I=0%uJ^plO{8BZzeNy=ia&Z`@Z9%1r>4O#<M>E zKj;5A@PEwvA0L1D^Dn1nw?6*j9sdcq_miJDetU4{L?&C3q8rFtZy!7WH2!tRy8pGa zukRY`dZVU<P{E=b^Zn8D`}RE5W{*C6_q!uo`kKN}qm&|@_4MA4z8XJz=$)Sc1Hksl zxyei<a=I*Xq9}6Yk}n5-yeO-sK%z*bT2<nCk*uuL#B~GN^V!MehAoq$fCb<fV=w|@ zz*|`4o_2@L&ki91O8t4NA1Y=R$_#8;-?iz^o>wk3ecg^YYmQQ&1dI_nteDM1z5nn+ z8^fFX(+myw5}731o*&;eT=1X#;qX+(iH$)U&|>lFFFwCr`yIg|)`loHM6nQrnn$+| zGPB%FU4aThjvtx3Rk@inj#4)Wre|Fyw)b#vcY?O=Buf=YFF|TibLG0?<eXxy<Wk5M zNT1EF#md)sQ@=W~z9C^|uEfNv8_4;>#rZl*tBqXAMPwHv(%C90y-J*NiHx8s^(x80 zr+DnLqqLzC2;D6<%ORvzEw1Km!kVU5hob9qZoPt@+iB@)B$2SGcG#WjTy9pN<irY& zomyeXXeX_}eQnU+Z{vC)gGt45&PBEM^T2$b0aM|+X`nndn3(tT9jd(Bag;G!TBz{h z_|29!Lp=#$yG;<l7t={(T`NO(RoOid;|CER_^~OxwBwPytDdt9#TpOxt@~SSj$?x* zXcIFr9MXS2!=dtR?9U{)RtfPgc)1{NpsA(X;<3ZqHgpCrbTnHHq6HCy)&><TRII2+ z%Dy`C#7@)Om&Ce-L`6hfmZkI>oI3rUb+eKQ!2Y+VY8d<G8L0$fc0RoiFVVt7w!?Sx dDb7rf`~l~Jm)qq$2yp-a002ovPDHLkV1kbImf`>a literal 0 HcmV?d00001 diff --git a/importexport/templates/default/images/import.png b/importexport/templates/default/images/import.png new file mode 100755 index 0000000000000000000000000000000000000000..5a7b5140cde25040155ae550386582e48cb3a3a0 GIT binary patch literal 1567 zcmV+)2H^RLP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru)(H;}2OuJ|)`tK91*}O# zK~#9!?U&ncRM!=UzqQXfbMeeD9^bGHloW7jAcQzECE^mCq^Uw_NlMfziSj4(1*HBP ziTcu~wu;n-MoQI6O_L^7B3g(=0=Xy(7>u#0@daOSJY$b%=A1KUuRe?wAlN3ZUG=3u z={$@^d+%@k_FC(#Bdk=1H#&fWsKvLSq{MaZH^%I>#@4CuCaM>CdsG2<DdVB)VQhup zmF(BIa~C_K<AfDZK<@f)dg>Jo{3}r`(V;VAziLtSe_}+QPbLyu0~-znQE(pN;#Uh) zBfz4fi=A}`K*w<#2ik!gh_e_$L(|VCs0<_ulfS+{9dCW2ah>iv^1bfHLS@$W^qw8Q zHh%5S1+NuvMg2lX5Cl+8&go(fJgVqnln%fV5abqvwG|WkLkYk5kH+S7v319H9?CuS z@WYASTXu5lLXUs$%118hr-<1sKZ+@$fCW)-tVrl5n%A{P*}B|ZKc83b1`yo|Jc+U! zKt&??%1KK#7?$$fdrrOkvHAY4`^;01>|x91cG6y!*cHbi3R>ea_ZcVQK8R2Q5WDb1 zAQF!m*TdR3fu2PikVaI~3qLBLB_L8&LVwUL&b*OqPhUztySLMH@984Q<au}g&pe$y z#`d-j=g6KPwiNu?XI#$>76Pj6WE86rdH+i;3|^QXpSkhrQc36m1}FlT1<qMpt_1!- zdS?8Wojcpg+n+mdF#XuxM;LN1a(4C<Uj|d`lS6F#_9kgr|KnI_!<-R8sU0qWhyfA) z`q?|ScewXjzI1bLxhhbgAn+;5Z)R`)!Ou_q_Q}o7(tYI7=W>tl?~=*nRnFz#XN&=o zi4-4)r>OTDaI+5Fo-_+;^qT2R9U#|NFP`V7Ou~&@s!Nw45Fu1h3dQ`}RD;o$riNXa z?VGonY%a-X^8=LJn`GTOJY4>z7crq(q-bmU79DiSAu_RyIJg<CB$sqRY5^!vMexhz zo_QSkaAfj&_qj`-G;e5aPCnGO54?cu)yo(h9*7+vBRK@Ku=GyVJ59u>qa2piR;yFC z63#B$i0<qZZW;J!V&=xlzkl+cSgCd$J@9m@YvT`CpJ-)T$LMqpQku35jt@}^O0`Rn zTS++Q%u?{qlP~8ra<xhv`^^E~9g8@;u@H*+W8Y|BR19Wy&M$WT^x)CD9a|2WV(JE| zN|v7f)AslOe7!P0J3isXym^dDtqhffm!N%J8zI&fg2E&o!8Zp0_}hpAGu3Ce{OS61 zDR|?9w{)T~z5B)PWA#t$dd!GGzEp^Yr$#0RCg$F9k}Z9fyl<KDBb}<$+*s&<f=`Tr zl>scMt@YKSYnsOW;C$bQgYT~&&cEvRj`pRyckDOwVPKs2rc^F>bbBUs%qn58)tM_p z(>p(3q{_no2q2l<FKE>2dK>Nwst+_w6wm0%(`UoWW25eTIAdl*vaYwG(@1@jh%rd( zD6dgoHpNx~P^%CpCX$ZFVmrij+ao`Gu<}9QU*f(UaeWe!je8zQ0wIlR{GK_!!fC)N z3<9SOu~Fl!H;!qhYPZG%|Mhlb`__RSQFK65zCgIX#sF%8t3Va_0x%*5&)Gyn-7jt% z0ki}R0~2cs(EqZd9mk`az&y~u=)_V>_z1KD;;;1;(Eo~4sl6%JMuz{f?sh<R;Z0t1 zciRb-WpV<5t1lb<Z#ZCTfV#|s3%J`(SZB=~&|0y3sMafN79_o7{0bsOKylmgXi7wE z{|en$DhV@)>_YWtKxxUwR73EAK7n^oEdtSM00LAa5z>l$S47%sW9^cy(V{vJoK`r4 zYI$`4s*@s2qqK>r1xYSCVSMctfV$e(lE`Xv;1X~KVG_mR9@{CZ0u_bJh*VIus{;@& zsp1OsBH}L9>2B+Iil`5a07XQiRRIVT7NQwNF>-GQP$fbriV}#3@_(&R{{}!ee=BH! R)0qGO002ovPDHLkV1mEt>#P6( literal 0 HcmV?d00001 diff --git a/importexport/templates/default/images/importexport.jpg b/importexport/templates/default/images/importexport.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b83bcffaa5178d3440695b2c6e6861cd581bf2d7 GIT binary patch literal 24636 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 L_J8?UPAC2YtJVR` literal 0 HcmV?d00001 diff --git a/importexport/templates/default/images/importexport.xcf b/importexport/templates/default/images/importexport.xcf new file mode 100755 index 0000000000000000000000000000000000000000..7b296ca3e3a6caeda3d42ab704d600e525e904e5 GIT binary patch literal 5190 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 Bc@O{q literal 0 HcmV?d00001 diff --git a/importexport/templates/default/images/navbar.png b/importexport/templates/default/images/navbar.png new file mode 100644 index 0000000000000000000000000000000000000000..21c775016833c327734bb0137b791d7342d34f48 GIT binary patch literal 2319 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 literal 0 HcmV?d00001