diff --git a/timesheet/inc/class.botimesheet.inc.php b/timesheet/inc/class.botimesheet.inc.php index 9edde8e914..3468f644ef 100644 --- a/timesheet/inc/class.botimesheet.inc.php +++ b/timesheet/inc/class.botimesheet.inc.php @@ -30,6 +30,14 @@ class botimesheet extends so_sql * @var array */ var $config = array(); + + + /** + * Timesheets config data + * + * @var array + */ + var $config_data = array(); /** * Timestaps that need to be adjusted to user-time on reading or saving * @@ -100,14 +108,21 @@ class botimesheet extends so_sql */ var $show_sums; + var $customfields; + function botimesheet() { $this->so_sql(TIMESHEET_APP,'egw_timesheet'); - $config =& CreateObject('phpgwapi.config',TIMESHEET_APP); - $config->read_repository(); - $this->config =& $config->config_data; - unset($config); + $this->config =& CreateObject('phpgwapi.config',TIMESHEET_APP); + $this->config->read_repository(); + $this->config_data =& $this->config->config_data; + //unset($config); + + if (isset($this->config_data['customfields']) && is_array($this->config_data['customfields'])) + { + $this->customfields = $this->config_data['customfields']; + } if (!is_object($GLOBALS['egw']->datetime)) { @@ -361,8 +376,33 @@ class botimesheet extends so_sql { return $ret; // no read rights, or entry not found } + + //assign custom fields + foreach($this->customfields as $name => $value) { + $row = $this->read_extra($name); + $this->data['#'.$name] = $row['ts_extra_value']; + } + return $this->data; } + + /** + * reads a timesheet extra entry of the current timesheet dataset + * + * @param int $name => name of the current timesheet extra entry + * @param int $value => value of the current timesheet extra entry + * @return array of resultset + */ + function read_extra($name='',$value='') + { + strlen($value) > 0 ? $where = ' and ts_extra_value ='.$this->db->quote($value) : ''; + strlen($name) > 0 ? $where .= ' and ts_extra_name ='.$this->db->quote($name) : ''; + + $this->db->select('egw_timesheet_extra', 'ts_extra_name, ts_extra_value',$query,__LINE__,__FILE__,False,'',False,0,'where ts_id='.$this->data['ts_id'].$where); + $row = $this->db->row(true); + + return $row; + } /** * saves a timesheet entry @@ -389,11 +429,50 @@ class botimesheet extends so_sql } if (!($err = parent::save())) { + //saves data of custom fields in timesheet_extra + $this->save_extra(); + // notify the link-class about the update, as other apps may be subscribt to it $this->link->notify_update(TIMESHEET_APP,$this->data['ts_id'],$this->data); } return $err; } + + /** + * saves a timesheet extra entry based one the "custom fields" settings + * + * @param boolean $updateNames => if true "change timesheet extra name", otherwise update existing datasets or insert new ones + * @param boolean $oldname => original name of the timesheet extra entry + * @param boolean $name => new name of the timesheet extra entry + * @return int true on success else false + */ + function save_extra($updateNames=False,$oldname='',$name='') + { + if($updateNames) { + $keys = array('ts_extra_name' => $oldname); + $fieldAssign = array('ts_extra_name' => $name); + $this->db->update('egw_timesheet_extra',$fieldAssign,$keys,__LINE__,__FILE__); + return true; + } + else { + foreach($this->customfields as $namecf => $valuecf) { + //if entry not exist => insert + if(!$this->read_extra($namecf)) { + $fieldAssign = array('ts_id' => $this->data['ts_id'],'ts_extra_name' => $namecf,'ts_extra_value' => $this->data['#'.$namecf]); + $this->db->insert('egw_timesheet_extra',$fieldAssign,false,__LINE__,__FILE__); + } + //otherwise update existing dataset + else { + $keys = array('ts_extra_name' => $namecf, 'ts_id' => $this->data['ts_id']); + $fieldAssign = array('ts_extra_value' => $this->data['#'.$namecf]); + $this->db->update('egw_timesheet_extra',$fieldAssign,$keys,__LINE__,__FILE__); + } + } + return true; + } + + return false; + } /** * deletes a timesheet entry identified by $keys or the loaded one, reimplemented to notify the link class (unlink) @@ -416,12 +495,35 @@ class botimesheet extends so_sql } if (($ret = parent::delete($keys)) && $ts_id) { + //delete custom fields entries + $this->delete_extra($ts_id); + // delete all links to timesheet entry $ts_id $this->link->unlink(0,TIMESHEET_APP,$ts_id); } return $ret; } + + /** + * deletes a timesheet extra entry identified by $ts_id and/or $ts_exra_name + * + * @param int $ts_id => number of timesheet + * @param string ts_extra_name => certain custom field name + * @return int false if an error + */ + function delete_extra($ts_id='',$ts_extra_name='') + { + strlen($ts_id) > 0 ? $where['ts_id'] = $ts_id : ''; + strlen($ts_extra_name) > 0 ? $where['ts_extra_name'] = $ts_extra_name : ''; + + if(count($where) > 0) { + return $this->db->delete('egw_timesheet_extra', $where,__LINE__,__FILE__); + } + + return false; + } + /** * changes the data from the db-format to your work-format * @@ -578,4 +680,59 @@ class botimesheet extends so_sql } return $rows; } + + /** + * updates the project titles in the timesheet application (called whenever a project name is changed in the project manager) + * + * Todo: implement via notification + * + * @param string $oldtitle => the origin title of the project + * @param string $newtitle => the new title of the project + * @return boolean true for success, false for invalid parameters + */ + function update_ts_project($oldtitle='', $newtitle='') + { + if(strlen($oldtitle) > 0 && strlen($newtitle) > 0) { + $keys = array('ts_project' => $oldtitle); + $fieldAssign = array('ts_project' => $newtitle,'ts_title' => $newtitle); + $this->db->update('egw_timesheet',$fieldAssign,$keys,__LINE__,__FILE__); + + return true; + } + + return false; + } + + /** + * returns array with relation link_id and ts_id (necessary for project-selection) + * + * @param int $pm_id ID of selected project + * @return array containing link_id and ts_id + */ + function get_ts_links($pm_id=0) { + $tslist = array(); + if(strlen($pm_id) > 0) { + if(isset($GLOBALS['egw_info']['user']['apps']['projectmanager'])) { + $bo_pm = CreateObject('projectmanager.boprojectmanager'); + $childs = $bo_pm->children($pm_id); + $childs[] = $pm_id; + $pmChilds = implode(",",$childs); + $this->db->select( 'egw_links','link_id, link_id1',$query, + __LINE__,__FILE__,False, + '',False,0, + 'JOIN egw_pm_projects ON (pm_id = link_id2) + WHERE + link_app1 = \'timesheet\' AND + link_app2 = \'projectmanager\' AND + link_id2 IN ('.$pmChilds.')'); + + while($row = $this->db->row(true)) { + $tslist[$row['link_id']] = $row['link_id1']; + } + + } + + } + return $tslist; + } } \ No newline at end of file diff --git a/timesheet/inc/class.egw_timesheet_record.inc.php b/timesheet/inc/class.egw_timesheet_record.inc.php new file mode 100644 index 0000000000..ee5f6be81c --- /dev/null +++ b/timesheet/inc/class.egw_timesheet_record.inc.php @@ -0,0 +1,152 @@ +, Knut Moeller + * @copyright Cornelius Weiss , Knut Moeller + * @version $Id: class.egw_addressbook_record.inc.php 22827 2006-11-10 15:35:35Z nelius_weiss $ + */ + +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.iface_egw_record.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/timesheet/inc/class.botimesheet.inc.php'); + +/** + * class egw_addressbook_record + * compability layer for iface_egw_record needet for importexport + */ +class egw_timesheet_record implements iface_egw_record +{ + + private $identifier = ''; + private $timesheetentry = array(); + private $botimesheet; + + + + /** + * constructor + * reads record from backend if identifier is given. + * + * @param string $_identifier + */ + public function __construct( $_identifier='' ){ + $this->identifier = $_identifier; + $this->botimesheet = new botimesheet(); + $this->timesheetentry = $this->botimesheet->read($this->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() { + return $this->timesheetentry; + } + + /** + * gets title of record + * + *@return string title + */ + public function get_title() { +// TODO get_record gibts nicht ? +// if (empty($this->timesheetentry)) { +// $this->get_record(); +// } + return $this->timesheetentry['ts_project'] . ' - ' . $this->timesheetentry['ts_title']; + } + + /** + * sets complete record from associative array + * + * @todo add some checks + * @return void + */ + public function set_record(array $_record){ + $this->timesheetentry = $_record; + } + + /** + * gets identifier of this record + * + * @return string identifier of current record + */ + public function get_identifier() { + return $this->identifier; + } + + /** + * saves record into backend + * + * @return string identifier + */ + public function save ( $_dst_identifier ) { + + } + + /** + * copies 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 + * + */ + public function delete () { + + } + + /** + * destructor + * + */ + public function __destruct() { + unset ($this->botimesheet); + } + +} // end of egw_timesheet_record +?> diff --git a/timesheet/inc/class.export_openoffice.inc.php b/timesheet/inc/class.export_openoffice.inc.php new file mode 100644 index 0000000000..b5521bd835 --- /dev/null +++ b/timesheet/inc/class.export_openoffice.inc.php @@ -0,0 +1,515 @@ + + * @copyright Knut Moeller + * @version $Id: $ + */ + +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.iface_export_record.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.import_export_helper_functions.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.iface_egw_record.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/timesheet/inc/class.spreadsheet.inc.php'); + +/** + * class export_cmslite + * + * special export class (metaways openoffice calc table) + * adapter to spreadsheet class + * + */ +class export_openoffice implements iface_export_record +{ + + /** + * 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; + + /** + * holds number of exported records + * @var unknown_type + */ + protected $num_of_records = 0; + + /** + * stream resource of csv file + * @var resource + */ + protected $handle; + + protected $document; + protected $table; + + protected $summarytable; // link to first table + protected $summary; // array, project -> user -> time + protected $summaryweekend; // same for weekend + + private $tablecount; + private $columncount; + private $rowcount; + + + + /** + * 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->handle = $_handle; + } + + /** + * sets field mapping + * + * @param array $_mapping egw_field_name => csv_field_name + */ + public function set_mapping( array $_mapping) { + + } + + /** + * Sets conversion. + * See import_export_helper_functions::conversion. + * + * @param array $_conversion + */ + public function set_conversion( array $_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 ) { + + } + + public function create_summarytable($_tablename='Gesamtzeiten') { + $this->summarytable = new SpreadSheetTable($this->document, $_tablename); + $this->tablecount++; + $this->summary = array(); + $this->summaryweekend = array(); + } + + + /** + * @return weekdate array (1-7) for given week + */ + private function week_dates($_timestamp) { + $week_dates = array(); + $day = (int) date("w", $_timestamp); + + if ($day != 1) { + $diff = $day - 1; + if ($day==0) $diff = 6; + $monday = strtotime("-".$diff." days" , $_timestamp); + } + else { + $monday = $_timestamp; + } + + for ($i=0; $i < 7; $i++) { + $week_dates[$i+1] = strtotime("+".$i." days", $monday); + } + return($week_dates); + } + + + /** + * + * + */ + public function fill_summarytable($_tstamp_min, $_tstamp_max) { + + // prepare data -------------------------- + + uksort($this->summary, "strnatcasecmp"); + uksort($this->summaryweekend, "strnatcasecmp"); + + // get user-array + $users = array(); + foreach ($this->summary as $project => $user_time) { + foreach($user_time as $user => $time) { + if (!in_array($user, $users)) $users[] = $user; + } + } + asort($users); + $usersweekend = array(); + foreach ($this->summaryweekend as $project => $user_time) { + foreach($user_time as $user => $time) { + if (!in_array($user, $usersweekend)) $usersweekend[] = $user; + } + } + asort($usersweekend); + + + // get project-array (sites) + $projects = array(); + foreach ($this->summary as $project => $user_time) { + if (!in_array($project ,$projects)) $projects[] = $project; + } + foreach ($this->summaryweekend as $project => $user_time) { + if (!in_array($project ,$projects)) $projects[] = $project; + } + asort($projects); + + $this->summarytable->addColumn('co20'); + for ($i=1; $i <= count($users) + count($usersweekend) + 2; $i++) { + $this->summarytable->addColumn(); + } + + + // populate table -------------------------- + + + // create table-headlines + $row = $this->summarytable->addRow(); + $this->summarytable->addCell($row, 'string', 'CMS Lite Support / Montag - Freitag / 08.00 Uhr - 18.00 Uhr', array('bold')); + + for ($i=0; $isummarytable->addCell($row, 'string', ''); + } + $this->summarytable->addCell($row, 'string', 'CMS Lite Support / Wochenende', array('bold')); + + + // headline, row 1 + $row = $this->summarytable->addRow(); + $this->summarytable->addCell($row, 'string', 'Mitarbeiter:', array('bold')); + + foreach ($users as $user) { + $this->summarytable->addCell($row, 'string', $user, array('bold')); + } + $this->summarytable->addCell($row, 'string', 'Mitarbeiter:', array('bold')); + foreach ($usersweekend as $user) { + $this->summarytable->addCell($row, 'string', $user, array('bold')); + } + + // fixed date rows, row 2 + $row = $this->summarytable->addRow(); + $this->summarytable->addCell($row, 'string', 'Zeitraum:', array('bold')); + $kw_min = strftime("%V", $_tstamp_min); + $kw_max = strftime("%V", $_tstamp_max); + $kw = ($kw_min == $kw_max) ? "KW $kw_min" : "KW $kw_min - KW $kw_max"; + for ($i=0; $i < count($users); $i++) { + $this->summarytable->addCell($row, 'string', $kw, array('bold')); + } + $this->summarytable->addCell($row, 'string', 'Zeitraum:', array('bold')); + for ($i=0; $i < count ($usersweekend); $i++) { + $this->summarytable->addCell($row, 'string', $kw, array('bold')); + } + + + // weekdays, row 3 left + $row = $this->summarytable->addRow(); + $this->summarytable->addCell($row, 'string', '', array('bold')); + + $week_dates = $this->week_dates($_tstamp_min); + + if ($kw_min != $kw_max) { + $days = strftime("%d.%m.%y", $_tstamp_min).' - '.strftime("%d.%m.%y", $_tstamp_max); + } + else { + // monday-friday + $days = strftime("%d.%m.%y", $week_dates[1]).' - '.strftime("%d.%m.%y", $week_dates[5]); + } + for ($i=0; $i < count($users); $i++) { + $this->summarytable->addCell($row, 'string', $days, array('bold')); + } + + // weekend, row 3 right + $this->summarytable->addCell($row, 'string', '', array('bold')); + if ($kw_min != $kw_max) { + $days = strftime("%d.%m.%y", $_tstamp_min).' - '.strftime("%d.%m.%y", $_tstamp_max); + } + else { + $days = strftime("%d.%m.%y", $week_dates[6]).' - '.strftime("%d.%m.%y", $week_dates[7]); + } + for ($i=0; $i < count($usersweekend); $i++) { + $this->summarytable->addCell($row, 'string', $days, array('bold')); + } + + $this->rowcount = 4; + + + + // project lines (sitenames) + foreach ($projects as $project) { + + // 1.Cell: projectname + $row = $this->summarytable->addRow(); + $this->rowcount++; + $this->summarytable->addCell($row, 'string', $project); + + // iterate all users for each line + foreach ($users as $user) { + + if (array_key_exists($project, $this->summary) && array_key_exists($user, $this->summary[$project])) { + $this->summarytable->addCell($row, 'float', (float) ($this->summary[$project][$user] / 60) ); + } + else { // add empty cell if no user-entry + $this->summarytable->addCell($row, 'string', ''); + } + } + $this->summarytable->addCell($row, 'string', ''); + foreach ($usersweekend as $user) { + + // weekend + if (array_key_exists($project, $this->summaryweekend) && array_key_exists($user, $this->summaryweekend[$project])) { + $this->summarytable->addCell($row, 'float', (float) ($this->summaryweekend[$project][$user] / 60) ); + } + else { // add empty cell if no user-entry + $this->summarytable->addCell($row, 'string', ''); + } + } + } + + + + // summary line 1 + $row = $this->summarytable->addRow(); + $this->rowcount++; + $row = $this->summarytable->addRow(); + $this->rowcount++; + $this->summarytable->addCell($row, 'string', 'Summe:'); + for ($i=1; $i <= count($users); $i++) { + $this->table->addCell($row, 'formula', 'SUM([.'.chr(65+$i).'5:.'.chr(65 + $i).($this->rowcount - 1).'])'); + } + $this->summarytable->addCell($row, 'string', 'Summe:'); + for ($i= count($users)+2; $i <= count($usersweekend)+count($users)+1; $i++) { + $this->table->addCell($row, 'formula', 'SUM([.'.chr(65+$i).'5:.'.chr(65 + $i).($this->rowcount - 1).'])'); + } + + + // summary line 2 + $row = $this->summarytable->addRow(); + $this->rowcount++; + $row = $this->summarytable->addRow(); + $this->rowcount++; + $this->summarytable->addCell($row, 'string', 'Gesamt:'); + $this->table->addCell($row, 'formula', 'SUM([.B'. ($this->rowcount - 2) . ':.' . chr(65 + count($users)) . ($this->rowcount - 2) . '])'); + for ($i=1; $i <= count($users)-1; $i++) { + $this->table->addCell($row, 'string', ''); + } + + $this->summarytable->addCell($row, 'string', 'Gesamt:'); + $this->table->addCell($row, 'formula', 'SUM([.'.chr(65 + count($users) + 2) . ($this->rowcount - 2). + ':.'.chr(65 + count($users) + 2 + count($usersweekend) - 1) . ($this->rowcount - 2).'])'); + } + + + public function create_usertable($_tablename, $_username, $_tstamp_min, $_tstamp_max) { + $this->table = new SpreadSheetTable($this->document, $_tablename); + for ($i=10; $i < 19; $i++) { + $this->table->addColumn('co'.$i); + } + + $row = $this->table->addRow(); + $this->table->addCell($row, 'string', 'Monat:' . strftime("%m/%Y", $_tstamp_min), array('bold')); + $this->table->addCell($row, 'string', 'KW ' . strftime("%V", $_tstamp_min), array('bold')); + + $row = $this->table->addRow(); + $this->table->addCell($row, 'string', 'Mitarbeiter:', array('bold') ); + $this->table->addCell($row, 'string', $_username, array('bold')); + + // create table-headlines + $headlines = array( + 'Datum', + 'Site', + 'Ansprechpartner', + 'Projekttyp', + 'Ticket#', + 'Std.', + 'Newsletter', +// 'SOW-Nr.', + 'Bemerkungen' + ); + $row = $this->table->addRow('double'); + $this->table->addCells($row, 'string', $headlines, array('bold', 'border')); + + $this->tablecount++; + $this->rowcount = 3; + $this->columncount = count($headlines); + } + + + + /** + * exports a record into resource of handle + * + * @param iface_egw_record record + * @return bool + * @access public + */ + public function add_record( $_record, $_extras ) { + + if (is_array($_record)) { + $row = $this->table->addRow(); + $this->rowcount++; + + $this->table->addCell($row, 'date', strftime("%d.%m.%Y", $_record['ts_start'])); + $this->table->addCell($row, 'string', $_record['ts_project']); + $this->table->addCell($row, 'string', $_extras['asp']); + $this->table->addCell($row, 'string', $_record['cat_name']);// $_extras['typ']); + $this->table->addCell($row, 'string', $_extras['ticket']); + $this->table->addCell($row, 'float', (float) ($_record['ts_duration'] / 60) ); + $this->table->addCell($row, 'string', $_extras['newsletter']); +// $this->table->addCell($row, 'string', $_extras['sow']); + $this->table->addCell($row, 'string', $_record['ts_description']); + + + // collect statistics + + // username + $res = array(); +// $nameRegex = '/\s(.*)\s(.*)$/'; // z.B. "[admin] Richard Blume" + $nameRegex = '/(.*),\s(.*)$/'; // z.B. "Blume, Richard" + preg_match($nameRegex, $GLOBALS['egw']->common->grab_owner_name($_record['ts_owner']), $res); +// $user = $res[1]; + $user = $res[0]; // full name + + $site = $_record['ts_project']; + $time = $_record['ts_duration']; + $weekday = (int) strftime("%w", $_record['ts_start']); + + if ( $weekday == 0 || $weekday == 6 ) { + + // weekend + if (!array_key_exists($site, $this->summaryweekend)) { + $this->summaryweekend[$site] = array(); + $this->summaryweekend[$site][$user] = $time; + } + elseif (!array_key_exists($user, $this->summaryweekend[$site])) { + $this->summaryweekend[$site][$user] = $time; + } + else { + $this->summaryweekend[$site][$user] += $time; + } + } + else { + // site -> user -> sum + if (!array_key_exists($site, $this->summary)) { + $this->summary[$site] = array(); + $this->summary[$site][$user] = $time; + } + elseif (!array_key_exists($user, $this->summary[$site])) { + $this->summary[$site][$user] = $time; + } + else { + $this->summary[$site][$user] += $time; + } + } + + $this->num_of_records++; + $this->record = array(); + + } + } + + + /** + * + * + */ + public function summarize() { + if ($this->rowcount > 1) { + $row = $this->table->addRow(); + $this->rowcount++; + $row = $this->table->addRow(); + $this->rowcount++; + + $this->table->addCell($row, 'string', ''); + $this->table->addCell($row, 'string', ''); + $this->table->addCell($row, 'string', ''); + $this->table->addCell($row, 'string', ''); + $this->table->addCell($row, 'string', ''); + $this->table->addCell($row, 'formula', 'SUM([.F2:.F'.($this->rowcount-2).'])'); + } + } + + + + public function init() { + $this->document = new SpreadSheetDocument($this->handle); + + // global usertable styles + $columnwidth = array( + '2.767cm', + '5.067cm', + '5.067cm', + '3.267cm', + '2.267cm', + '2.267cm', + '2.267cm', +// '2.267cm', + '6.267cm' + ); + + for ($i=0; $i < count($columnwidth); $i++) { + $this->document->addColumnStyle('co'.($i + 10), $columnwidth[$i]); + } + + // first column, summary-table + $this->document->addColumnStyle('co20', '5.2cm'); + } + + public function finalize() { + $this->document->finalize(); + } + + /** + * Returns 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() { + + } +} + + + +?> diff --git a/timesheet/inc/class.export_timesheet_csv.inc.php b/timesheet/inc/class.export_timesheet_csv.inc.php new file mode 100644 index 0000000000..25022bbb36 --- /dev/null +++ b/timesheet/inc/class.export_timesheet_csv.inc.php @@ -0,0 +1,97 @@ + + * @copyright Knut Moeller + * @version $Id: $ + */ + +require_once(EGW_INCLUDE_ROOT. '/etemplate/inc/class.etemplate.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.export_csv.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.iface_export_plugin.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/timesheet/inc/class.egw_timesheet_record.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/timesheet/inc/class.uitimesheet.inc.php'); + +/** + * export plugin of addressbook + */ +class export_timesheet_csv implements iface_export_plugin { + + /** + * Exports records as defined in $_definition + * + * @param egw_record $_definition + */ + public static function export( $_stream, $_charset, definition $_definition) { + $options = $_definition->options; + + $uitimesheet = new uitimesheet(); + $selection = array(); + + $query = $GLOBALS['egw']->session->appsession('index',TIMESHEET_APP); + $query['num_rows'] = -1; // all + + $uitimesheet->get_rows($query,$selection,$readonlys,true); // true = only return the id's + + $options['begin_with_fieldnames'] = true; + $export_object = new export_csv($_stream, $charset, (array)$options); + + // $options['selection'] is array of identifiers as this plugin doesn't + // support other selectors atm. + foreach ($selection as $identifier) { + $timesheetentry = new egw_timesheet_record($identifier); + $export_object->export_record($timesheetentry); + unset($timesheetentry); + } + } + + /** + * returns translated name of plugin + * + * @return string name + */ + public static function get_name() { + return lang('Timesheet CSV export'); + } + + /** + * returns translated (user) description of plugin + * + * @return string descriprion + */ + public static function get_description() { + return lang("Exports entries from your Timesheet into a CSV File. CSV means 'Comma Seperated Values'. However in the options Tab you can also choose other seperators."); + } + + /** + * returns file suffix for exported file + * + * @return string suffix + */ + public static function get_filesuffix() { + return 'csv'; + } + + /** + * return html for options. + * this way the plugin has all opportunities for options tab + * + * @return string html + */ + public static function get_options_etpl() { + return 'timesheet.export_csv_options'; + } + + /** + * returns slectors of this plugin via xajax + * + */ + public static function get_selectors_etpl() { + return 'Selectors:'; + } +} \ No newline at end of file diff --git a/timesheet/inc/class.export_timesheet_openoffice.inc.php b/timesheet/inc/class.export_timesheet_openoffice.inc.php new file mode 100644 index 0000000000..359bfc07ce --- /dev/null +++ b/timesheet/inc/class.export_timesheet_openoffice.inc.php @@ -0,0 +1,205 @@ + + * @copyright Knut Moeller + * @version $Id: $ + */ + +require_once(EGW_INCLUDE_ROOT. '/etemplate/inc/class.etemplate.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/importexport/inc/class.iface_export_plugin.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/timesheet/inc/class.uitimesheet.inc.php'); +require_once(EGW_INCLUDE_ROOT. '/timesheet/inc/class.botimesheet.inc.php'); + +/** + * export plugin of addressbook + */ +class export_timesheet_openoffice implements iface_export_plugin { + + + + /** + * Exports records as defined in $_definition + * + * @param egw_record $_definition + */ + public static function export( $_stream, $_charset, definition $_definition) { + + $options = $_definition->options; + + $botimesheet = new botimesheet(); + + // get current display selection + + $query = $GLOBALS['egw']->session->appsession('index',TIMESHEET_APP); + + $bo_pm = CreateObject('projectmanager.boprojectmanager'); + $childs = $bo_pm->children( $query['col_filter']['pm_id'] ); + $childs[] = $query['col_filter']['pm_id']; + $pmChilds = implode(",",$childs); + $botimesheet->db->select( 'egw_links','link_id, link_id1','', + __LINE__,__FILE__,False, + '',False,0, + 'JOIN egw_pm_projects ON (pm_id = link_id2) + JOIN egw_timesheet ON (ts_id=link_id1) + WHERE + link_app1 = \'timesheet\' AND + link_app2 = \'projectmanager\' AND + link_id2 IN ('.$pmChilds.') AND ts_start >= '.$query['startdate'].' AND ts_start < '.$query['enddate'] ); + + while($row = $botimesheet->db->row(true)) { + $tslist[$row['link_id']] = $row['link_id1']; + } + +//error_log(print_r($query,true)); +//error_log(print_r($tslist,true)); + + $ids = implode(',', $tslist); + + // no result + if (strlen($ids)==0) return false; + +//error_log('IDS: '.$ids); + + // get full result + + $rows = $botimesheet->search(array("egw_timesheet.ts_id IN ($ids)"), + false, 'ts_owner,ts_start', array('cat_name') , + '', false, 'AND', false, null, + 'LEFT JOIN egw_categories ON (egw_categories.cat_id=egw_timesheet.cat_id AND egw_categories.cat_appname=\'timesheet\')'); + + + if (is_array($rows) && count($rows)>0 ) { +//error_log(print_r($rows,true)); + // export rows + + $export_object = new export_openoffice($_stream, $charset, (array)$options); + $export_object->init(); + + + // get date values + + $tstamp_min = 0; // date range for table date entries (KW...) + $tstamp_max = 0; + + foreach($rows as $row) { + if ($row['ts_start']<$tstamp_min || $tstamp_min == 0) $tstamp_min = $row['ts_start']; + if ($row['ts_start']>$tstamp_max || $tstamp_max == 0) $tstamp_max = $row['ts_start']; + } + + + // init summarytable + $export_object->create_summarytable(); + + + // user tables + $last_username = 0; + $first_table = true; + + foreach($rows as $row) { + + // read in extra values (custom fields) + $extrarows = $botimesheet->search(array("egw_timesheet.ts_id=".$row['ts_id']), false, '', 'ts_extra_name,ts_extra_value' , + '', false, 'AND', false, null, + 'LEFT JOIN egw_timesheet_extra ON (egw_timesheet_extra.ts_id=egw_timesheet.ts_id)' ); + $extras = array(); + foreach($extrarows as $extrarow) { + $extras[$extrarow['ts_extra_name']] = $extrarow['ts_extra_value']; + } + + // change projectname + $titleRegex = '/^.*:.*-\s(.*)$/'; + preg_match($titleRegex, $row['ts_project'], $title); + $row['ts_project'] = $title[1]; + + // get firstname, lastname + $res = array(); +// $nameRegex = '/\s(.*)\s(.*)$/'; // z.B. "[admin] Richard Blume" + $nameRegex = '/(.*),\s(.*)$/'; // z.B. "Blume, Richard" + preg_match($nameRegex, $GLOBALS['egw']->common->grab_owner_name($row['ts_owner']), $res); +// error_log('|'.$name . '| -> ' . print_r($res,true)); + $firstname = $res[2]; + $lastname = $res[1]; + $fullname = $firstname.' '.$lastname; + + // new table on username change + if ($row['ts_owner'] != $last_username) { + + // create sum as last tablerow before creating new table + if (!$first_table) { + $export_object->summarize(); + } + else { + $first_table = false; + } + + // create new table sheet + $export_object->create_usertable($lastname, + $fullname, $tstamp_min, $tstamp_max); + + } + $export_object->add_record($row, $extras); + $last_username = $row['ts_owner']; + } + $export_object->summarize(); // for last table + + // fill collected sums into sum-table + $export_object->fill_summarytable($tstamp_min, $tstamp_max); + + // write to zipfile, cleanup + $export_object->finalize(); + } + } + + /** + * returns translated name of plugin + * + * @return string name + */ + public static function get_name() { + return lang('Timesheet OpenOffice export'); + } + + /** + * returns translated (user) description of plugin + * + * @return string descriprion + */ + public static function get_description() { + return lang("Export to OpenOffice Spreadsheet"); + } + + /** + * retruns file suffix for exported file + * + * @return string suffix + */ + public static function get_filesuffix() { + return 'ods'; + } + + /** + * return html for options. + * this way the plugin has all opertunities for options tab + * + * @return string html + */ + public static function get_options_etpl() { + return 'timesheet.export_openoffice_options'; + } + + /** + * returns slectors of this plugin via xajax + * + */ + public static function get_selectors_etpl() { + return 'Selectors:'; + } +} + + diff --git a/timesheet/inc/class.spreadsheet.inc.php b/timesheet/inc/class.spreadsheet.inc.php new file mode 100644 index 0000000000..aede801166 --- /dev/null +++ b/timesheet/inc/class.spreadsheet.inc.php @@ -0,0 +1,653 @@ + + * @copyright Knut Moeller + * @version $Id:$ + */ + + +/** + * + * Spreadsheet main class + * + * + */ +class SpreadSheetDocument { + + // TODO generalize... + // TODO internalize counts + + const TEMPDIR = '/tmp'; // system temppath TODO: better solution? + const ZIP = 'zip'; // system zip command TODO: zip via php possible? no shellexec ? + + private $debug = 0; + + private $temppath; // temppath (to assemble zip file) + private $tempzip; // temp zip file + + private $handle; // filehandle OO export file for importexport + + // xml nodes + private $doc; + private $styles; + private $body; + private $spreadsheet; // contains tables .... + + + + // File handling, INIT + + function __construct($_handle) { + $this->temppath = $this->mk_tempdir(self::TEMPDIR, 'cmsliteexport'); + $this->tempzip = tempnam('/tmp','cmslite_export_outfile'); + $this->handle = $_handle; + $this->initSpreadsheet(); + } + + + function __destruct() { + @unlink($this->tempzip); + @unlink($this->tempzip . '.zip'); + $this->remove_directory($this->temppath.'/'); + } + + + + /** + * assemble the zipped OpenOffice Document + * + */ + public function finalize() { + + @mkdir($this->temppath."/META-INF"); + @mkdir($this->temppath."/Thumbnails"); + @mkdir($this->temppath."/Configurations2"); + @mkdir($this->temppath."/Configurations2/statusbar"); + @mkdir($this->temppath."/Configurations2/accelerator"); + @touch($this->temppath."/Configurations2/accelerator/current.xml"); + @mkdir($this->temppath."/Configurations2/floater"); + @mkdir($this->temppath."/Configurations2/popupmenu"); + @mkdir($this->temppath."/Configurations2/progressbar"); + @mkdir($this->temppath."/Configurations2/menubar"); + @mkdir($this->temppath."/Configurations2/toolbar"); + @mkdir($this->temppath."/Configurations2/images"); + @mkdir($this->temppath."/Configurations2/images/Bitmaps"); + + // main content + $this->write( $this->doc->saveXML(), "content.xml"); + if ($this->debug > 0) error_log(print_r($this->doc->saveXML(),true)); + + $this->createMimetype(); + $this->createManifest(); + $this->createMeta(); + + // create zip + shell_exec("cd $this->temppath; ".self::ZIP." -r $this->tempzip.zip * "); + + // copy to filehandle + if ($this->handle) { + $fh = fopen($this->tempzip.'.zip', "rb"); + if($fh) { + while (!feof($fh)) { + fwrite($this->handle, fread($fh, 8192)); + } + } + fclose($fh); + } + else error_log("output file error"); + } + + + + protected function createMeta() { + + //TODO generate + $content = ' + +OpenOffice.org/2.0$Linux OpenOffice.org_project/680m5$Build-9011 +2005-01-18T12:05:292006-10-25T10:35:552006-01-17T10:57:23 +de-DE218P1DT9H3M54S + +'; + $this->write($content,"meta.xml"); + } + + protected function createMimetype() { + $content = 'application/vnd.oasis.opendocument.spreadsheet'; + $this->write($content,"mimetype"); + + } + + protected function createManifest() { + //TODO generate +$content = ' + + + + + + + + + + + + + + + + +'; + + $this->write($content,"META-INF/manifest.xml"); + } + + + protected function initSpreadsheet() { + + $this->doc = new DOMDocument ('1.0', 'UTF-8'); + $this->doc->formatOutput = true; + + $root = $this->doc->createElementNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', + 'office:document-content'); + $this->doc->appendChild($root); + + // define Namespaces.... + + $namespaces = array( + 'style' => 'urn:oasis:names:tc:opendocument:xmlns:style:1.0', + 'text' => 'urn:oasis:names:tc:opendocument:xmlns:text:1.0', + 'table' => 'urn:oasis:names:tc:opendocument:xmlns:table:1.0', + 'draw' => 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0', + 'fo' => 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0', + 'xlink' => 'http://www.w3.org/1999/xlink', + 'dc' => 'http://purl.org/dc/elements/1.1/', + 'meta' => 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0', + 'number' => 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0', + 'svg' => 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0', + 'chart' => 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0', + 'dr3d' => 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0', + 'math' => 'http://www.w3.org/1998/Math/MathML', + 'form' => 'urn:oasis:names:tc:opendocument:xmlns:form:1.0', + 'script' => 'urn:oasis:names:tc:opendocument:xmlns:script:1.0', + 'ooo' => 'http://openoffice.org/2004/office', + 'ooow' => 'http://openoffice.org/2004/writer', + 'oooc' => 'http://openoffice.org/2004/calc', + 'dom' => 'http://www.w3.org/2001/xml-events', + 'xforms' => 'http://www.w3.org/2002/xforms', + 'xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance'); + + foreach ($namespaces as $name => $url) { + $root->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$name", $url); + } + $root->setAttribute('office:version', '1.0'); + + + // main parts + + $node = $root->appendChild( $this->doc->createElement('office:scripts') ); + $node = $root->appendChild( $this->doc->createElement('office:font-face-decls') ); + $this->styles = $root->appendChild( $this->doc->createElement('office:automatic-styles') ); + $this->body = $root->appendChild( $this->doc->createElement('office:body') ); + $this->spreadsheet = $this->body->appendChild( $this->doc->createElement('office:spreadsheet') ); + + // styles + + $this->initTableStyles(); + $this->initColumnStyles(); + $this->initRowStyles(); + $this->initCellStyles(); + $this->initNumberStyles(); + } + + + /** + * define the table styles + */ + protected function initTableStyles() { + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ta1'); + $style->setAttribute('style:family', 'table'); + $style->setAttribute('style:master-page-name', 'Default'); + $property = $style->appendChild($this->doc->createElement('style:table-properties')); + $property->setAttribute('table:display', 'true'); + $property->setAttribute('style:writing-mode', 'lr-tb'); + } + + /** + * define the column styles + */ + protected function initColumnStyles() { + $this->addColumnStyle('co1'); + } + + public function addColumnStyle($_name, $_width='2.267cm') { + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', $_name); + $style->setAttribute('style:family', 'table-column'); + $property = $style->appendChild($this->doc->createElement('style:table-column-properties')); + $property->setAttribute('fo:break-before', 'auto'); + $property->setAttribute('style:column-width', $_width); + } + + + /** + * define the row styles + */ + protected function initRowStyles() { + + // standard + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ro1'); + $style->setAttribute('style:family', 'table-row'); + $property = $style->appendChild($this->doc->createElement('style:table-row-properties')); + $property->setAttribute('style:row-height', '0.48cm'); + $property->setAttribute('fo:break-before', 'auto'); + $property->setAttribute('style:use-optimal-row-height', 'true'); + + // double height + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ro2'); + $style->setAttribute('style:family', 'table-row'); + $property = $style->appendChild($this->doc->createElement('style:table-row-properties')); + $property->setAttribute('style:row-height', '1.053cm'); + $property->setAttribute('fo:break-before', 'auto'); + $property->setAttribute('style:use-optimal-row-height', 'false'); + + } + + + /** + * define the cell styles + */ + protected function initCellStyles() { + + // default + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ce1'); + $style->setAttribute('style:family', 'table-cell'); + $style->setAttribute('style:parent-style-name', 'Default'); + + $property = $style->appendChild($this->doc->createElement('style:table-cell-properties')); + $property->setAttribute('fo:border', 'none'); + $this->appendTextAttributes($style); + + // default bold + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ce2'); + $style->setAttribute('style:family', 'table-cell'); + $style->setAttribute('style:parent-style-name', 'Default'); + + $property = $style->appendChild($this->doc->createElement('style:table-cell-properties')); + $property->setAttribute('fo:border', 'none'); + $this->appendTextAttributes($style, true); + + // default bold + border + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ce3'); + $style->setAttribute('style:family', 'table-cell'); + $style->setAttribute('style:parent-style-name', 'Default'); + + $property = $style->appendChild($this->doc->createElement('style:table-cell-properties')); + $property->setAttribute('fo:border', '0.002cm solid #000000'); + $this->appendTextAttributes($style, true); + + + + // N2 decimal + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ce101'); + $style->setAttribute('style:family', 'table-cell'); + $style->setAttribute('style:parent-style-name', 'Default'); + $style->setAttribute('style:data-style-name', 'N2'); + + $property = $style->appendChild($this->doc->createElement('style:table-cell-properties')); + $property->setAttribute('fo:border', 'none'); + $this->appendTextAttributes($style); + + + // N0 decimal + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ce100'); + $style->setAttribute('style:family', 'table-cell'); + $style->setAttribute('style:parent-style-name', 'Default'); + $style->setAttribute('style:data-style-name', 'N0'); + + $property = $style->appendChild($this->doc->createElement('style:table-cell-properties')); + $property->setAttribute('fo:border', 'none'); + $this->appendTextAttributes($style); + + + // N3 decimal Sum + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ce102'); + $style->setAttribute('style:family', 'table-cell'); + $style->setAttribute('style:parent-style-name', 'Default'); + $style->setAttribute('style:data-style-name', 'N2'); + + $property = $style->appendChild($this->doc->createElement('style:table-cell-properties')); + $property->setAttribute('fo:border', 'none'); + $this->appendTextAttributes($style, true); + + + // N4 date + $style = $this->styles->appendChild($this->doc->createElement('style:style')); + $style->setAttribute('style:name', 'ce103'); + $style->setAttribute('style:family', 'table-cell'); + $style->setAttribute('style:parent-style-name', 'Default'); + $style->setAttribute('style:data-style-name', 'N4'); + + $property = $style->appendChild($this->doc->createElement('style:table-cell-properties')); + $property->setAttribute('fo:border', 'none'); + $this->appendTextAttributes($style); + + } + + + protected function appendTextAttributes($_style, $_bold=false) { + $property = $_style->appendChild($this->doc->createElement('style:text-properties')); + $property->setAttribute('style:use-window-font-color', 'true' ); + $property->setAttribute('style:text-outline', 'false' ); + $property->setAttribute('style:text-line-through-style', 'none' ); + $property->setAttribute('style:font-name', 'Arial1' ); + $property->setAttribute('style:text-underline-style', 'none' ); + $property->setAttribute('style:font-size-asian', '10pt' ); + $property->setAttribute('style:font-style-asian', 'normal' ); + $property->setAttribute('style:font-weight-asian', 'bold' ); + $property->setAttribute('style:font-size-complex', '10pt' ); + $property->setAttribute('style:font-style-complex', 'normal' ); + $property->setAttribute('style:font-weight-complex', 'bold' ); + $property->setAttribute('fo:font-size', '10pt' ); + $property->setAttribute('fo:font-style', 'normal' ); + $property->setAttribute('fo:text-shadow', 'none' ); + $property->setAttribute('fo:font-weight', ($_bold)?'bold':'normal' ); + } + + + /** + * define the number styles + */ + protected function initNumberStyles() { + + // N2 : decimal precision 2 + $style = $this->styles->appendChild($this->doc->createElement('number:number-style')); + $style->setAttribute('style:name', 'N2'); + $property = $style->appendChild($this->doc->createElement('number:number')); + $property->setAttribute('number:decimal-places', '2'); + $property->setAttribute('number:min-integer-digits', '1'); + $property->setAttribute('number:grouping', 'false'); + + // N0 : plain integer N0 + $style = $this->styles->appendChild($this->doc->createElement('number:number-style')); + $style->setAttribute('style:name', 'N0'); + $property = $style->appendChild($this->doc->createElement('number:number')); + $property->setAttribute('number:decimal-places', '0'); + $property->setAttribute('number:min-integer-digits', '0'); + $property->setAttribute('number:grouping', 'false'); + + // N4 : date + $style = $this->styles->appendChild($this->doc->createElement('number:date-style')); + $style->setAttribute('style:name', 'N4'); + $style->setAttribute('number:automatic-order', 'true'); + + $property = $style->appendChild($this->doc->createElement('number:day')); + $property->setAttribute('number:style', 'long'); + $property = $style->appendChild($this->doc->createElement('number:text', '.')); + + $property = $style->appendChild($this->doc->createElement('number:month')); + $property->setAttribute('number:style', 'long'); + $property = $style->appendChild($this->doc->createElement('number:text', '.')); + + $property = $style->appendChild($this->doc->createElement('number:year')); + } + + + + + /* GETTER,SETTER FUNCTIONS */ + + public function getDoc() { + return $this->doc; + } + + public function get() { + return $this->spreadsheet; + } + + public function toString() { + return (is_object($this->doc)) ? $this->doc->saveXML() : 'empty' ; + } + + + + /* HELPER FUNCTIONS */ + + private function mk_tempdir($dir, $prefix='', $mode=0700) { + if (substr($dir, -1) != '/') $dir .= '/'; + + do { + $path = $dir.$prefix.mt_rand(0, 9999999); + } while (!mkdir($path, $mode)); + + return $path; + } + + private function write($content, $file) { + $fh = fopen ($this->temppath.'/'.$file, "w") or die("can't open tempfile ". $this->temppath.'/'.$file); + fwrite($fh, $content) or die ("cannot write to tempfile ". $this->temppath.'/'.$file); + fclose($fh); + } + + private function remove_directory($dir) { + $dir_contents = scandir($dir); + foreach ($dir_contents as $item) { + if (is_dir($dir.$item) && $item != '.' && $item != '..') { + $this->remove_directory($dir.$item.'/'); + } + elseif (file_exists($dir.$item) && $item != '.' && $item != '..') { + unlink($dir.$item); + } + } + rmdir($dir); + } + +} // class + + + + +/** + * + * + * + */ +class SpreadSheetTable { + + private $doc; + private $table; + private $columns; + + + function __construct($_spreadsheet, $_name) { + $this->doc = $_spreadsheet->getDoc(); + $this->spreadsheet = $_spreadsheet->get(); + + if (is_object($this->doc)) { + $this->table = $this->spreadsheet->appendChild( $this->doc->createElement('table:table') ); + $this->table->setAttribute('table:name', $_name); + $this->table->setAttribute('table:style-name', 'ta1'); + $this->table->setAttribute('table:print','false'); + } + else return false; + } + + /** + * + */ + public function get() { + return $this->table; + } + + + /** + * add column + * @return colElement + */ + public function addColumn($_name='co1', $_cellstylename='ce1', $_repeat=1) { + $col = $this->table->appendChild( $this->doc->createElement('table:table-column') ); + $col->setAttribute('table:style-name', $_name); + $col->setAttribute('table:number-columns-repeated',$_repeat); + $col->setAttribute('table:default-cell-style-name', $_cellstylename); + + // table:number-columns-repeated="2" + return $col; + } + + + /** + * add row + * @return rowElement + * @param type: normal, double (height) + */ + public function addRow($_type='normal') { + $row = $this->table->appendChild( $this->doc->createElement('table:table-row') ); + $row->setAttribute('table:style-name', ($_type=='normal') ? 'ro1' : 'ro2'); + return $row; + } + + + /** + * requires row element, adds one cell to it + * @return cellElement + */ + public function addCell( $row, $type, $value, $attributes=array() ) { + $cell = $row->appendChild( $this->doc->createElement('table:table-cell') ); + cellType($this->doc, $cell, $type, $value, $attributes); + return $cell; + } + + + /** + * requires row element, adds multiple cells + */ + public function addCells( $row, $type, $values, $attributes=array() ) { + if (!is_array($values)) { + echo "array required"; + return false; + } + foreach ($values as $value) { + $this->addCell($row, $type, $value, $attributes); + } + } + +} // class + + + + +/** + * + * type factory + * + */ +function cellType($_doc, $_cell, $_type, $_value, $_attributes=array()) { + + //TODO think about attribute dispatching <-> xml cell definition + + switch ($_type) { + case 'float' : return new SpreadSheetCellTypeFloat ($_doc, $_cell, $_value, $_attributes); + case 'int' : return new SpreadSheetCellTypeInt ($_doc, $_cell, $_value, $_attributes); + case 'formula' : return new SpreadSheetCellTypeFormula($_doc, $_cell, $_value, $_attributes); + case 'date' : return new SpreadSheetCellTypeDate ($_doc, $_cell, $_value, $_attributes); + default : return new SpreadSheetCellTypeString ($_doc, $_cell, $_value, $_attributes); + } +} + + +/** + * string type cell + */ +class SpreadSheetCellTypeString { + + function __construct($doc, $cell, $value, $_attributes) { + $cell->setAttribute('office:value-type', 'string'); + + $name = 'ce1'; + if (array_search('bold', $_attributes) !== FALSE ) { + $name = 'ce2'; + if (array_search('border', $_attributes) !== FALSE ) { + $name = 'ce3'; + } + } + $cell->setAttribute('table:style-name', $name); + + $cell->appendChild( $doc->createElement('text:p', $value) ); + } +} + + +/** + * float type cell + */ +class SpreadSheetCellTypeFloat { + function __construct($doc, $cell, $value, $_attributes) { + $cell->setAttribute('office:value-type', 'float'); + $cell->setAttribute('office:value', number_format($value, 2, '.', '') ); + $cell->setAttribute('table:style-name', 'ce101'); + $cell->appendChild( $doc->createElement('text:p', number_format($value, 2, ',', '')) ); + } +} + + +/** + * int type cell + */ +class SpreadSheetCellTypeInt { + function __construct($doc, $cell, $value, $_attributes) { + $cell->setAttribute('office:value-type', 'float'); + $cell->setAttribute('office:value', number_format($value, 0, ',', '')); + $cell->setAttribute('table:style-name', 'ce100'); + $cell->appendChild( $doc->createElement('text:p', number_format($value, 0, ',', '')) ); + } +} + + +/** + * date type cell + */ +class SpreadSheetCellTypeDate { + function __construct($doc, $cell, $value, $_attributes) { + if (($timestamp = strtotime($value)) === false) { + echo "Wrong date ($value)"; + } + else { + $cell->setAttribute('office:value-type', 'date'); + $cell->setAttribute('office:date-value', strftime("%Y-%m-%d", $timestamp) ); + $cell->setAttribute('table:style-name', 'ce103'); + $cell->appendChild( $doc->createElement('text:p', strftime("%d.%m.%Y", $timestamp) ) ); + } + } +} + + +/** + * formula type cell + */ +class SpreadSheetCellTypeFormula { + + function __construct($doc, $cell, $value, $_attributes) { + $cell->setAttribute('table:formula', 'oooc:='.$value); + $cell->setAttribute('office:value-type', 'float'); + $cell->setAttribute('office:value', '0'); + $cell->setAttribute('table:style-name', 'ce102'); + $cell->appendChild( $doc->createElement('text:p', '0,00' )); + } +} + + +?> diff --git a/timesheet/inc/class.ts_admin_prefs_sidebox_hooks.inc.php b/timesheet/inc/class.ts_admin_prefs_sidebox_hooks.inc.php index 400f8e6b47..39bd4d0a42 100644 --- a/timesheet/inc/class.ts_admin_prefs_sidebox_hooks.inc.php +++ b/timesheet/inc/class.ts_admin_prefs_sidebox_hooks.inc.php @@ -74,6 +74,9 @@ class ts_admin_prefs_sidebox_hooks 'menuaction' => 'admin.uicategories.index', 'appname' => $appname, 'global_cats'=> True)), + 'Custom fields' => $GLOBALS['egw']->link('/index.php',array( + 'menuaction' => 'timesheet.uicustomfields.edit', + 'appname' => $appname)), ); if ($location == 'admin') { diff --git a/timesheet/inc/class.uicustomfields.inc.php b/timesheet/inc/class.uicustomfields.inc.php new file mode 100644 index 0000000000..ea725e9bf8 --- /dev/null +++ b/timesheet/inc/class.uicustomfields.inc.php @@ -0,0 +1,235 @@ + + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + */ + + if (!defined('TIMESHEET_APP')) + { + define('TIMESHEET_APP','timesheet'); + } + + + class uicustomfields + { + var $public_functions = array + ( + 'edit' => True + ); + + /** + * Customfield types, without the link app-names + * + * @var array + */ + var $cf_types = array( + 'text' => 'Text', + 'label' => 'Label', + 'select' => 'Selectbox', + 'radio' => 'Radiobutton', + 'checkbox' => 'Checkbox', + ); + + var $config; + var $bo; + var $tmpl; + var $fields; + + + function uicustomfields( ) + { + $this->bo =& CreateObject('timesheet.botimesheet'); + $this->tmpl =& CreateObject('etemplate.etemplate'); + $this->config = &$this->bo->config; + $this->fields = &$this->bo->customfields; + + $GLOBALS['egw']->translation->add_app('etemplate'); + foreach($this->cf_types as $name => $label) $this->cf_types[$name] = lang($label); + + } + + /** + * Edit/Create an Timesheet Custom field + * + * @param array $content Content from the eTemplate Exec + */ + function edit($content=null) + { + + $GLOBALS['egw_info']['flags']['app_header'] = lang(TIMESHEET_APP).' - '.lang('Custom fields'); + + if (is_array($content)) + { + //echo '
'; print_r($content); echo "
\n"; + list($action) = @each($content['button']); + switch($action) + { + default: + if(!$content['fields']['create'] && !$content['fields']['delete']) { + break; + } + case 'save': + case 'apply': + $this->update($content); + if ($action != 'save') + { + break; + } + case 'cancel': + $GLOBALS['egw']->redirect_link('/timesheet/index.php?menuaction=timesheet.uitimesheet.index'); + exit; + } + } + $readonlys = array(); + + //echo 'customfields=
'; print_r($this->fields); echo "
\n"; + $content['fields'] = array(); + $n = 0; + if(is_array($this->fields)) { + foreach($this->fields as $name => $data) + { + if (is_array($data['values'])) + { + $values = ''; + foreach($data['values'] as $var => $value) + { + $values .= (!empty($values) ? "\n" : '').$var.'='.$value; + } + $data['values'] = $values; + } + $content['fields'][++$n] = $data + array( + 'typ' => '', + 'name' => $name + ); + $preserv_fields[$n]['old_name'] = $name; + $readonlys['fields']["create$name"] = True; + } + } + + //$content['fields'][++$n] = array('typ'=>'','order' => 10 * $n); // new line for create + $content['fields'][++$n] = array('typ' => '', 'label'=>'', 'help'=>'', 'values'=>'', 'len'=>'', 'rows'=>'', 'order'=>10 * $n, 'name'=>''); + + $readonlys['fields']["delete[]"] = True; + + //echo '

uicustomfields.edit(content =

'; print_r($content); echo "
\n"; + //echo 'readonlys =
'; print_r($readonlys); echo "
\n"; + $this->tmpl->read('timesheet.customfields'); + $this->tmpl->exec('timesheet.uicustomfields.edit',$content,array( + 'type' => $this->cf_types, + ),$readonlys,array('fields' => $preserv_fields)); + } + + function update_fields(&$content) + { + $fields = &$content['fields']; + $create = $fields['create']; + unset($fields['create']); + + if ($fields['delete']) + { + list($delete) = each($fields['delete']); + unset($fields['delete']); + } + + foreach($fields as $field) + { + $name = trim($field['name']); + $old_name = $field['old_name']; + + if (!empty($delete) && $delete == $old_name) + { + //delete all timesheet extra entries with that certain name + $this->bo->delete_extra('',$old_name); + unset($this->fields[$old_name]); + continue; + } + if (isset($field['name']) && empty($name) && ($create || !empty($old_name))) // empty name not allowed + { + $content['error_msg'] = lang('Name must not be empty !!!'); + } + if (isset($field['old_name'])) + { + if (!empty($name) && $old_name != $name) // renamed + { + //update all timesheet_extra entries with that certain name + $this->bo->save_extra(True,$old_name,$name); + unset($this->fields[$old_name]); + } + elseif (empty($name)) + { + $name = $old_name; + } + } + elseif (empty($name)) // new item and empty ==> ignore it + { + continue; + } + $values = array(); + if (!empty($field['values'])) + { + foreach(explode("\n",$field['values']) as $line) + { + list($var,$value) = split('=',trim($line),2); + $var = trim($var); + $values[$var] = empty($value) ? $var : $value; + } + } + $this->fields[$name] = array( + 'type' => $field['type'], + 'label' => empty($field['label']) ? $name : $field['label'], + 'help' => $field['help'], + 'values'=> $values, + 'len' => $field['len'], + 'rows' => intval($field['rows']), + 'order' => intval($field['order']) + ); + } + if (!function_exists('sort_by_order')) + { + function sort_by_order($arr1,$arr2) + { + return $arr1['order'] - $arr2['order']; + } + } + uasort($this->fields,sort_by_order); + + $n = 0; + foreach($this->fields as $name => $data) + { + $this->fields[$name]['order'] = ($n += 10); + } + } + + function update(&$content) + { + $this->update_fields($content); + // save changes to repository + $this->save_repository(); + } + + + function save_repository() + { + // save changes to repository + + //echo '

uicustomfields::save_repository() \$this->fields=

'; print_r($this->fields); echo "
\n"; + $this->config->value('customfields',$this->fields); + $this->config->save_repository(); + } + } diff --git a/timesheet/inc/class.uitimesheet.inc.php b/timesheet/inc/class.uitimesheet.inc.php index a31a19fa74..7a436f1e57 100644 --- a/timesheet/inc/class.uitimesheet.inc.php +++ b/timesheet/inc/class.uitimesheet.inc.php @@ -24,12 +24,19 @@ class uitimesheet extends botimesheet 'index' => true, ); /** - * ProjectManager integraion: 'none', 'full' or default null + * ProjectManager integration: 'none', 'full' or default null * * @var string */ var $pm_integration; + /** + * TimeSheet view type: 'short' or 'normal' + * + * @var string + */ + var $ts_viewtype; + function uitimesheet() { $this->botimesheet(); @@ -37,6 +44,11 @@ class uitimesheet extends botimesheet $config =& CreateObject('phpgwapi.config',TIMESHEET_APP); $config->read_repository(); $this->pm_integration = $config->config_data['pm_integration']; + $this->ts_viewtype = $config->config_data['ts_viewtype']; + + // our javascript + // to be moved in a seperate file if rewrite is over + $GLOBALS['egw_info']['flags']['java_script'] .= $this->js(); } function view() @@ -46,7 +58,7 @@ class uitimesheet extends botimesheet function edit($content = null,$view = false) { - $tabs = 'general|notes|links'; + $tabs = 'general|notes|links|customfields'; $etpl =& new etemplate('timesheet.edit'); if (!is_array($content)) @@ -294,6 +306,11 @@ class uitimesheet extends botimesheet $etpl->set_cell_attribute('pm_id','disabled',true); $etpl->set_cell_attribute('pl_id','disabled',true); } + + if($this->ts_viewtype == 'short') { + $content['ts_viewtype'] = /*$readonlys[$tabs]['links'] =*/ $readonlys[$tabs]['notes'] = true; + } + return $etpl->exec(TIMESHEET_APP.'.uitimesheet.edit',$content,array( 'ts_owner' => $edit_grants, ),$readonlys,$preserv,2); @@ -320,8 +337,10 @@ class uitimesheet extends botimesheet * @param array &$query * @param array &$rows returned rows/cups * @param array &$readonlys eg. to disable buttons based on acl + * @param boolean $id_only=false if true only return (via $rows) an array of contact-ids, dont save state to session + * @return int total number of contacts matching the selection */ - function get_rows(&$query_in,&$rows,&$readonlys) + function get_rows(&$query_in,&$rows,&$readonlys,$id_only=false) { $this->show_sums = false; if ($query_in['filter']) @@ -370,7 +389,8 @@ class uitimesheet extends botimesheet // PM project filter for the PM integration if ((string)$query['col_filter']['pm_id'] != '') { - $query['col_filter']['ts_id'] = $this->link->get_links('projectmanager',$query['col_filter']['pm_id'],'timesheet'); + //$query['col_filter']['ts_id'] = $this->link->get_links('projectmanager',$query['col_filter']['pm_id'],'timesheet'); + $query['col_filter']['ts_id'] = $this->get_ts_links($query['col_filter']['pm_id']); if (!$query['col_filter']['ts_id']) $query['col_filter']['ts_id'] = 0; } unset($query['col_filter']['pm_id']); @@ -458,6 +478,15 @@ class uitimesheet extends botimesheet } $total = parent::get_rows($query,$rows,$readonlys); + if ($id_only) + { + foreach($rows as $n => $row) + { + $rows[$n] = $row['ts_id']; + } + return $this->total; // no need to set other fields or $readonlys + } + unset($query['col_filter'][0]); $readonlys = array(); @@ -536,6 +565,10 @@ class uitimesheet extends botimesheet } $rows['pm_integration'] = $this->pm_integration; + if($this->ts_viewtype == 'short') { + $rows['ts_viewtype'] = true; + } + return $total; } @@ -604,4 +637,21 @@ class uitimesheet extends botimesheet } return $etpl->exec(TIMESHEET_APP.'.uitimesheet.index',$content,$sel_options,$readonlys,$preserv); } + + + + function js() + { + return ''; + } + }