From b31e40e0677882551e1fa7fc90a82f84ae147e4b Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 1 Oct 2005 20:18:41 +0000 Subject: [PATCH] moved InfoLog's link class and it's table into the api --- infolog/inc/class.boinfolog.inc.php | 2 +- infolog/inc/class.bolink.inc.php | 1131 +--------------- infolog/inc/class.soinfolog.inc.php | 2 +- infolog/setup/setup.inc.php | 6 +- infolog/setup/tables_current.inc.php | 16 - infolog/setup/tables_update.inc.php | 7 +- phpgwapi/inc/class.bolink.inc.php | 1135 +++++++++++++++++ .../inc/class.solink.inc.php | 10 +- phpgwapi/setup/setup.inc.php | 3 +- phpgwapi/setup/tables_current.inc.php | 18 +- phpgwapi/setup/tables_update.inc.php | 36 + 11 files changed, 1207 insertions(+), 1159 deletions(-) create mode 100644 phpgwapi/inc/class.bolink.inc.php rename {infolog => phpgwapi}/inc/class.solink.inc.php (97%) diff --git a/infolog/inc/class.boinfolog.inc.php b/infolog/inc/class.boinfolog.inc.php index 7204c4f592..984163f91b 100644 --- a/infolog/inc/class.boinfolog.inc.php +++ b/infolog/inc/class.boinfolog.inc.php @@ -14,7 +14,7 @@ /* $Id$ */ include_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.soinfolog.inc.php'); - include_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.bolink.inc.php'); + include_once(EGW_API_INC.'/class.bolink.inc.php'); /** * This class is the BO-layer of InfoLog, it also handles xmlrpc requests diff --git a/infolog/inc/class.bolink.inc.php b/infolog/inc/class.bolink.inc.php index dbf1e2817d..c60affbe99 100644 --- a/infolog/inc/class.bolink.inc.php +++ b/infolog/inc/class.bolink.inc.php @@ -12,1131 +12,6 @@ /* $Id$ */ - include_once(EGW_INCLUDE_ROOT . '/infolog/inc/class.solink.inc.php'); - - /** - * generalized linking between entries of eGroupware apps - BO layer - * - * This class is the BO-layer of the links - * - * Links have two ends each pointing to an entry, each entry is a double: - * - app app-name or directory-name of an egw application, eg. 'infolog' - * - id this is the id, eg. an integer or a tupple like '0:INBOX:1234' - * - * The BO-layer implementes 2 extra features on top of the so-layer: - * 1) It handles links to not already existing entries. This is used by the eTemplate link-widget, which allows to - * setup links even for new / not already existing entries, before they get saved. - * In that case you have to set the first id to 0 for the link-function and pass the array returned in that id - * (not the return-value) after saveing your new entry again to the link function. - * 2) Attaching files: they are saved in the vfs and not the link-table (!). - * - * @package infolog - * @subpackage link - * @author RalfBecker-At-outdoor-training.de - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - */ - class bolink extends solink - { - /** - * other apps can participate in the linking by implementing a 'search_link' hook, which - * has to return an array in the format of an app_register entry below - * @var array $app_register - */ - var $app_register = array( - 'addressbook' => array( - 'query' => 'addressbook_query', - 'title' => 'addressbook_title', - 'view' => array( - 'menuaction' => 'addressbook.uiaddressbook.view' - ), - 'view_id' => 'ab_id' - ), - 'projects' => array( - 'query' => 'projects_query', - 'title' => 'projects_title', - 'view' => array ( - 'menuaction' => 'projects.uiprojects.view_project' - ), - 'view_id' => 'project_id' - ), - 'infolog' => array( - 'query' => 'infolog.boinfolog.link_query', - 'title' => 'infolog.boinfolog.link_title', - 'view' => array( - 'menuaction' => 'infolog.uiinfolog.index', - 'action' => 'sp' - ), - 'view_id' => 'action_id', - 'add' => array( - 'menuaction' => 'infolog.uiinfolog.edit', - 'type' => 'task' - ), - 'add_app' => 'action', - 'add_id' => 'action_id', - ), -/* - 'email' => array( - 'view' => array( - 'menuaction' => 'email.uimessage.message' - ), - 'view_id' => 'msgball[acctnum:folder:msgnum]' // id is a tupple/array, fields separated by ':' - ), - */ - ); - var $public_functions = array( // functions callable via menuaction - 'get_file' => True - ); - var $vfs; - var $vfs_basedir='/infolog'; // might changes to links if class gets imported in the api - var $vfs_appname='file'; // pseudo-appname for own file-attachments in vfs, this is NOT the vfs-app - var $valid_pathes = array(); - var $send_file_ips = array(); - - /** - * constructor - */ - function bolink( ) - { - $this->solink( ); // call constructor of derived class - - $this->vfs =& CreateObject('phpgwapi.vfs'); - - $config =& CreateObject('phpgwapi.config'); - $config->read_repository(); - if (is_array($config->config_data)) - { - $this->link_pathes = $config->config_data['link_pathes']; - $this->send_file_ips = $config->config_data['send_file_ips']; - } - unset($config); - - // other apps can participate in the linking by implementing a search_link hook, which - // has to return an array in the format of an app_register entry - // for performance reasons, we do it only once / cache it in the session - if (!($search_link_hooks = $GLOBALS['egw']->session->appsession('search_link_hooks','infolog'))) - { - $search_link_hooks = $GLOBALS['egw']->hooks->process('search_link'); - $GLOBALS['egw']->session->appsession('search_link_hooks','infolog',$search_link_hooks); - } - if (is_array($search_link_hooks)) - { - foreach($search_link_hooks as $app => $data) - { - if (is_array($data)) - { - $this->app_register[$app] = $data; - } - } - } - } - - /** - * creats a link between $app1,$id1 and $app2,$id2 - $id1 does NOT need to exist yet - * - * Does NOT check if link already exists. - * File-attachments return a negative link-id !!! - * - * @param string $app1 app of $id1 - * @param string/array &$id1 id of item to linkto or 0 if item not yet created or array with links - * of not created item or $file-array if $app1 == $this->vfs_appname (see below). - * If $id==0 it will be set on return to an array with the links for the new item. - * @param string/array $app2 app of 2.linkend or array with links ($id2 not used) - * @param string $id2='' id of 2. item of $file-array if $app2 == $this->vfs_appname (see below)
- * $file array with informations about the file in format of the etemplate file-type
- * $file['name'] name of the file (no directory)
- * $file['type'] mine-type of the file
- * $file['tmp_name'] name of the uploaded file (incl. directory)
- * $file['path'] path of the file on the client computer
- * $file['ip'] of the client (path and ip in $file are only needed if u want a symlink (if possible)) - * @param string $remark='' Remark to be saved with the link (defaults to '') - * @param int $owner=0 Owner of the link (defaults to user) - * @param int $lastmod=0 timestamp of last modification (defaults to now=time()) - * @return int/boolean False (for db or param-error) or on success link_id (Please not the return-value of $id1) - */ - function link( $app1,&$id1,$app2,$id2='',$remark='',$owner=0,$lastmod=0 ) - { - if ($this->debug) - { - echo "

bolink.link('$app1',$id1,'".print_r($app2,true)."',".print_r($id2,true).",'$remark',$owner,$lastmod)

\n"; - } - if (!$app1 || !$app2 || $app1 == $app2 && $id1 == $id2) - { - return False; - } - if (is_array($id1) || !$id1) // create link only in $id1 array - { - if (!is_array($id1)) - { - $id1 = array( ); - } - $link_id = $app2 != $this->vfs_appname ? "$app2:$id2" : "$app2:$id2[name]"; - $id1[$link_id] = array( - 'app' => $app2, - 'id' => $id2, - 'remark' => $remark, - 'owner' => $owner, - 'link_id' => $link_id, - 'lastmod' => time() - ); - if ($this->debug) - { - _debug_array($id1); - } - return $link_id; - } - if (is_array($app2) && !$id2) - { - reset($app2); - $link_id = True; - while ($link_id && list(,$link) = each($app2)) - { - if (!is_array($link)) // check for unlink-marker - { - echo "link='$link' is no array
\n"; - continue; - } - if ($link['app'] == $this->vfs_appname) - { - $link_id = $this->attach_file($app1,$id1,$link['id'],$link['remark']); - } - else - { - $link_id = solink::link($app1,$id1,$link['app'],$link['id'], - $link['remark'],$link['owner'],$link['lastmod']); - - // notify both sides - $this->notify('link',$link['app'],$link['id'],$app1,$id1,$link_id); - $this->notify('link',$app1,$id1,$link['app'],$link['id'],$link_id); - } - } - return $link_id; - } - if ($app1 == $this->vfs_appname) - { - return $this->attach_file($app2,$id2,$id1,$remark); - } - elseif ($app2 == $this->vfs_appname) - { - return $this->attach_file($app1,$id1,$id2,$remark); - } - $link_id = solink::link($app1,$id1,$app2,$id2,$remark,$owner); - - $this->notify('link',$app2,$id2,$app1,$id1,$link_id); - $this->notify('link',$app1,$id1,$app2,$id2,$link_id); - - return $link_id; - } - - /** - * returns array of links to $app,$id (reimplemented to deal with not yet created items) - * - * @param string $app appname - * @param string/array $id id of entry in $app or array of links if entry not yet created - * @param string $only_app if set return only links from $only_app (eg. only addressbook-entries) or NOT from if $only_app[0]=='!' - * @param string $order='link_lastmod DESC' defaults to newest links first - * @return array of links or empty array if no matching links found - */ - function get_links( $app,$id,$only_app='',$order='link_lastmod DESC' ) - { - //echo "

bolink::get_links(app='$app',id='$id',only_app='$only_app',order='$order')

\n"; - - if (is_array($id) || !$id) - { - $ids = array(); - if (is_array($id)) - { - if ($not_only = $only_app[0]) - { - $only_app = substr(1,$only_app); - } - end($id); - while ($link = current($id)) - { - if (!is_array($link) || // check for unlink-marker - $only_app && $not_only == ($link['app'] == $only_app)) - { - continue; - } - $ids[$link['link_id']] = $link; - prev($id); - } - } - return $ids; - } - $ids = solink::get_links($app,$id,$only_app,$order); - - if (empty($only_app) || $only_app == $this->vfs_appname || - ($only_app[0] == '!' && $only_app != '!'.$this->vfs_appname)) - { - if ($vfs_ids = $this->list_attached($app,$id)) - { - $ids += $vfs_ids; - } - } - //echo "ids=
"; print_r($ids); echo "
\n"; - - return $ids; - } - - /** - * Read one link specified by it's link_id or by the two end-points - * - * If $id is an array (links not yet created) only link_ids are allowed. - * - * @param int/string $app_link_id > 0 link_id of link or app-name of link - * @param string/array $id='' id if $app_link_id is an appname or array with links, if 1. entry not yet created - * @param string $app2='' second app - * @param string $id2='' id in $app2 - * @return array with link-data or False - */ - function get_link($app_link_id,$id='',$app2='',$id2='') - { - if (is_array($id)) - { - if (isset($id[$app_link_id]) && is_array($id[$app_link_id])) // check for unlinked-marker - { - return $id[$app_link_id]; - } - return False; - } - if (intval($app_link_id) < 0 || $app_link_id == $this->vfs_appname || $app2 == $this->vfs_appname) - { - if (intval($app_link_id) < 0) // vfs link_id ? - { - return $this->fileinfo2link(-$app_link_id); - } - if ($app_link_id == $this->vfs_appname) - { - return $this->info_attached($app2,$id2,$id); - } - return $this->info_attached($app_link_id,$id,$id2); - } - return solink::get_link($app_link_id,$id,$app2,$id2); - } - - /** - * Remove link with $link_id or all links matching given $app,$id - * - * Note: if $link_id != '' and $id is an array: unlink removes links from that array only - * unlink has to be called with &$id to see the result (depricated) or unlink2 has to be used !!! - * - * @param $link_id link-id to remove if > 0 - * @param string $app='' appname of first endpoint - * @param string/array $id='' id in $app or array with links, if 1. entry not yet created - * @param string $app2='' app of second endpoint - * @param string $id2='' id in $app2 - * @return the number of links deleted - */ - function unlink($link_id,$app='',$id='',$owner='',$app2='',$id2='') - { - return $this->unlink2($link_id,$app,$id,$owner,$app2,$id2); - } - - /** - * Remove link with $link_id or all links matching given $app,$id - * - * @param $link_id link-id to remove if > 0 - * @param string $app='' appname of first endpoint - * @param string/array &$id='' id in $app or array with links, if 1. entry not yet created - * @param string $app2='' app of second endpoint - * @param string $id2='' id in $app2 - * @return the number of links deleted - */ - function unlink2($link_id,$app,&$id,$owner='',$app2='',$id2='') - { - if ($this->debug) - { - echo "

bolink::unlink('$link_id','$app','$id','$owner','$app2','$id2')

\n"; - } - if ($link_id < 0) // vfs-link? - { - return $this->delete_attached(-$link_id); - } - elseif ($app == $this->vfs_appname) - { - return $this->delete_attached($app2,$id2,$id); - } - elseif ($app2 == $this->vfs_appname) - { - return $this->delete_attached($app,$id,$id2); - } - if (!is_array($id)) - { - if (!$link_id && !$app2 && !$id2) - { - $this->delete_attached($app,$id); // deleting all attachments - } - $deleted =& solink::unlink($link_id,$app,$id,$owner,$app2,$id2); - - // only notify on real links, not the one cached for writing or fileattachments - $this->notify_unlink($deleted); - - return count($deleted); - } - if (isset($id[$link_id])) - { - $id[$link_id] = False; // set the unlink marker - - return True; - } - return False; - } - - /** - * get list/array of link-aware apps the user has rights to use - * - * @param string $must_support capability the apps need to support, eg. 'add', default ''=list all apps - * @return array with app => title pairs - */ - function app_list($must_support='') - { - $apps = array(); - foreach($this->app_register as $app => $reg) - { - if ($must_support && !isset($reg[$must_support])) continue; - - if ($GLOBALS['egw_info']['user']['apps'][$app]) - { - $apps[$app] = $GLOBALS['egw_info']['apps'][$app]['title']; - } - } - return $apps; - } - - /** - * Searches for a $pattern in the entries of $app - * - * @param string $app app to search - * @param string $pattern pattern to search - * @return array with $id => $title pairs of matching entries of app - */ - function query($app,$pattern) - { - if ($app == '' || !is_array($reg = $this->app_register[$app]) || !isset($reg['query'])) - { - return array(); - } - $method = $reg['query']; - - if ($this->debug) - { - echo "

bolink.query('$app','$pattern') => '$method'

\n"; - } - return strchr($method,'.') ? ExecMethod($method,$pattern) : $this->$method($pattern); - } - - /** - * returns the title (short description) of entry $id and $app - * - * @param string $app appname - * @param string $id id in $app - * @param array $link=null link-data for file-attachments - * @return the title or false if $id does not exist in $app - */ - function title($app,$id,$link=null) - { - if ($this->debug) - { - echo "

bolink::title('$app','$id')

\n"; - } - if ($app == $this->vfs_appname) - { - if (is_array($id) && $link) - { - $link = $id; - $id = $link['name']; - } - if (is_array($link)) - { - $size = $link['size']; - if ($size_k = intval($size / 1024)) - { - if (intval($size_k / 1024)) - { - $size = sprintf('%3.1dM',doubleval($size_k)/1024.0); - } - else - { - $size = $size_k.'k'; - } - } - $extra = ': '.$link['type'] . ' '.$size; - } - return $id.$extra; - } - if ($app == '' || !is_array($reg = $this->app_register[$app]) || !isset($reg['title'])) - { - return array(); - } - $method = $reg['title']; - - $title = strchr($method,'.') ? ExecMethod($method,$id) : $this->$method($id); - - if ($id && !$title) // $app,$id has been deleted ==> unlink als links to it - { - $this->unlink(0,$app,$id); - return False; - } - return $title; - } - - /** - * Add new entry to $app, evtl. already linked to $to_app, $to_id - * - * @param string $app appname of entry to create - * @param string $to_app appname to link the new entry to - * @param string $to_id id in $to_app - * @return array/boolean with name-value pairs for link to add-methode of $app or false if add not supported - */ - function add($app,$to_app='',$to_id='') - { - //echo "

bolink::add('$app','$to_app','$to_id') app_register[$app] ="; _debug_array($app_register[$app]); - if ($app == '' || !is_array($reg = $this->app_register[$app]) || !isset($reg['add'])) - { - return false; - } - $params = $reg['add']; - - if ($reg['add_app'] && $to_app && $reg['add_id'] && $to_id) - { - $params[$reg['add_app']] = $to_app; - $params[$reg['add_id']] = $to_id; - } - return $params; - } - - /** - * view entry $id of $app - * - * @param string $app appname - * @param string $id id in $app - * @param array $link=null link-data for file-attachments - * @return array with name-value pairs for link to view-methode of $app to view $id - */ - function view($app,$id,$link=null) - { - if ($app == $this->vfs_appname && !empty($id) && is_array($link)) - { - return $this->get_file($link); - } - if ($app == '' || !is_array($reg = $this->app_register[$app]) || !isset($reg['view']) || !isset($reg['view_id'])) - { - return array(); - } - $view = $reg['view']; - - $names = explode(':',$reg['view_id']); - if (count($names) > 1) - { - $id = explode(':',$id); - while (list($n,$name) = each($names)) - { - $view[$name] = $id[$n]; - } - } - else - { - $view[$reg['view_id']] = $id; - } - return $view; - } - - /** - * Check if $app uses a popup for $action - * - * @param string $app app-name - * @param string $action='view' name of the action, atm. 'view' or 'add' - * @return boolean/string false if no popup is used or $app is not registered, otherwise string with the prefered popup size (eg. '640x400) - */ - function is_popup($app,$action='view') - { - if (!($reg = $this->app_register[$app]) || !$reg[$action.'_popup']) - { - return false; - } - return $reg[$action.'_popup']; - } - - function get_file($link='') - { - if (is_array($link)) - { - return array( - 'menuaction' => 'infolog.bolink.get_file', - 'app' => $link['app2'], - 'id' => $link['id2'], - 'filename' => $link['id'] - ); - } - $app = get_var('app','GET'); - $id = get_var('id','GET'); - $filename = get_var('filename','GET'); - - if (empty($app) || empty($id) || empty($filename) /* || !$this->bo->check_access($info_id,EGW_ACL_READ)*/) - { - $GLOBALS['egw']->redirect_link('/'); - } - $browser =& CreateObject('phpgwapi.browser'); - - $local = $this->attached_local($app,$id,$filename,$_SERVER['REMOTE_ADDR'],$browser->is_windows()); - - if ($local) - { - Header('Location: ' . $local); - } - else - { - $info = $this->info_attached($app,$id,$filename); - $browser->content_header($filename,$info['type']); - echo $this->read_attached($app,$id,$filename); - } - $GLOBALS['egw']->common->egw_exit(); - } - - /** - * path to the attached files of $app/$ip or the directory for $app if no $id,$file given - * - * All link-files are based in the vfs-subdir 'infolog'. For other apps - * separate subdirs with name app are created. - * - * @param string $app appname - * @param string $id='' id in $app - * @param string $file='' filename - * @param boolean/array $relatives=False return path as array with path in string incl. relatives - * @return string/array path or array with path and relatives, depending on $relatives - */ - function vfs_path($app,$id='',$file='',$relatives=False) - { - $path = $this->vfs_basedir . ($app == '' || $app == 'infolog' ? '' : '/'.$app) . - ($id != '' ? '/' . $id : '') . ($file != '' ? '/' . $file : ''); - - if ($this->debug) - { - echo "

bolink::vfs_path('$app','$id','$file') = '$path'

\n"; - } - return $relatives ? array( - 'string' => $path, - 'relatives' => is_array($relatives) ? $relatives : array($relatives) - ) : $path; - } - - /** - * Put a file to the corrosponding place in the VFS and set the attributes - * - * @param string $app appname to linke the file to - * @param string $id id in $app - * @param array $file informations about the file in format of the etemplate file-type - * $file['name'] name of the file (no directory) - * $file['type'] mine-type of the file - * $file['tmp_name'] name of the uploaded file (incl. directory) - * $file['path'] path of the file on the client computer - * $file['ip'] of the client (path and ip are only needed if u want a symlink (if possible)) - * @param string $comment='' comment to add to the link - * @return int negative id of phpgw_vfs table as negative link-id's are for vfs attachments - */ - function attach_file($app,$id,$file,$comment='') - { - if ($this->debug) - { - echo "

attach_file: app='$app', id='$id', tmp_name='$file[tmp_name]', name='$file[name]', size='$file[size]', type='$file[type]', path='$file[path]', ip='$file[ip]', comment='$comment'

\n"; - } - // create the root for attached files in infolog, if it does not exists - $vfs_data = array('string'=>$this->vfs_basedir,'relatives'=>array(RELATIVE_ROOT)); - if (!($this->vfs->file_exists($vfs_data))) - { - $this->vfs->override_acl = 1; - $this->vfs->mkdir($vfs_data); - $this->vfs->override_acl = 0; - } - - $vfs_data = $this->vfs_path($app,False,False,RELATIVE_ROOT); - if (!($this->vfs->file_exists($vfs_data))) - { - $this->vfs->override_acl = 1; - $this->vfs->mkdir($vfs_data); - $this->vfs->override_acl = 0; - } - $vfs_data = $this->vfs_path($app,$id,False,RELATIVE_ROOT); - if (!($this->vfs->file_exists($vfs_data))) - { - $this->vfs->override_acl = 1; - $this->vfs->mkdir($vfs_data); - $this->vfs->override_acl = 0; - } - $fname = $this->vfs_path($app,$id,$file['name']); - $tfname = ''; - if (!empty($file['path']) && is_array($this->link_pathes) && count($this->link_pathes)) - { - $file['path'] = str_replace('\\\\','/',$file['path']); // vfs uses only '/' - @reset($this->link_pathes); - while ((list($valid,$trans) = @each($this->link_pathes)) && !$tfname) - { // check case-insensitive for WIN etc. - $check = $valid[0] == '\\' || strstr(':',$valid) ? 'eregi' : 'ereg'; - $valid2 = str_replace('\\','/',$valid); - //echo "

attach_file: ereg('".$this->send_file_ips[$valid]."', '$file[ip]')=".ereg($this->send_file_ips[$valid],$file['ip'])."

\n"; - if ($check('^('.$valid2.')(.*)$',$file['path'],$parts) && - ereg($this->send_file_ips[$valid],$file['ip']) && // right IP - $this->vfs->file_exists(array('string'=>$trans.$parts[2],'relatives'=>array(RELATIVE_NONE|VFS_REAL)))) - { - $tfname = $trans.$parts[2]; - } - //echo "

attach_file: full_fname='$file[path]', valid2='$valid2', trans='$trans', check=$check, tfname='$tfname', parts=(x,'${parts[1]}','${parts[2]}')

\n"; - } - if ($tfname && !$this->vfs->securitycheck(array('string'=>$tfname))) - { - return False; //lang('Invalid filename').': '.$tfname; - } - } - $this->vfs->override_acl = 1; - $this->vfs->cp(array( - 'symlink' => !!$tfname, // try a symlink - 'from' => $tfname ? $tfname : $file['tmp_name'], - 'to' => $fname, - 'relatives' => array(RELATIVE_NONE|VFS_REAL,RELATIVE_ROOT), - )); - $this->vfs->set_attributes(array( - 'string' => $fname, - 'relatives' => array (RELATIVE_ROOT), - 'attributes' => array ( - 'mime_type' => $file['type'], - 'comment' => stripslashes ($comment), - 'app' => $app - ))); - $this->vfs->override_acl = 0; - - $link = $this->info_attached($app,$id,$file['name']); - - return is_array($link) ? $link['link_id'] : False; - } - - /** - * deletes an attached file - * - * @param int/string $app > 0: file_id of an attchemnt or $app/$id entry which linked to - * @param string $id='' id in app - * @param string $fname filename - */ - function delete_attached($app,$id='',$fname = '') - { - if (intval($app) > 0) // is file_id - { - $link = $this->fileinfo2link($file_id=$app); - $app = $link['app2']; - $id = $link['id2']; - $fname = $link['id']; - } - if ($this->debug) - { - echo "

bolink::delete_attached('$app','$id','$fname') file_id=$file_id

\n"; - } - if (empty($app) || empty($id)) - { - return False; // dont delete more than all attachments of an entry - } - $vfs_data = $this->vfs_path($app,$id,$fname,RELATIVE_ROOT); - - $Ok = false; - if ($this->vfs->file_exists($vfs_data)) - { - $this->vfs->override_acl = 1; - $Ok = $this->vfs->delete($vfs_data); - $this->vfs->override_acl = 0; - } - // if filename given (and now deleted) check if dir is empty and remove it in that case - if ($fname && !count($this->vfs->ls($vfs_data=$this->vfs_path($app,$id,'',RELATIVE_ROOT)))) - { - $this->vfs->override_acl = 1; - $this->vfs->delete($vfs_data); - $this->vfs->override_acl = 0; - } - return $Ok; - } - - /** - * converts the infos vfs has about a file into a link - * - * @param string $app appname - * @param string $id id in app - * @param string $filename filename - * @return array 'kind' of link-array - */ - function info_attached($app,$id,$filename) - { - $this->vfs->override_acl = 1; - $attachments = $this->vfs->ls($this->vfs_path($app,$id,$filename,RELATIVE_NONE)); - $this->vfs->override_acl = 0; - - if (!count($attachments) || !$attachments[0]['name']) - { - return False; - } - return $this->fileinfo2link($attachments[0]); - } - - /** - * converts a fileinfo (row in the vfs-db-table) in a link - * - * @param array/int $fileinfo a row from the vfs-db-table (eg. returned by the vfs ls function) or a file_id of that table - * @return array a 'kind' of link-array - */ - function fileinfo2link($fileinfo) - { - if (!is_array($fileinfo)) - { - $fileinfo = $this->vfs->ls(array('file_id' => $fileinfo)); - list(,$fileinfo) = each($fileinfo); - - if (!is_array($fileinfo)) - { - return False; - } - } - $lastmod = $fileinfo[!empty($fileinfo['modified']) ? 'modified' : 'created']; - list($y,$m,$d) = explode('-',$lastmod); - $lastmod = mktime(0,0,0,$m,$d,$y); - - $dir_parts = array_reverse(explode('/',$fileinfo['directory'])); - - return array( - 'app' => $this->vfs_appname, - 'id' => $fileinfo['name'], - 'app2' => $dir_parts[1], - 'id2' => $dir_parts[0], - 'remark' => $fileinfo['comment'], - 'owner' => $fileinfo['owner_id'], - 'link_id' => -$fileinfo['file_id'], - 'lastmod' => $lastmod, - 'size' => $fileinfo['size'], - 'type' => $fileinfo['mime_type'] - ); - } - - /** - * lists all attachments to $app/$id - * - * @param string $app appname - * @param string $id id in app - * @return array with link_id => 'kind' of link-array pairs - */ - function list_attached($app,$id) - { - $this->vfs->override_acl = 1; - $attachments = $this->vfs->ls($this->vfs_path($app,$id,False,RELATIVE_ROOT)); - $this->vfs->override_acl = 0; - - if (!count($attachments) || !$attachments[0]['name']) - { - return False; - } - foreach($attachments as $fileinfo) - { - $link = $this->fileinfo2link($fileinfo); - $attached[$link['link_id']] = $link; - } - return $attached; - } - - /** - * checks if path starts with a '\\' or has a ':' in it - * - * @param string $path path to check - * @return boolean true if windows path, false otherwise - */ - function is_win_path($path) - { - return $path[0] == '\\' || strstr($path,':'); - } - - /** - * reads the attached file and returns the content - * - * @param string $app appname - * @param string $id id in app - * @param string $filename filename - * @return string content of the attached file - */ - function read_attached($app,$id,$filename) - { - if (empty($app) || !$id || empty($filename) /*|| !$this->check_access($info_id,EGW_ACL_READ)*/) - { - return False; - } - $this->vfs->override_acl = 1; - $data = $this->vfs->read($this->vfs_path($app,$id,$filename,RELATIVE_ROOT)); - $this->vfs->override_acl = 0; - return $data; - } - - /** - * Checks if filename should be local availible and if so returns - * - * @param string $app appname - * @param string $id id in app - * @param string $filename filename - * @param string $id ip-address of user - * @param boolean $win_user true if user is on windows, otherwise false - * @return string 'file:/path' for HTTP-redirect else return False - */ - function attached_local($app,$id,$filename,$ip,$win_user) - { - //echo "

attached_local(app=$app, id='$id', filename='$filename', ip='$ip', win_user='$win_user', count(send_file_ips)=".count($this->send_file_ips).")

\n"; - - if (!$id || !$filename || /* !$this->check_access($info_id,EGW_ACL_READ) || */ - !count($this->send_file_ips)) - { - return False; - } - $link = $this->vfs->ls($this->vfs_path($app,$id,$filename,RELATIVE_ROOT)+array('readlink'=>True)); - $link = @$link[0]['symlink']; - - if ($link && is_array($this->link_pathes)) - { - reset($this->link_pathes); $fname = ''; - while ((list($valid,$trans) = each($this->link_pathes)) && !$fname) - { - if (!$this->is_win_path($valid) == !$win_user && // valid for this OS - $win_user && // only for IE/windows atm - eregi('^'.$trans.'(.*)$',$link,$parts) && // right path - ereg($this->send_file_ips[$valid],$ip)) // right IP - { - $fname = $valid . $parts[1]; - $fname = !$win_user ? str_replace('\\','/',$fname) : str_replace('/','\\',$fname); - return 'file:'.($win_user ? '//' : '' ).$fname; - } - //echo "

attached_local: link=$link, valid=$valid, trans='$trans', fname='$fname', parts=(x,'${parts[1]}','${parts[2]}')

\n"; - } - } - return False; - } - - /** - * reverse function of htmlspecialchars() - * - * @param string $str string to decode - * @return string decoded string - */ - function decode_htmlspecialchars($str) - { - return str_replace(array('&','"','<','>'),array('&','"','<','>'),$str); - } - - /** - * get title for an event, should be moved to bocalendar.link_title - * - * @param int/array $event event-id or already read event - * @return string/boolean the title (startdate plus subject), of false if event is not found - */ - function calendar_title( $event ) - { - if (!is_object($this->bocal)) - { - $this->bocal = createobject('calendar.bocalendar'); - } - if (!is_array($event) && (int) $event > 0) - { - $event = $this->bocal->read_entry($event); - } - if (!is_array($event)) - { - return False; - } - $format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'] . ' '. - ($GLOBALS['egw_info']['user']['preferences']['common']['timeformat'] == '12' ? 'h:i a' : 'H:i'); - - $name = $GLOBALS['egw']->common->show_date($this->bocal->maketime($event['start']) - $this->bocal->datetime->tz_offset,$format); - $name .= ': ' . $this->decode_htmlspecialchars($event['title']); // cal returns entities - - return $name; - } - - /** - * query calendar for an event $matching pattern, should be moved to bocalendar.link_query - * - * @param string $pattern pattern to search - * @return array with id => title pairs of matching events - */ - function calendar_query($pattern) - { - if (!is_object($this->bocal)) - { - $this->bocal = createobject('calendar.bocalendar'); - } - $content = array( ); - - if ($event_ids = $this->bocal->search_keywords($pattern)) - { - foreach($event_ids as $id) - { - // only include it in the list, if we have permissions to read it - if ($this->bocal->check_perms(EGW_ACL_READ,$id)) - { - $content[$id] = $this->calendar_title( $id ); - } - } - } - return $content; - } - - /** - * get title for an address, should be moved to boaddressbook.link_title - * - * @param int/array $event address-id or already read address - * @return string/boolean the title ([org:] n_familiy[, n_given|n_prefix]), of false if address is not found - */ - function addressbook_title( $addr ) - { - if (!is_object($this->contacts)) - { - $this->contacts = createobject('phpgwapi.contacts'); - } - if (!is_array($addr)) - { - list( $addr ) = $this->contacts->read_single_entry( $addr ); - } - if (!is_array($addr)) - { - return False; - } - $name = $addr['n_family']; - if ($addr['n_given']) - { - $name .= ', '.$addr['n_given']; - } - else - { - if ($addr['n_prefix']) - { - $name .= ', '.$addr['n_prefix']; - } - } - if ($addr['org_name']) - { - $name = $addr['org_name'].($name !== '' ? ': '.$name : ''); - } - return stripslashes($name); // addressbook returns quotes with slashes - } - - /** - * query addressbook for $pattern, should be moved to boaddressbook.link_query - * - * @param string $pattern pattern to search - * @return array with id => title pairs of matching addresses - */ - function addressbook_query( $pattern ) - { - if (!is_object($this->contacts)) - { - $this->contacts = createobject('phpgwapi.contacts'); - } - $addrs = $this->contacts->read( 0,0,'',$pattern,'','DESC','org_name,n_family,n_given' ); - $content = array( ); - while ($addrs && list( $key,$addr ) = each( $addrs )) - { - $content[$addr['id']] = $this->addressbook_title( $addr ); - } - return $content; - } - - /** - * get title for a project, should be moved to boprojects.link_title - * - * @param int/array $event project-id or already read project - * @return string/boolean the title (number: title), of false if address is not found - */ - function projects_title( $proj ) - { - if (!is_object($this->boprojects)) - { - if (!file_exists(EGW_SERVER_ROOT.'/projects')) // check if projects installed - return ''; - $this->boprojects = createobject('projects.boprojects'); - } - if (!is_array($proj)) - { - $proj = $this->boprojects->read_single_project( $proj ); - } - return is_array($proj) ? $proj['number'].': '.$proj['title'] : False; - } - - /** - * query for projects matching $pattern, should be moved to boprojects.link_query - * - * @param string $pattern pattern to search - * @return array with id => title pairs of matching projects - */ - function projects_query( $pattern ) - { - if (!is_object($this->boprojects)) - { - if (!file_exists(EGW_SERVER_ROOT.'/projects')) // check if projects installed - return array(); - $this->boprojects = createobject('projects.boprojects'); - } - $projs = $this->boprojects->list_projects( array('action'=>'all','query'=>$pattern,'limit'=>FALSE) ); - $content = array(); - while ($projs && list( $key,$proj ) = each( $projs )) - { - $content[$proj['project_id']] = $this->projects_title($proj); - } - return $content; - } - - /** - * notify other apps about changed content in $app,$id - * - * @param string $app name of app in which the updated happend - * @param string $id id in $app of the updated entry - * @param array $data=null updated data of changed entry, as the read-method of the BO-layer would supply it - */ - function notify_update($app,$id,$data=null) - { - foreach($this->get_links($app,$id,'!'.$this->vfs_appname) as $link_id => $link) - { - $this->notify('update',$link['app'],$link['id'],$app,$id,$link_id,$data); - } - } - - /** - * notify an application about a new or deleted links to own entries or updates in the content of the linked entry - * - * Please note: not all apps supply update notifications - * - * @internal - * @param string $type 'link' for new links, 'unlink' for unlinked entries, 'update' of content in linked entries - * @param string $notify_app app to notify - * @param string $notify_id id in $notify_app - * @param string $target_app name of app whos entry changed, linked or deleted - * @param string $target_id id in $target_app - * @param array $data=null data of entry in app2 (optional) - */ - function notify($type,$notify_app,$notify_id,$target_app,$target_id,$link_id,$data=null) - { - if ($link_id && isset($this->app_register[$notify_app]) && isset($this->app_register[$notify_app]['notify'])) - { - ExecMethod($this->app_register[$notify_app]['notify'],array( - 'type' => $type, - 'id' => $notify_id, - 'target_app' => $target_app, - 'target_id' => $target_id, - 'link_id' => $link_id, - 'data' => $data, - )); - } - } - - /** - * notifies about unlinked links - * - * @internal - * @param array &$links unlinked links from the database - */ - function notify_unlink(&$links) - { - foreach($links as $link) - { - // we notify both sides of the link, as the unlink command NOT clearly knows which side initiated the unlink - $this->notify('unlink',$link['link_app1'],$link['link_id1'],$link['link_app2'],$link['link_id2'],$link['link_id']); - $this->notify('unlink',$link['link_app2'],$link['link_id2'],$link['link_app1'],$link['link_id1'],$link['link_id']); - } - } - } \ No newline at end of file + // class has been moved to the API, this file is only for compatibility with existing applications + // it should be removed after the 1.2 release + include_once(EGW_API_INC . '/class.bolink.inc.php'); diff --git a/infolog/inc/class.soinfolog.inc.php b/infolog/inc/class.soinfolog.inc.php index ff04e88e33..fc6d7d5ab7 100644 --- a/infolog/inc/class.soinfolog.inc.php +++ b/infolog/inc/class.soinfolog.inc.php @@ -13,7 +13,7 @@ /* $Id$ */ - include_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.solink.inc.php'); + include_once(EGW_API_INC.'/class.solink.inc.php'); /** * storage object / db-layer for InfoLog diff --git a/infolog/setup/setup.inc.php b/infolog/setup/setup.inc.php index 45b1b13181..1edda5d60a 100755 --- a/infolog/setup/setup.inc.php +++ b/infolog/setup/setup.inc.php @@ -14,7 +14,7 @@ $setup_info['infolog']['name'] = 'infolog'; $setup_info['infolog']['version'] = '1.0.1.001'; $setup_info['infolog']['app_order'] = 5; - $setup_info['infolog']['tables'] = array('egw_infolog','egw_infolog_extra','egw_links'); + $setup_info['infolog']['tables'] = array('egw_infolog','egw_infolog_extra'); $setup_info['infolog']['enable'] = 1; $setup_info['infolog']['author'] = @@ -63,11 +63,11 @@ /* Dependencies for this app to work */ $setup_info['infolog']['depends'][] = array( 'appname' => 'phpgwapi', - 'versions' => Array('0.9.14','0.9.15','0.9.16','1.0.0','1.0.1') + 'versions' => Array('1.0.1') ); $setup_info['infolog']['depends'][] = array( 'appname' => 'etemplate', - 'versions' => Array('0.9.15','0.9.16','1.0.0') + 'versions' => Array('1.0.0','1.0.1') ); diff --git a/infolog/setup/tables_current.inc.php b/infolog/setup/tables_current.inc.php index 77ae21a75c..220c3e5574 100644 --- a/infolog/setup/tables_current.inc.php +++ b/infolog/setup/tables_current.inc.php @@ -41,22 +41,6 @@ 'ix' => array(array('info_owner','info_responsible','info_status','info_startdate'),array('info_id_parent','info_owner','info_responsible','info_status','info_startdate')), 'uc' => array() ), - 'egw_links' => array( - 'fd' => array( - 'link_id' => array('type' => 'auto','nullable' => False), - 'link_app1' => array('type' => 'varchar','precision' => '25','nullable' => False), - 'link_id1' => array('type' => 'varchar','precision' => '50','nullable' => False), - 'link_app2' => array('type' => 'varchar','precision' => '25','nullable' => False), - 'link_id2' => array('type' => 'varchar','precision' => '50','nullable' => False), - 'link_remark' => array('type' => 'varchar','precision' => '50'), - 'link_lastmod' => array('type' => 'int','precision' => '4','nullable' => False), - 'link_owner' => array('type' => 'int','precision' => '4','nullable' => False) - ), - 'pk' => array('link_id'), - 'fk' => array(), - 'ix' => array(array('link_app1','link_id1','link_lastmod'),array('link_app2','link_id2','link_lastmod')), - 'uc' => array() - ), 'egw_infolog_extra' => array( 'fd' => array( 'info_id' => array('type' => 'int','precision' => '4','nullable' => False), diff --git a/infolog/setup/tables_update.inc.php b/infolog/setup/tables_update.inc.php index 8396c7da80..7ac671715a 100644 --- a/infolog/setup/tables_update.inc.php +++ b/infolog/setup/tables_update.inc.php @@ -436,8 +436,11 @@ $GLOBALS['phpgw_setup']->oProc->RenameTable('phpgw_infolog','egw_infolog'); $GLOBALS['phpgw_setup']->oProc->RenameTable('phpgw_infolog_extra','egw_infolog_extra'); - $GLOBALS['phpgw_setup']->oProc->RenameTable('phpgw_links','egw_links'); - + // only rename links table, if it has not been moved into the API and therefor been already renamed by the API update + if ($GLOBALS['phpgw_setup']->oProc->GetTableDefinition('phpgw_links')) + { + $GLOBALS['phpgw_setup']->oProc->RenameTable('phpgw_links','egw_links'); + } $GLOBALS['setup_info']['infolog']['currentver'] = '1.0.1.001'; return $GLOBALS['setup_info']['infolog']['currentver']; } diff --git a/phpgwapi/inc/class.bolink.inc.php b/phpgwapi/inc/class.bolink.inc.php new file mode 100644 index 0000000000..6b8d8d5ff7 --- /dev/null +++ b/phpgwapi/inc/class.bolink.inc.php @@ -0,0 +1,1135 @@ + * + * -------------------------------------------- * + * 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$ */ + + include_once(EGW_API_INC . '/class.solink.inc.php'); + + /** + * generalized linking between entries of eGroupware apps - BO layer + * + * Links have two ends each pointing to an entry, each entry is a double: + * - app app-name or directory-name of an egw application, eg. 'infolog' + * - id this is the id, eg. an integer or a tupple like '0:INBOX:1234' + * + * The BO-layer implementes 2 extra features on top of the so-layer: + * 1) It handles links to not already existing entries. This is used by the eTemplate link-widget, which allows to + * setup links even for new / not already existing entries, before they get saved. + * In that case you have to set the first id to 0 for the link-function and pass the array returned in that id + * (not the return-value) after saveing your new entry again to the link function. + * 2) Attaching files: they are saved in the vfs and not the link-table (!). + * Attached files are stored under $vfs_basedir='/infolog' in the vfs! + * + * @package API + * @subpackage link + * @author RalfBecker-At-outdoor-training.de + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + */ + class bolink extends solink + { + /** + * other apps can participate in the linking by implementing a 'search_link' hook, which + * has to return an array in the format of an app_register entry below + * @var array $app_register + */ + var $app_register = array( + 'addressbook' => array( + 'query' => 'addressbook_query', + 'title' => 'addressbook_title', + 'view' => array( + 'menuaction' => 'addressbook.uiaddressbook.view' + ), + 'view_id' => 'ab_id' + ), + 'projects' => array( + 'query' => 'projects_query', + 'title' => 'projects_title', + 'view' => array ( + 'menuaction' => 'projects.uiprojects.view_project' + ), + 'view_id' => 'project_id' + ), + 'infolog' => array( + 'query' => 'infolog.boinfolog.link_query', + 'title' => 'infolog.boinfolog.link_title', + 'view' => array( + 'menuaction' => 'infolog.uiinfolog.index', + 'action' => 'sp' + ), + 'view_id' => 'action_id', + 'add' => array( + 'menuaction' => 'infolog.uiinfolog.edit', + 'type' => 'task' + ), + 'add_app' => 'action', + 'add_id' => 'action_id', + ), +/* + 'email' => array( + 'view' => array( + 'menuaction' => 'email.uimessage.message' + ), + 'view_id' => 'msgball[acctnum:folder:msgnum]' // id is a tupple/array, fields separated by ':' + ), + */ + ); + var $public_functions = array( // functions callable via menuaction + 'get_file' => True + ); + var $vfs; + var $vfs_basedir='/infolog'; + var $vfs_appname='file'; // pseudo-appname for own file-attachments in vfs, this is NOT the vfs-app + var $valid_pathes = array(); + var $send_file_ips = array(); + + /** + * constructor + */ + function bolink( ) + { + $this->solink( ); // call constructor of derived class + + $this->vfs =& CreateObject('phpgwapi.vfs'); + + $this->link_pathes = $GLOBALS['egw_info']['server']['link_pathes']; + $this->send_file_ips = $GLOBALS['egw_info']['server']['send_file_ips']; + + // other apps can participate in the linking by implementing a search_link hook, which + // has to return an array in the format of an app_register entry + // for performance reasons, we do it only once / cache it in the session + if (!($search_link_hooks = $GLOBALS['egw']->session->appsession('search_link_hooks','phpgwapi'))) + { + $search_link_hooks = $GLOBALS['egw']->hooks->process('search_link'); + $GLOBALS['egw']->session->appsession('search_link_hooks','phpgwapi',$search_link_hooks); + } + if (is_array($search_link_hooks)) + { + foreach($search_link_hooks as $app => $data) + { + if (is_array($data)) + { + $this->app_register[$app] = $data; + } + } + } + } + + /** + * creats a link between $app1,$id1 and $app2,$id2 - $id1 does NOT need to exist yet + * + * Does NOT check if link already exists. + * File-attachments return a negative link-id !!! + * + * @param string $app1 app of $id1 + * @param string/array &$id1 id of item to linkto or 0 if item not yet created or array with links + * of not created item or $file-array if $app1 == $this->vfs_appname (see below). + * If $id==0 it will be set on return to an array with the links for the new item. + * @param string/array $app2 app of 2.linkend or array with links ($id2 not used) + * @param string $id2='' id of 2. item of $file-array if $app2 == $this->vfs_appname (see below)
+ * $file array with informations about the file in format of the etemplate file-type
+ * $file['name'] name of the file (no directory)
+ * $file['type'] mine-type of the file
+ * $file['tmp_name'] name of the uploaded file (incl. directory)
+ * $file['path'] path of the file on the client computer
+ * $file['ip'] of the client (path and ip in $file are only needed if u want a symlink (if possible)) + * @param string $remark='' Remark to be saved with the link (defaults to '') + * @param int $owner=0 Owner of the link (defaults to user) + * @param int $lastmod=0 timestamp of last modification (defaults to now=time()) + * @return int/boolean False (for db or param-error) or on success link_id (Please not the return-value of $id1) + */ + function link( $app1,&$id1,$app2,$id2='',$remark='',$owner=0,$lastmod=0 ) + { + if ($this->debug) + { + echo "

bolink.link('$app1',$id1,'".print_r($app2,true)."',".print_r($id2,true).",'$remark',$owner,$lastmod)

\n"; + } + if (!$app1 || !$app2 || $app1 == $app2 && $id1 == $id2) + { + return False; + } + if (is_array($id1) || !$id1) // create link only in $id1 array + { + if (!is_array($id1)) + { + $id1 = array( ); + } + $link_id = $app2 != $this->vfs_appname ? "$app2:$id2" : "$app2:$id2[name]"; + $id1[$link_id] = array( + 'app' => $app2, + 'id' => $id2, + 'remark' => $remark, + 'owner' => $owner, + 'link_id' => $link_id, + 'lastmod' => time() + ); + if ($this->debug) + { + _debug_array($id1); + } + return $link_id; + } + if (is_array($app2) && !$id2) + { + reset($app2); + $link_id = True; + while ($link_id && list(,$link) = each($app2)) + { + if (!is_array($link)) // check for unlink-marker + { + echo "link='$link' is no array
\n"; + continue; + } + if ($link['app'] == $this->vfs_appname) + { + $link_id = $this->attach_file($app1,$id1,$link['id'],$link['remark']); + } + else + { + $link_id = solink::link($app1,$id1,$link['app'],$link['id'], + $link['remark'],$link['owner'],$link['lastmod']); + + // notify both sides + $this->notify('link',$link['app'],$link['id'],$app1,$id1,$link_id); + $this->notify('link',$app1,$id1,$link['app'],$link['id'],$link_id); + } + } + return $link_id; + } + if ($app1 == $this->vfs_appname) + { + return $this->attach_file($app2,$id2,$id1,$remark); + } + elseif ($app2 == $this->vfs_appname) + { + return $this->attach_file($app1,$id1,$id2,$remark); + } + $link_id = solink::link($app1,$id1,$app2,$id2,$remark,$owner); + + $this->notify('link',$app2,$id2,$app1,$id1,$link_id); + $this->notify('link',$app1,$id1,$app2,$id2,$link_id); + + return $link_id; + } + + /** + * returns array of links to $app,$id (reimplemented to deal with not yet created items) + * + * @param string $app appname + * @param string/array $id id of entry in $app or array of links if entry not yet created + * @param string $only_app if set return only links from $only_app (eg. only addressbook-entries) or NOT from if $only_app[0]=='!' + * @param string $order='link_lastmod DESC' defaults to newest links first + * @return array of links or empty array if no matching links found + */ + function get_links( $app,$id,$only_app='',$order='link_lastmod DESC' ) + { + //echo "

bolink::get_links(app='$app',id='$id',only_app='$only_app',order='$order')

\n"; + + if (is_array($id) || !$id) + { + $ids = array(); + if (is_array($id)) + { + if ($not_only = $only_app[0]) + { + $only_app = substr(1,$only_app); + } + end($id); + while ($link = current($id)) + { + if (!is_array($link) || // check for unlink-marker + $only_app && $not_only == ($link['app'] == $only_app)) + { + continue; + } + $ids[$link['link_id']] = $link; + prev($id); + } + } + return $ids; + } + $ids = solink::get_links($app,$id,$only_app,$order); + + if (empty($only_app) || $only_app == $this->vfs_appname || + ($only_app[0] == '!' && $only_app != '!'.$this->vfs_appname)) + { + if ($vfs_ids = $this->list_attached($app,$id)) + { + $ids += $vfs_ids; + } + } + //echo "ids=
"; print_r($ids); echo "
\n"; + + return $ids; + } + + /** + * Read one link specified by it's link_id or by the two end-points + * + * If $id is an array (links not yet created) only link_ids are allowed. + * + * @param int/string $app_link_id > 0 link_id of link or app-name of link + * @param string/array $id='' id if $app_link_id is an appname or array with links, if 1. entry not yet created + * @param string $app2='' second app + * @param string $id2='' id in $app2 + * @return array with link-data or False + */ + function get_link($app_link_id,$id='',$app2='',$id2='') + { + if (is_array($id)) + { + if (isset($id[$app_link_id]) && is_array($id[$app_link_id])) // check for unlinked-marker + { + return $id[$app_link_id]; + } + return False; + } + if (intval($app_link_id) < 0 || $app_link_id == $this->vfs_appname || $app2 == $this->vfs_appname) + { + if (intval($app_link_id) < 0) // vfs link_id ? + { + return $this->fileinfo2link(-$app_link_id); + } + if ($app_link_id == $this->vfs_appname) + { + return $this->info_attached($app2,$id2,$id); + } + return $this->info_attached($app_link_id,$id,$id2); + } + return solink::get_link($app_link_id,$id,$app2,$id2); + } + + /** + * Remove link with $link_id or all links matching given $app,$id + * + * Note: if $link_id != '' and $id is an array: unlink removes links from that array only + * unlink has to be called with &$id to see the result (depricated) or unlink2 has to be used !!! + * + * @param $link_id link-id to remove if > 0 + * @param string $app='' appname of first endpoint + * @param string/array $id='' id in $app or array with links, if 1. entry not yet created + * @param string $app2='' app of second endpoint + * @param string $id2='' id in $app2 + * @return the number of links deleted + */ + function unlink($link_id,$app='',$id='',$owner='',$app2='',$id2='') + { + return $this->unlink2($link_id,$app,$id,$owner,$app2,$id2); + } + + /** + * Remove link with $link_id or all links matching given $app,$id + * + * @param $link_id link-id to remove if > 0 + * @param string $app='' appname of first endpoint + * @param string/array &$id='' id in $app or array with links, if 1. entry not yet created + * @param string $app2='' app of second endpoint + * @param string $id2='' id in $app2 + * @return the number of links deleted + */ + function unlink2($link_id,$app,&$id,$owner='',$app2='',$id2='') + { + if ($this->debug) + { + echo "

bolink::unlink('$link_id','$app','$id','$owner','$app2','$id2')

\n"; + } + if ($link_id < 0) // vfs-link? + { + return $this->delete_attached(-$link_id); + } + elseif ($app == $this->vfs_appname) + { + return $this->delete_attached($app2,$id2,$id); + } + elseif ($app2 == $this->vfs_appname) + { + return $this->delete_attached($app,$id,$id2); + } + if (!is_array($id)) + { + if (!$link_id && !$app2 && !$id2) + { + $this->delete_attached($app,$id); // deleting all attachments + } + $deleted =& solink::unlink($link_id,$app,$id,$owner,$app2,$id2); + + // only notify on real links, not the one cached for writing or fileattachments + $this->notify_unlink($deleted); + + return count($deleted); + } + if (isset($id[$link_id])) + { + $id[$link_id] = False; // set the unlink marker + + return True; + } + return False; + } + + /** + * get list/array of link-aware apps the user has rights to use + * + * @param string $must_support capability the apps need to support, eg. 'add', default ''=list all apps + * @return array with app => title pairs + */ + function app_list($must_support='') + { + $apps = array(); + foreach($this->app_register as $app => $reg) + { + if ($must_support && !isset($reg[$must_support])) continue; + + if ($GLOBALS['egw_info']['user']['apps'][$app]) + { + $apps[$app] = $GLOBALS['egw_info']['apps'][$app]['title']; + } + } + return $apps; + } + + /** + * Searches for a $pattern in the entries of $app + * + * @param string $app app to search + * @param string $pattern pattern to search + * @return array with $id => $title pairs of matching entries of app + */ + function query($app,$pattern) + { + if ($app == '' || !is_array($reg = $this->app_register[$app]) || !isset($reg['query'])) + { + return array(); + } + $method = $reg['query']; + + if ($this->debug) + { + echo "

bolink.query('$app','$pattern') => '$method'

\n"; + } + return strchr($method,'.') ? ExecMethod($method,$pattern) : $this->$method($pattern); + } + + /** + * returns the title (short description) of entry $id and $app + * + * @param string $app appname + * @param string $id id in $app + * @param array $link=null link-data for file-attachments + * @return the title or false if $id does not exist in $app + */ + function title($app,$id,$link=null) + { + if ($this->debug) + { + echo "

bolink::title('$app','$id')

\n"; + } + if ($app == $this->vfs_appname) + { + if (is_array($id) && $link) + { + $link = $id; + $id = $link['name']; + } + if (is_array($link)) + { + $size = $link['size']; + if ($size_k = intval($size / 1024)) + { + if (intval($size_k / 1024)) + { + $size = sprintf('%3.1dM',doubleval($size_k)/1024.0); + } + else + { + $size = $size_k.'k'; + } + } + $extra = ': '.$link['type'] . ' '.$size; + } + return $id.$extra; + } + if ($app == '' || !is_array($reg = $this->app_register[$app]) || !isset($reg['title'])) + { + return array(); + } + $method = $reg['title']; + + $title = strchr($method,'.') ? ExecMethod($method,$id) : $this->$method($id); + + if ($id && !$title) // $app,$id has been deleted ==> unlink als links to it + { + $this->unlink(0,$app,$id); + return False; + } + return $title; + } + + /** + * Add new entry to $app, evtl. already linked to $to_app, $to_id + * + * @param string $app appname of entry to create + * @param string $to_app appname to link the new entry to + * @param string $to_id id in $to_app + * @return array/boolean with name-value pairs for link to add-methode of $app or false if add not supported + */ + function add($app,$to_app='',$to_id='') + { + //echo "

bolink::add('$app','$to_app','$to_id') app_register[$app] ="; _debug_array($app_register[$app]); + if ($app == '' || !is_array($reg = $this->app_register[$app]) || !isset($reg['add'])) + { + return false; + } + $params = $reg['add']; + + if ($reg['add_app'] && $to_app && $reg['add_id'] && $to_id) + { + $params[$reg['add_app']] = $to_app; + $params[$reg['add_id']] = $to_id; + } + return $params; + } + + /** + * view entry $id of $app + * + * @param string $app appname + * @param string $id id in $app + * @param array $link=null link-data for file-attachments + * @return array with name-value pairs for link to view-methode of $app to view $id + */ + function view($app,$id,$link=null) + { + if ($app == $this->vfs_appname && !empty($id) && is_array($link)) + { + return $this->get_file($link); + } + if ($app == '' || !is_array($reg = $this->app_register[$app]) || !isset($reg['view']) || !isset($reg['view_id'])) + { + return array(); + } + $view = $reg['view']; + + $names = explode(':',$reg['view_id']); + if (count($names) > 1) + { + $id = explode(':',$id); + while (list($n,$name) = each($names)) + { + $view[$name] = $id[$n]; + } + } + else + { + $view[$reg['view_id']] = $id; + } + return $view; + } + + /** + * Check if $app uses a popup for $action + * + * @param string $app app-name + * @param string $action='view' name of the action, atm. 'view' or 'add' + * @return boolean/string false if no popup is used or $app is not registered, otherwise string with the prefered popup size (eg. '640x400) + */ + function is_popup($app,$action='view') + { + if (!($reg = $this->app_register[$app]) || !$reg[$action.'_popup']) + { + return false; + } + return $reg[$action.'_popup']; + } + + function get_file($link='') + { + if (is_array($link)) + { + return array( + 'menuaction' => 'phpgapi.bolink.get_file', + 'app' => $link['app2'], + 'id' => $link['id2'], + 'filename' => $link['id'] + ); + } + $app = get_var('app','GET'); + $id = get_var('id','GET'); + $filename = get_var('filename','GET'); + + if (empty($app) || empty($id) || empty($filename) /* || !$this->bo->check_access($info_id,EGW_ACL_READ)*/) + { + $GLOBALS['egw']->redirect_link('/'); + } + $browser =& CreateObject('phpgwapi.browser'); + + $local = $this->attached_local($app,$id,$filename,$_SERVER['REMOTE_ADDR'],$browser->is_windows()); + + if ($local) + { + Header('Location: ' . $local); + } + else + { + $info = $this->info_attached($app,$id,$filename); + $browser->content_header($filename,$info['type']); + echo $this->read_attached($app,$id,$filename); + } + $GLOBALS['egw']->common->egw_exit(); + } + + /** + * path to the attached files of $app/$ip or the directory for $app if no $id,$file given + * + * All link-files are based in the vfs-subdir '/infolog'. For other apps + * separate subdirs with name app are created. + * + * @param string $app appname + * @param string $id='' id in $app + * @param string $file='' filename + * @param boolean/array $relatives=False return path as array with path in string incl. relatives + * @return string/array path or array with path and relatives, depending on $relatives + */ + function vfs_path($app,$id='',$file='',$relatives=False) + { + $path = $this->vfs_basedir . ($app == '' || $app == 'infolog' ? '' : '/'.$app) . + ($id != '' ? '/' . $id : '') . ($file != '' ? '/' . $file : ''); + + if ($this->debug) + { + echo "

bolink::vfs_path('$app','$id','$file') = '$path'

\n"; + } + return $relatives ? array( + 'string' => $path, + 'relatives' => is_array($relatives) ? $relatives : array($relatives) + ) : $path; + } + + /** + * Put a file to the corrosponding place in the VFS and set the attributes + * + * @param string $app appname to linke the file to + * @param string $id id in $app + * @param array $file informations about the file in format of the etemplate file-type + * $file['name'] name of the file (no directory) + * $file['type'] mine-type of the file + * $file['tmp_name'] name of the uploaded file (incl. directory) + * $file['path'] path of the file on the client computer + * $file['ip'] of the client (path and ip are only needed if u want a symlink (if possible)) + * @param string $comment='' comment to add to the link + * @return int negative id of phpgw_vfs table as negative link-id's are for vfs attachments + */ + function attach_file($app,$id,$file,$comment='') + { + if ($this->debug) + { + echo "

attach_file: app='$app', id='$id', tmp_name='$file[tmp_name]', name='$file[name]', size='$file[size]', type='$file[type]', path='$file[path]', ip='$file[ip]', comment='$comment'

\n"; + } + // create the root for attached files in infolog, if it does not exists + $vfs_data = array('string'=>$this->vfs_basedir,'relatives'=>array(RELATIVE_ROOT)); + if (!($this->vfs->file_exists($vfs_data))) + { + $this->vfs->override_acl = 1; + $this->vfs->mkdir($vfs_data); + $this->vfs->override_acl = 0; + } + + $vfs_data = $this->vfs_path($app,False,False,RELATIVE_ROOT); + if (!($this->vfs->file_exists($vfs_data))) + { + $this->vfs->override_acl = 1; + $this->vfs->mkdir($vfs_data); + $this->vfs->override_acl = 0; + } + $vfs_data = $this->vfs_path($app,$id,False,RELATIVE_ROOT); + if (!($this->vfs->file_exists($vfs_data))) + { + $this->vfs->override_acl = 1; + $this->vfs->mkdir($vfs_data); + $this->vfs->override_acl = 0; + } + $fname = $this->vfs_path($app,$id,$file['name']); + $tfname = ''; + if (!empty($file['path']) && is_array($this->link_pathes) && count($this->link_pathes)) + { + $file['path'] = str_replace('\\\\','/',$file['path']); // vfs uses only '/' + @reset($this->link_pathes); + while ((list($valid,$trans) = @each($this->link_pathes)) && !$tfname) + { // check case-insensitive for WIN etc. + $check = $valid[0] == '\\' || strstr(':',$valid) ? 'eregi' : 'ereg'; + $valid2 = str_replace('\\','/',$valid); + //echo "

attach_file: ereg('".$this->send_file_ips[$valid]."', '$file[ip]')=".ereg($this->send_file_ips[$valid],$file['ip'])."

\n"; + if ($check('^('.$valid2.')(.*)$',$file['path'],$parts) && + ereg($this->send_file_ips[$valid],$file['ip']) && // right IP + $this->vfs->file_exists(array('string'=>$trans.$parts[2],'relatives'=>array(RELATIVE_NONE|VFS_REAL)))) + { + $tfname = $trans.$parts[2]; + } + //echo "

attach_file: full_fname='$file[path]', valid2='$valid2', trans='$trans', check=$check, tfname='$tfname', parts=(x,'${parts[1]}','${parts[2]}')

\n"; + } + if ($tfname && !$this->vfs->securitycheck(array('string'=>$tfname))) + { + return False; //lang('Invalid filename').': '.$tfname; + } + } + $this->vfs->override_acl = 1; + $this->vfs->cp(array( + 'symlink' => !!$tfname, // try a symlink + 'from' => $tfname ? $tfname : $file['tmp_name'], + 'to' => $fname, + 'relatives' => array(RELATIVE_NONE|VFS_REAL,RELATIVE_ROOT), + )); + $this->vfs->set_attributes(array( + 'string' => $fname, + 'relatives' => array (RELATIVE_ROOT), + 'attributes' => array ( + 'mime_type' => $file['type'], + 'comment' => stripslashes ($comment), + 'app' => $app + ))); + $this->vfs->override_acl = 0; + + $link = $this->info_attached($app,$id,$file['name']); + + return is_array($link) ? $link['link_id'] : False; + } + + /** + * deletes an attached file + * + * @param int/string $app > 0: file_id of an attchemnt or $app/$id entry which linked to + * @param string $id='' id in app + * @param string $fname filename + */ + function delete_attached($app,$id='',$fname = '') + { + if (intval($app) > 0) // is file_id + { + $link = $this->fileinfo2link($file_id=$app); + $app = $link['app2']; + $id = $link['id2']; + $fname = $link['id']; + } + if ($this->debug) + { + echo "

bolink::delete_attached('$app','$id','$fname') file_id=$file_id

\n"; + } + if (empty($app) || empty($id)) + { + return False; // dont delete more than all attachments of an entry + } + $vfs_data = $this->vfs_path($app,$id,$fname,RELATIVE_ROOT); + + $Ok = false; + if ($this->vfs->file_exists($vfs_data)) + { + $this->vfs->override_acl = 1; + $Ok = $this->vfs->delete($vfs_data); + $this->vfs->override_acl = 0; + } + // if filename given (and now deleted) check if dir is empty and remove it in that case + if ($fname && !count($this->vfs->ls($vfs_data=$this->vfs_path($app,$id,'',RELATIVE_ROOT)))) + { + $this->vfs->override_acl = 1; + $this->vfs->delete($vfs_data); + $this->vfs->override_acl = 0; + } + return $Ok; + } + + /** + * converts the infos vfs has about a file into a link + * + * @param string $app appname + * @param string $id id in app + * @param string $filename filename + * @return array 'kind' of link-array + */ + function info_attached($app,$id,$filename) + { + $this->vfs->override_acl = 1; + $attachments = $this->vfs->ls($this->vfs_path($app,$id,$filename,RELATIVE_NONE)); + $this->vfs->override_acl = 0; + + if (!count($attachments) || !$attachments[0]['name']) + { + return False; + } + return $this->fileinfo2link($attachments[0]); + } + + /** + * converts a fileinfo (row in the vfs-db-table) in a link + * + * @param array/int $fileinfo a row from the vfs-db-table (eg. returned by the vfs ls function) or a file_id of that table + * @return array a 'kind' of link-array + */ + function fileinfo2link($fileinfo) + { + if (!is_array($fileinfo)) + { + $fileinfo = $this->vfs->ls(array('file_id' => $fileinfo)); + list(,$fileinfo) = each($fileinfo); + + if (!is_array($fileinfo)) + { + return False; + } + } + $lastmod = $fileinfo[!empty($fileinfo['modified']) ? 'modified' : 'created']; + list($y,$m,$d) = explode('-',$lastmod); + $lastmod = mktime(0,0,0,$m,$d,$y); + + $dir_parts = array_reverse(explode('/',$fileinfo['directory'])); + + return array( + 'app' => $this->vfs_appname, + 'id' => $fileinfo['name'], + 'app2' => $dir_parts[1], + 'id2' => $dir_parts[0], + 'remark' => $fileinfo['comment'], + 'owner' => $fileinfo['owner_id'], + 'link_id' => -$fileinfo['file_id'], + 'lastmod' => $lastmod, + 'size' => $fileinfo['size'], + 'type' => $fileinfo['mime_type'] + ); + } + + /** + * lists all attachments to $app/$id + * + * @param string $app appname + * @param string $id id in app + * @return array with link_id => 'kind' of link-array pairs + */ + function list_attached($app,$id) + { + $this->vfs->override_acl = 1; + $attachments = $this->vfs->ls($this->vfs_path($app,$id,False,RELATIVE_ROOT)); + $this->vfs->override_acl = 0; + + if (!count($attachments) || !$attachments[0]['name']) + { + return False; + } + foreach($attachments as $fileinfo) + { + $link = $this->fileinfo2link($fileinfo); + $attached[$link['link_id']] = $link; + } + return $attached; + } + + /** + * checks if path starts with a '\\' or has a ':' in it + * + * @param string $path path to check + * @return boolean true if windows path, false otherwise + */ + function is_win_path($path) + { + return $path[0] == '\\' || strstr($path,':'); + } + + /** + * reads the attached file and returns the content + * + * @param string $app appname + * @param string $id id in app + * @param string $filename filename + * @return string content of the attached file + */ + function read_attached($app,$id,$filename) + { + if (empty($app) || !$id || empty($filename) /*|| !$this->check_access($info_id,EGW_ACL_READ)*/) + { + return False; + } + $this->vfs->override_acl = 1; + $data = $this->vfs->read($this->vfs_path($app,$id,$filename,RELATIVE_ROOT)); + $this->vfs->override_acl = 0; + return $data; + } + + /** + * Checks if filename should be local availible and if so returns + * + * @param string $app appname + * @param string $id id in app + * @param string $filename filename + * @param string $id ip-address of user + * @param boolean $win_user true if user is on windows, otherwise false + * @return string 'file:/path' for HTTP-redirect else return False + */ + function attached_local($app,$id,$filename,$ip,$win_user) + { + //echo "

attached_local(app=$app, id='$id', filename='$filename', ip='$ip', win_user='$win_user', count(send_file_ips)=".count($this->send_file_ips).")

\n"; + + if (!$id || !$filename || /* !$this->check_access($info_id,EGW_ACL_READ) || */ + !count($this->send_file_ips)) + { + return False; + } + $link = $this->vfs->ls($this->vfs_path($app,$id,$filename,RELATIVE_ROOT)+array('readlink'=>True)); + $link = @$link[0]['symlink']; + + if ($link && is_array($this->link_pathes)) + { + reset($this->link_pathes); $fname = ''; + while ((list($valid,$trans) = each($this->link_pathes)) && !$fname) + { + if (!$this->is_win_path($valid) == !$win_user && // valid for this OS + $win_user && // only for IE/windows atm + eregi('^'.$trans.'(.*)$',$link,$parts) && // right path + ereg($this->send_file_ips[$valid],$ip)) // right IP + { + $fname = $valid . $parts[1]; + $fname = !$win_user ? str_replace('\\','/',$fname) : str_replace('/','\\',$fname); + return 'file:'.($win_user ? '//' : '' ).$fname; + } + //echo "

attached_local: link=$link, valid=$valid, trans='$trans', fname='$fname', parts=(x,'${parts[1]}','${parts[2]}')

\n"; + } + } + return False; + } + + /** + * reverse function of htmlspecialchars() + * + * @param string $str string to decode + * @return string decoded string + */ + function decode_htmlspecialchars($str) + { + return str_replace(array('&','"','<','>'),array('&','"','<','>'),$str); + } + + /** + * get title for an event, should be moved to bocalendar.link_title + * + * @param int/array $event event-id or already read event + * @return string/boolean the title (startdate plus subject), of false if event is not found + */ + function calendar_title( $event ) + { + if (!is_object($this->bocal)) + { + $this->bocal = createobject('calendar.bocalendar'); + } + if (!is_array($event) && (int) $event > 0) + { + $event = $this->bocal->read_entry($event); + } + if (!is_array($event)) + { + return False; + } + $format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'] . ' '. + ($GLOBALS['egw_info']['user']['preferences']['common']['timeformat'] == '12' ? 'h:i a' : 'H:i'); + + $name = $GLOBALS['egw']->common->show_date($this->bocal->maketime($event['start']) - $this->bocal->datetime->tz_offset,$format); + $name .= ': ' . $this->decode_htmlspecialchars($event['title']); // cal returns entities + + return $name; + } + + /** + * query calendar for an event $matching pattern, should be moved to bocalendar.link_query + * + * @param string $pattern pattern to search + * @return array with id => title pairs of matching events + */ + function calendar_query($pattern) + { + if (!is_object($this->bocal)) + { + $this->bocal = createobject('calendar.bocalendar'); + } + $content = array( ); + + if ($event_ids = $this->bocal->search_keywords($pattern)) + { + foreach($event_ids as $id) + { + // only include it in the list, if we have permissions to read it + if ($this->bocal->check_perms(EGW_ACL_READ,$id)) + { + $content[$id] = $this->calendar_title( $id ); + } + } + } + return $content; + } + + /** + * get title for an address, should be moved to boaddressbook.link_title + * + * @param int/array $event address-id or already read address + * @return string/boolean the title ([org:] n_familiy[, n_given|n_prefix]), of false if address is not found + */ + function addressbook_title( $addr ) + { + if (!is_object($this->contacts)) + { + $this->contacts = createobject('phpgwapi.contacts'); + } + if (!is_array($addr)) + { + list( $addr ) = $this->contacts->read_single_entry( $addr ); + } + if (!is_array($addr)) + { + return False; + } + $name = $addr['n_family']; + if ($addr['n_given']) + { + $name .= ', '.$addr['n_given']; + } + else + { + if ($addr['n_prefix']) + { + $name .= ', '.$addr['n_prefix']; + } + } + if ($addr['org_name']) + { + $name = $addr['org_name'].($name !== '' ? ': '.$name : ''); + } + return stripslashes($name); // addressbook returns quotes with slashes + } + + /** + * query addressbook for $pattern, should be moved to boaddressbook.link_query + * + * @param string $pattern pattern to search + * @return array with id => title pairs of matching addresses + */ + function addressbook_query( $pattern ) + { + if (!is_object($this->contacts)) + { + $this->contacts = createobject('phpgwapi.contacts'); + } + $addrs = $this->contacts->read( 0,0,'',$pattern,'','DESC','org_name,n_family,n_given' ); + $content = array( ); + while ($addrs && list( $key,$addr ) = each( $addrs )) + { + $content[$addr['id']] = $this->addressbook_title( $addr ); + } + return $content; + } + + /** + * get title for a project, should be moved to boprojects.link_title + * + * @param int/array $event project-id or already read project + * @return string/boolean the title (number: title), of false if address is not found + */ + function projects_title( $proj ) + { + if (!is_object($this->boprojects)) + { + if (!file_exists(EGW_SERVER_ROOT.'/projects')) // check if projects installed + return ''; + $this->boprojects = createobject('projects.boprojects'); + } + if (!is_array($proj)) + { + $proj = $this->boprojects->read_single_project( $proj ); + } + return is_array($proj) ? $proj['number'].': '.$proj['title'] : False; + } + + /** + * query for projects matching $pattern, should be moved to boprojects.link_query + * + * @param string $pattern pattern to search + * @return array with id => title pairs of matching projects + */ + function projects_query( $pattern ) + { + if (!is_object($this->boprojects)) + { + if (!file_exists(EGW_SERVER_ROOT.'/projects')) // check if projects installed + return array(); + $this->boprojects = createobject('projects.boprojects'); + } + $projs = $this->boprojects->list_projects( array('action'=>'all','query'=>$pattern,'limit'=>FALSE) ); + $content = array(); + while ($projs && list( $key,$proj ) = each( $projs )) + { + $content[$proj['project_id']] = $this->projects_title($proj); + } + return $content; + } + + /** + * notify other apps about changed content in $app,$id + * + * @param string $app name of app in which the updated happend + * @param string $id id in $app of the updated entry + * @param array $data=null updated data of changed entry, as the read-method of the BO-layer would supply it + */ + function notify_update($app,$id,$data=null) + { + foreach($this->get_links($app,$id,'!'.$this->vfs_appname) as $link_id => $link) + { + $this->notify('update',$link['app'],$link['id'],$app,$id,$link_id,$data); + } + } + + /** + * notify an application about a new or deleted links to own entries or updates in the content of the linked entry + * + * Please note: not all apps supply update notifications + * + * @internal + * @param string $type 'link' for new links, 'unlink' for unlinked entries, 'update' of content in linked entries + * @param string $notify_app app to notify + * @param string $notify_id id in $notify_app + * @param string $target_app name of app whos entry changed, linked or deleted + * @param string $target_id id in $target_app + * @param array $data=null data of entry in app2 (optional) + */ + function notify($type,$notify_app,$notify_id,$target_app,$target_id,$link_id,$data=null) + { + if ($link_id && isset($this->app_register[$notify_app]) && isset($this->app_register[$notify_app]['notify'])) + { + ExecMethod($this->app_register[$notify_app]['notify'],array( + 'type' => $type, + 'id' => $notify_id, + 'target_app' => $target_app, + 'target_id' => $target_id, + 'link_id' => $link_id, + 'data' => $data, + )); + } + } + + /** + * notifies about unlinked links + * + * @internal + * @param array &$links unlinked links from the database + */ + function notify_unlink(&$links) + { + foreach($links as $link) + { + // we notify both sides of the link, as the unlink command NOT clearly knows which side initiated the unlink + $this->notify('unlink',$link['link_app1'],$link['link_id1'],$link['link_app2'],$link['link_id2'],$link['link_id']); + $this->notify('unlink',$link['link_app2'],$link['link_id2'],$link['link_app1'],$link['link_id1'],$link['link_id']); + } + } + } \ No newline at end of file diff --git a/infolog/inc/class.solink.inc.php b/phpgwapi/inc/class.solink.inc.php similarity index 97% rename from infolog/inc/class.solink.inc.php rename to phpgwapi/inc/class.solink.inc.php index 8ca36412ba..7a0d543e73 100644 --- a/infolog/inc/class.solink.inc.php +++ b/phpgwapi/inc/class.solink.inc.php @@ -1,6 +1,6 @@ * * -------------------------------------------- * @@ -13,9 +13,7 @@ /* $Id$ */ /** - * generalized linking between entries of eGroupware apps - BO layer - * - * This class is the SO-layer of the links + * generalized linking between entries of eGroupware apps - SO layer * * Links have two ends each pointing to an entry, each entry is a double: * - app app-name or directory-name of an egw application, eg. 'infolog' @@ -23,7 +21,7 @@ * * All vars passed to this class get correct escaped to prevent query insertion. * - * @package infolog + * @package API * @subpackage link * @author RalfBecker-At-outdoor-training.de * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License @@ -41,7 +39,7 @@ function solink( ) { $this->db = clone($GLOBALS['egw']->db); - $this->db->set_app('infolog'); + $this->db->set_app('phpgwapi'); $this->user = $GLOBALS['egw_info']['user']['account_id']; } diff --git a/phpgwapi/setup/setup.inc.php b/phpgwapi/setup/setup.inc.php index 3361669778..252427e350 100755 --- a/phpgwapi/setup/setup.inc.php +++ b/phpgwapi/setup/setup.inc.php @@ -14,7 +14,7 @@ /* Basic information about this app */ $setup_info['phpgwapi']['name'] = 'phpgwapi'; $setup_info['phpgwapi']['title'] = 'phpgwapi'; - $setup_info['phpgwapi']['version'] = '1.0.1.011'; + $setup_info['phpgwapi']['version'] = '1.0.1.012'; $setup_info['phpgwapi']['versions']['current_header'] = '1.28'; $setup_info['phpgwapi']['enable'] = 3; $setup_info['phpgwapi']['app_order'] = 1; @@ -53,6 +53,7 @@ $setup_info['phpgwapi']['tables'][] = 'egw_contentmap'; $setup_info['phpgwapi']['tables'][] = 'egw_syncmldevinfo'; $setup_info['phpgwapi']['tables'][] = 'egw_syncmlsummary'; + $setup_info['phpgwapi']['tables'][] = 'egw_links'; diff --git a/phpgwapi/setup/tables_current.inc.php b/phpgwapi/setup/tables_current.inc.php index 2bbae0acf4..b8b300c77a 100644 --- a/phpgwapi/setup/tables_current.inc.php +++ b/phpgwapi/setup/tables_current.inc.php @@ -549,5 +549,21 @@ 'fk' => array(), 'ix' => array(), 'uc' => array() - ) + ), + 'egw_links' => array( + 'fd' => array( + 'link_id' => array('type' => 'auto','nullable' => False), + 'link_app1' => array('type' => 'varchar','precision' => '25','nullable' => False), + 'link_id1' => array('type' => 'varchar','precision' => '50','nullable' => False), + 'link_app2' => array('type' => 'varchar','precision' => '25','nullable' => False), + 'link_id2' => array('type' => 'varchar','precision' => '50','nullable' => False), + 'link_remark' => array('type' => 'varchar','precision' => '50'), + 'link_lastmod' => array('type' => 'int','precision' => '4','nullable' => False), + 'link_owner' => array('type' => 'int','precision' => '4','nullable' => False) + ), + 'pk' => array('link_id'), + 'fk' => array(), + 'ix' => array(array('link_app1','link_id1','link_lastmod'),array('link_app2','link_id2','link_lastmod')), + 'uc' => array() + ), ); diff --git a/phpgwapi/setup/tables_update.inc.php b/phpgwapi/setup/tables_update.inc.php index d1a091a198..58be87b161 100644 --- a/phpgwapi/setup/tables_update.inc.php +++ b/phpgwapi/setup/tables_update.inc.php @@ -543,4 +543,40 @@ $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.0.1.011'; return $GLOBALS['setup_info']['phpgwapi']['currentver']; } + + $test[] = '1.0.1.011'; + function phpgwapi_upgrade1_0_1_011() + { + // moving the egw_links table into the API + if ($GLOBALS['phpgw_setup']->oProc->GetTableDefinition('phpgw_links')) + { + // table exists with old name ==> rename it to new one + $GLOBALS['phpgw_setup']->oProc->RenameTable('phpgw_links','egw_links'); + } + elseif (!$GLOBALS['phpgw_setup']->oProc->GetTableDefinition('egw_links')) + { + // table does not exist at all (infolog not installed) ==> create it + $GLOBALS['phpgw_setup']->oProc->CreateTable('egw_links',array( + 'fd' => array( + 'link_id' => array('type' => 'auto','nullable' => False), + 'link_app1' => array('type' => 'varchar','precision' => '25','nullable' => False), + 'link_id1' => array('type' => 'varchar','precision' => '50','nullable' => False), + 'link_app2' => array('type' => 'varchar','precision' => '25','nullable' => False), + 'link_id2' => array('type' => 'varchar','precision' => '50','nullable' => False), + 'link_remark' => array('type' => 'varchar','precision' => '50'), + 'link_lastmod' => array('type' => 'int','precision' => '4','nullable' => False), + 'link_owner' => array('type' => 'int','precision' => '4','nullable' => False) + ), + 'pk' => array('link_id'), + 'fk' => array(), + 'ix' => array(array('link_app1','link_id1','link_lastmod'),array('link_app2','link_id2','link_lastmod')), + 'uc' => array() + )); + } + // move the link-configuration to the api + $GLOBALS['phpgw_setup']->oProc->query('UPDATE '.$GLOBALS['phpgw_setup']->config_table." SET config_app='phpgwapi' WHERE config_app='infolog' AND config_name IN ('link_pathes','send_file_ips')",__LINE__,__FILE__); + + $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.0.1.012'; + return $GLOBALS['setup_info']['phpgwapi']['currentver']; + } ?> \ No newline at end of file