/** * function csv2iCal * * Convert csv file to iCal format and send file to browser (default) or save Ical file to disk * Definition iCal : rcf2445, http://kigkonsult.se/downloads/index.php#rfc2445 * Definition csv : http://en.wikipedia.org/wiki/Comma-separated_values * Using iCalcreator: http://kigkonsult.se/downloads/index.php#iCalcreator * csv directory/file read/write * * @author Kjell-Inge Gustafsson <*****@*****.**> * @since 3.0 - 2011-12-21 * @return bool return FALSE when error */ public function csv2iCal() { $timeexec = array('start' => microtime(TRUE)); if ($this->log) { $this->log->log(' ********** START **********', PEAR_LOG_NOTICE); } $conf = array(); foreach ($this->config as $key => $value) { if (in_array(strtolower($key), array('inputdirectory', 'outputdirectory', 'inputfilename', 'outputfilename', 'inputurl', 'backup', 'save', 'skip'))) { continue; } if (in_array($key, array('del', 'sep', 'nl'))) { $conf[$key] = "{$value}"; } else { $conf[strtoupper($value)] = strtoupper($key); // flip map names if ($this->log) { $this->log->log("{$value} mapped to {$key}", PEAR_LOG_DEBUG); } } } $fp = false; $string_to_parse = $this->getConfig('string_to_parse'); if ($string_to_parse) { $fp = fopen('php://temp/maxmemory:' . 1024 * 1024, 'rw'); fputs($fp, $string_to_parse); fseek($fp, 0); } else { /** check input/output directory and filename */ $inputdirFile = $outputdirFile = ''; $inputFileParts = $outputFileParts = array(); $remoteInput = $remoteOutput = FALSE; if (FALSE === $this->_fixIO('input', 'csv', $inputdirFile, $inputFileParts, $remoteInput)) { if ($this->log) { $this->log->log(number_format(microtime(TRUE) - $timeexec['start'], 5) . ' sec', PEAR_LOG_ERR); $this->log->log("ERROR 2, invalid input ({$inputdirFile})", PEAR_LOG_ERR); $this->log->flush(); } return FALSE; } if (FALSE === $this->_fixIO('output', FALSE, $outputdirFile, $outputFileParts, $remoteOutput)) { if (FALSE === $this->setConfig('outputfilename', $inputFileParts['filename'] . '.ics')) { if ($this->log) { $this->log->log(number_format(microtime(TRUE) - $timeexec['start'], 5) . ' sec', PEAR_LOG_ERR); $this->log->log('ERROR 3,invalid output (' . $inputFileParts['filename'] . '.csv)', PEAR_LOG_ERR); $this->log->flush(); } return FALSE; } $outputdirFile = $this->getConfig('outputdirectory') . DIRECTORY_SEPARATOR . $inputFileParts['filename'] . '.ics'; $outputFileParts = pathinfo($outputdirFile); if ($this->log) { $this->log->log("output set to '{$outputdirFile}'", PEAR_LOG_NOTICE); } } if ($this->log) { $this->log->log("INPUT..FILE:{$inputdirFile}", PEAR_LOG_NOTICE); $this->log->log("OUTPUT.FILE:{$outputdirFile}", PEAR_LOG_NOTICE); } /** read csv file into input array */ ini_set('auto_detect_line_endings', true); $fp = fopen($inputdirFile, "r"); if (FALSE === $fp) { if ($this->log) { $this->log->log("ERROR 4, unable to read file: '{$inputdirFile}'", PEAR_LOG_ERR); $this->log->log(number_format(microtime(TRUE) - $timeexec['start'], 5) . ' sec', PEAR_LOG_DEBUG); $this->log->flush(); } return FALSE; } } $rows = array(); while (FALSE !== ($row = fgetcsv($fp, FALSE, $conf['sep'], $conf['del']))) { $rows[] = $row; } fclose($fp); ini_set('auto_detect_line_endings', false); $cntrows = count($rows); /** iCalcreator checks when setting directory and filename */ $calendar = new vcalendar(); if (FALSE !== ($unique_id = $this->getConfig('unique_id'))) { $calendar->setConfig('unique_id', $unique_id); } if (!$this->getConfig('outputobj')) { if ($remoteOutput) { if (FALSE === $calendar->setConfig('url', $outputdirFile)) { if ($this->log) { $this->log->log("ERROR 5, iCalcreator: invalid url: '{$outputdirFile}'", PEAR_LOG_ERR); $this->log->log(number_format(microtime(TRUE) - $timeexec['start'], 5) . ' sec', PEAR_LOG_DEBUG); $this->log->flush(); } return FALSE; } } else { if (FALSE === $calendar->setConfig('directory', $outputFileParts['dirname'])) { if ($this->log) { $this->log->log("ERROR 6, INPUT FILE:'{$inputdirFile}' iCalcreator: invalid directory: '" . $outputFileParts['dirname'] . "'", PEAR_LOG_ERR); $this->log->log(number_format(microtime(TRUE) - $timeexec['start'], 5) . ' sec', PEAR_LOG_DEBUG); $this->log->flush(); } return FALSE; } if (FALSE === $calendar->setConfig('filename', $outputFileParts['basename'])) { if ($this->log) { $this->log->log("ERROR 7, INPUT FILE:'{$inputdirFile}' iCalcreator: invalid filename: '" . $outputFileParts['basename'] . "'", PEAR_LOG_ERR); $this->log->log(number_format(microtime(TRUE) - $timeexec['start'], 5) . ' sec', PEAR_LOG_DEBUG); $this->log->flush(); } return FALSE; } } } $timeexec['fileOk'] = microtime(TRUE); /** info rows */ $actrow = 0; for ($row = $actrow; $row < $cntrows; $row++) { if (empty($rows[$row]) || 1 >= count($rows[$row]) || '' >= $rows[$row][1] || 'iCal' == substr($rows[$row][0], 0, 4) || 'kigkonsult.se' == $rows[$row][0]) { continue; } elseif ('TYPE' == strtoupper($rows[$row][0])) { $actrow = $row; break; } elseif ('CALSCALE' == strtoupper($rows[$row][0])) { $calendar->setProperty('CALSCALE', $rows[$row][1]); } elseif ('METHOD' == strtoupper($rows[$row][0])) { $calendar->setProperty('METHOD', $rows[$row][1]); } elseif ('X-' == substr($rows[$row][0], 0, 2)) { $calendar->setProperty($rows[$row][0], $rows[$row][1]); } elseif (2 >= count($rows[$row])) { continue; } else { $actrow = $row; break; } } $timeexec['infoOk'] = microtime(TRUE); $cntprops = 0; $proporder = array(); /** fix opt. vtimezone */ if ($actrow < $cntrows && (in_array('tzid', $rows[$actrow]) || in_array('TZID', $rows[$actrow]))) { foreach ($rows[$actrow] as $key => $header) { $header = strtoupper($header); if (isset($conf[$header])) { $proporder[$conf[$header]] = $key; // check map of userfriendly name to iCal property name if ($this->log) { $this->log->log("header row ix:{$key} => {$header}, replaced by " . $conf[$header], PEAR_LOG_DEBUG); } } else { $proporder[$header] = $key; } } if ($this->log) { $this->log->log("comp proporder=" . implode(',', array_flip($proporder)), PEAR_LOG_DEBUG); } $allowedProps = array('VTIMEZONE' => array('TZID', 'LAST-MODIFIED', 'TZURL'), 'STANDARD' => array('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM', 'COMMENT', 'RDATE', 'RRULE', 'TZNAME'), 'DAYLIGHT' => array('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM', 'COMMENT', 'RDATE', 'RRULE', 'TZNAME')); $actrow++; $comp = $subcomp = $actcomp = FALSE; for ($row = $actrow; $row < $cntrows; $row++) { if (empty($rows[$row]) || 1 >= count($rows[$row])) { continue; } $compname = strtoupper($rows[$row][0]); if ('TYPE' == $compname) { // next header $actrow = $row; break; } if ($comp && $subcomp) { $comp->setComponent($subcomp); $subcomp = FALSE; } if ('VTIMEZONE' == $compname) { if ($comp) { $calendar->setComponent($comp); } $comp = new vtimezone(); $actcomp =& $comp; $cntprops += 1; } elseif ('STANDARD' == $compname) { $subcomp = new vtimezone('STANDARD'); $actcomp =& $subcomp; } elseif ('DAYLIGHT' == $compname) { $subcomp = new vtimezone('DAYLIGHT'); $actcomp =& $subcomp; } else { if ($this->log) { $this->log->log("skipped {$compname}", PEAR_LOG_WARNING); } continue; } foreach ($proporder as $propName => $col) { // insert all properties into component if (2 > $col || 'ORDER' == strtoupper($propName)) { continue; } $propName = strtoupper($propName); if ('X-' != substr($propName, 0, 2) && !in_array($propName, $allowedProps[$compname])) { // check if allowed property for the component if ($this->log) { $this->log->log("skipped {$compname}: {$propName}", PEAR_LOG_DEBUG); } continue; } if (isset($rows[$row][$col]) && !empty($rows[$row][$col])) { $rows[$row][$col] = str_replace(array("\r\n", "\n\r", "\n", "\r"), $conf['nl'], $rows[$row][$col]); $value = FALSE !== strpos($rows[$row][$col], $conf['nl']) ? explode($conf['nl'], $rows[$row][$col]) : array($rows[$row][$col]); foreach ($value as $val) { if (empty($val) && '0' != $val) { continue; } $del = FALSE !== strpos($val, ':') ? ';' : ':'; if (FALSE !== $actcomp->parse("{$propName}{$del}{$val}")) { if ($this->log) { $this->log->log("iCalcreator->parse( '{$propName} {$val}' )", PEAR_LOG_DEBUG); } } elseif ($this->log) { $this->log->log("ERROR 8, INPUT FILE:'{$inputdirFile}' iCalcreator: parse error: '{$propName}{$del}{$val}'", PEAR_LOG_ERR); } } // end foreach( $value } // end if( isset } // end foreach( $proporder } // end for( $row = $actrow if ($comp && $subcomp) { $comp->setComponent($subcomp); } if ($comp) { $calendar->setComponent($comp); } $comp = $subcomp = $actcomp = FALSE; } $timeexec['zoneOk'] = microtime(TRUE); /** fix data */ $proporder = array(); if ($actrow < $cntrows && isset($rows[$actrow][0]) && 'TYPE' == strtoupper($rows[$actrow][0])) { foreach ($rows[$actrow] as $key => $header) { $header = strtoupper($header); if (isset($conf[$header])) { $proporder[$conf[$header]] = $key; // check map of user friendly name to iCal property name if ($this->log) { $this->log->log("header row ix:'{$key} => {$header}', mapped to '" . $conf[$header] . "'", PEAR_LOG_DEBUG); } } else { $proporder[$header] = $key; } } if ($this->log) { $this->log->log("comp proporder=" . implode(',', array_flip($proporder)), PEAR_LOG_DEBUG); } $allowedProps = array('VEVENT' => array('ATTACH', 'ATTENDEE', 'CATEGORIES', 'CLASS', 'COMMENT', 'CONTACT', 'CREATED', 'DESCRIPTION', 'DTEND', 'DTSTAMP', 'DTSTART', 'DURATION', 'EXDATE', 'RXRULE', 'GEO', 'LAST-MODIFIED', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RDATE', 'RECURRENCE-ID', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS', 'SEQUENCE', 'STATUS', 'SUMMARY', 'TRANSP', 'UID', 'URL'), 'VTODO' => array('ATTACH', 'ATTENDEE', 'CATEGORIES', 'CLASS', 'COMMENT', 'COMPLETED', 'CONTACT', 'CREATED', 'DESCRIPTION', 'DTSTAMP', 'DTSTART', 'DUE', 'DURATION', 'EXDATE', 'EXRULE', 'GEO', 'LAST-MODIFIED', 'LOCATION', 'ORGANIZER', 'PERCENT', 'PRIORITY', 'RDATE', 'RECURRENCE-ID', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL'), 'VJOURNAL' => array('ATTACH', 'ATTENDEE', 'CATEGORIES', 'CLASS', 'COMMENT', 'CONTACT', 'CREATED', 'DESCRIPTION', 'DTSTAMP', 'DTSTART', 'EXDATE', 'EXRULE', 'LAST-MODIFIED', 'ORGANIZER', 'RDATE', 'RECURRENCE-ID', 'RELATED-TO', 'RRULE', 'REQUEST-STATUS', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL'), 'VFREEBUSY' => array('ATTENDEE', 'COMMENT', 'CONTACT', 'DTEND', 'DTSTAMP', 'DTSTART', 'DURATION', 'FREEBUSY', 'ORGANIZER', 'UID', 'URL'), 'VALARM' => array('ACTION', 'ATTACH', 'ATTENDEE', 'DESCRIPTION', 'DURATION', 'REPEAT', 'SUMMARY', 'TRIGGER')); $actrow++; $comp = $subcomp = $actcomp = FALSE; $allowedComps = array('VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY'); for ($row = $actrow; $row < $cntrows; $row++) { if (empty($rows[$row]) || 1 >= count($rows[$row])) { continue; } if ($comp && $subcomp) { $comp->setComponent($subcomp); $subcomp = FALSE; } $compname = strtoupper($rows[$row][0]); if ($this->log) { $this->log->log("'{$compname}' START", PEAR_LOG_NOTICE); } if (in_array($compname, $allowedComps)) { if ($comp) { $calendar->setComponent($comp); } $comp = new $rows[$row][0](); $actcomp =& $comp; $cntprops += 1; } elseif ('VALARM' == $compname) { $subcomp = new valarm(); $actcomp =& $subcomp; } else { if ($this->log) { $this->log->log("skipped {$compname}", PEAR_LOG_WARNING); } continue; } foreach ($proporder as $propName => $col) { // insert all properties into component if (2 > $col || 'ORDER' == strtoupper($propName)) { continue; } $propName = strtoupper($propName); if ($this->log) { $this->log->log("{$compname} {$propName} START (col={$col})", PEAR_LOG_DEBUG); } if ('X-' != substr($propName, 0, 2) && !in_array($propName, $allowedProps[$compname])) { // check if allowed property for the component if ($this->log) { $this->log->log("skipped {$compname} {$propName}", PEAR_LOG_NOTICE); } continue; } if (isset($rows[$row][$col]) && !empty($rows[$row][$col]) || 'SEQUENCE' == $propName && '0' == $rows[$row][$col]) { $rows[$row][$col] = str_replace(array("\r\n", "\n\r", "\n", "\r"), $conf['nl'], $rows[$row][$col]); $value = FALSE !== strpos($rows[$row][$col], $conf['nl']) ? explode($conf['nl'], $rows[$row][$col]) : array($rows[$row][$col]); $ctests = array('://', 'fax:', 'cid:', 'sms:', 'tel:', 'urn:', 'crid:', 'news:', 'pres:', 'mailto:', 'MAILTO:'); foreach ($value as $val) { if (empty($val) && '0' != $val && 0 != $val) { continue; } if ('GEO' == $propName) { $parseval = FALSE !== strpos($val, ':') ? "GEO{$val}" : "GEO:{$val}"; if (FALSE === $actcomp->parse($parseval)) { if ($this->log) { $this->log->log("ERROR 11, INPUT FILE:'{$inputdirFile}' iCalcreator: parse error: '{$parseval}'", PEAR_LOG_ERR); } } } elseif ('REQUEST-STATUS' == $propName) { // 'REQUEST-STATUS' without any parameters.. . if (FALSE === $actcomp->parse("{$propName}:{$val}")) { if ($this->log) { $this->log->log("ERROR 12, INPUT FILE:'{$inputdirFile}' iCalcreator: parse error: '{$propName}:{$val}'", PEAR_LOG_ERR); } } } $cntm = $pos = 0; foreach ($ctests as $tst) { $cntm += substr_count($val, $tst); } $cntc = substr_count($val, ':'); $cntq = substr_count($val, '='); $cnts = substr_count($val, ';'); if (0 == $cntq && 0 == $cnts) { // no parameters $del = ':'; } elseif (1 == $cntc && $cntq + 1 == $cnts) { // parameters and colon $del = ';'; } elseif ($cntc == $cntm + 1) { $del = ';'; } else { $del = 1 > $cntm && 0 < $cntc ? ';' : ':'; } if ('X-' == substr($propName, 0, 2) || in_array($propName, array('CATEGORIES', 'COMMENT', 'CONTACT', 'DESCRIPTION', 'LOCATION', 'RESOURCES', 'SUMMARY'))) { $val = str_replace(',', '\\,', $val); if (FALSE !== ($pos = strpos($del . $val, ':'))) { while (FALSE !== ($pos2 = strpos($val, ';', $pos + 1))) { $val = substr($val, 0, $pos2) . '\\;' . substr($val, $pos2 + 1); if ($this->log) { $this->log->log("pos={$pos} pos2={$pos2} val='{$val}'", PEAR_LOG_DEBUG); } $pos = $pos2 + 1; } } } if (FALSE === $actcomp->parse("{$propName}{$del}{$val}")) { if ($this->log) { $this->log->log("ERROR 13, INPUT FILE:'{$inputdirFile}' iCalcreator: parse error: '{$propName}{$del}{$val}'", PEAR_LOG_ERR); } } elseif ($this->log) { $this->log->log("iCalcreator->parse( '{$propName}{$del}{$val}' )", PEAR_LOG_DEBUG); } } // end foreach( $value as $val } // end if( isset( $rows[$row][$col] } // end foreach( $proporder } // end for( $row = $actrow; if ($comp && $subcomp) { $comp->setComponent($subcomp); } if ($comp) { $calendar->setComponent($comp); } } $save = $this->getConfig('save'); if ($this->log) { $timeexec['exit'] = microtime(TRUE); $msg = "INPUT '{$inputdirFile}'"; $msg .= ' fileOk:' . number_format($timeexec['fileOk'] - $timeexec['start'], 5); $msg .= ' infoOk:' . number_format($timeexec['infoOk'] - $timeexec['fileOk'], 5); $msg .= ' zoneOk:' . number_format($timeexec['zoneOk'] - $timeexec['infoOk'], 5); $msg .= ' compOk:' . number_format($timeexec['exit'] - $timeexec['zoneOk'], 5); $msg .= ' total:' . number_format($timeexec['exit'] - $timeexec['start'], 5) . ' sec'; $this->log->log($msg, PEAR_LOG_DEBUG); $msg = "'{$inputdirFile}' (" . $cntprops . ' components) start:' . date('H:i:s', $timeexec['start']); $msg .= ' total:' . number_format($timeexec['exit'] - $timeexec['start'], 5) . ' sec'; if ($save) { $msg .= " -> '{$outputdirFile}'"; } $this->log->log($msg, PEAR_LOG_NOTICE); } /** return calendar, save or send the file */ if ($this->getConfig('outputobj')) { if ($this->log) { $this->log->log("INPUT FILE:'{$inputdirFile}' returning iCalcreator vcalendar instance", PEAR_LOG_NOTICE); $this->log->flush(); } return $calendar; exit; } $d = $calendar->getConfig('directory'); $f = $calendar->getConfig('filename'); $df = $d . DIRECTORY_SEPARATOR . $f; if ($save) { if (FALSE !== $calendar->saveCalendar()) { if ($this->log) { $this->log->log("INPUT FILE:'{$inputdirFile}' saved '{$df}'", PEAR_LOG_NOTICE); $this->log->flush(); } return TRUE; } else { // ?? if ($this->log) { $this->log->log("ERROR 16, INPUT FILE:'{$inputdirFile}' can't write to output file : '{$df}'", PEAR_LOG_ERR); $this->log->flush(); } return FALSE; } } else { if ($this->log) { $this->log->log("INPUT FILE:'{$inputdirFile}' returning : '{$f}'", PEAR_LOG_NOTICE); $this->log->flush(); } $output = $calendar->createCalendar(); $filesize = strlen($output); if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) { $output = gzencode($output, 9); $filesize = strlen($output); header('Content-Encoding: gzip'); header('Vary: *'); } header('Content-Type: text/calendar; charset=utf-8'); header("Content-Disposition: attachment; filename='{$f}'"); header('Cache-Control: max-age=10'); header('Content-Length: ' . $filesize); echo $output; } return TRUE; }
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* define timezone */ $v = new vcalendar(); // initiate new CALENDAR $t = new vtimezone(); // initiate TIMEZONE $t->setProperty('tzid', 'US-Eastern'); $t->setProperty('last-modified', 1987, 1, 1); $ts = new vtimezone('standard'); $ts->setProperty('dtstart', 1997, 10, 26, 2); $rdate1 = array('year' => 1997, 'month' => 10, 'day' => 26, 'hour' => 02, 'min' => 0, 'sec' => 0); $ts->setProperty('rdate', array($rdate1)); $ts->setProperty('tzoffsetfrom', '-0400'); $ts->setProperty('tzoffsetto', '-0500'); $ts->setProperty('tzname', 'EST'); $t->setComponent($ts); $td = new vtimezone('daylight'); $td->setProperty('dtstart', 1997, 10, 26, 2); $rdate1 = array('year' => 1997, 'month' => 4, 'day' => 6, 'hour' => 02, 'min' => 0, 'sec' => 0); $td->setProperty('rdate', array($rdate1)); $td->setProperty('tzoffsetfrom', '-0500'); $td->setProperty('tzoffsetto', '-0400'); $td->setProperty('tzname', 'EDT'); $t->setComponent($td); $v->setComponent($t); /* alt. production $v->returnCalendar(); // generate and redirect output to user browser */ /* alt. dev. and test */ $str = $v->createCalendar(); // generate and get output in string, for testing?