2014-10-06 03:00:31 +02:00
< ? php
use Illuminate\Console\Command ;
use Symfony\Component\Console\Input\InputOption ;
use Symfony\Component\Console\Input\InputArgument ;
2014-10-07 23:37:06 +02:00
use PHPBenchTime\Timer ;
2014-10-06 03:00:31 +02:00
class ImportTimesheetData extends Command {
2014-10-06 20:42:07 +02:00
protected $name = 'ninja:import-timesheet-data' ;
protected $description = 'Import timesheet data' ;
2014-10-06 23:59:32 +02:00
2014-10-06 20:42:07 +02:00
public function fire () {
$this -> info ( date ( 'Y-m-d' ) . ' Running ImportTimesheetData...' );
2014-10-07 23:37:06 +02:00
2014-10-06 23:59:32 +02:00
// Seems we are using the console timezone
DB :: statement ( " SET SESSION time_zone = '+00:00' " );
// Get the Unix epoch
$unix_epoch = new DateTime ( '1970-01-01T00:00:01' , new DateTimeZone ( " UTC " ));
2014-10-06 03:00:31 +02:00
// Create some initial sources we can test with
$user = User :: first ();
if ( ! $user ) {
$this -> error ( " Error: please create user account by logging in " );
return ;
}
2014-10-06 23:59:32 +02:00
2014-10-06 03:00:31 +02:00
// TODO: Populate with own test data until test data has been created
// Truncate the tables
2014-10-07 00:00:57 +02:00
/* $this -> info ( " Truncate tables " );
2014-10-06 03:00:31 +02:00
DB :: statement ( 'SET FOREIGN_KEY_CHECKS=0;' );
DB :: table ( 'projects' ) -> truncate ();
DB :: table ( 'project_codes' ) -> truncate ();
DB :: table ( 'timesheet_event_sources' ) -> truncate ();
DB :: table ( 'timesheet_events' ) -> truncate ();
2014-10-07 00:00:57 +02:00
DB :: statement ( 'SET FOREIGN_KEY_CHECKS=1;' ); */
2014-10-06 23:59:32 +02:00
2014-10-06 03:00:31 +02:00
if ( ! Project :: find ( 1 )) {
$this -> info ( " Import old project codes " );
$oldcodes = json_decode ( file_get_contents ( " /home/tlb/git/itktime/codes.json " ), true );
foreach ( $oldcodes as $name => $options ) {
$project = Project :: createNew ( $user );
$project -> name = $options [ 'description' ];
$project -> save ();
2014-10-06 20:42:07 +02:00
2014-10-06 03:00:31 +02:00
$code = ProjectCode :: createNew ( $user );
$code -> name = $name ;
$project -> codes () -> save ( $code );
}
}
2014-10-06 20:42:07 +02:00
2014-10-06 03:00:31 +02:00
if ( ! TimesheetEventSource :: find ( 1 )) {
$this -> info ( " Import old event sources " );
2014-10-06 20:42:07 +02:00
2014-10-06 03:00:31 +02:00
$oldevent_sources = json_decode ( file_get_contents ( " /home/tlb/git/itktime/employes.json " ), true );
foreach ( $oldevent_sources as $source ) {
$event_source = TimesheetEventSource :: createNew ( $user );
$event_source -> name = $source [ 'name' ];
$event_source -> url = $source [ 'url' ];
$event_source -> owner = $source [ 'owner' ];
$event_source -> type = 'ical' ;
2014-10-06 20:42:07 +02:00
//$event_source->from_date = new DateTime("2009-01-01");
2014-10-06 03:00:31 +02:00
$event_source -> save ();
}
}
2014-10-06 20:42:07 +02:00
2014-10-06 03:00:31 +02:00
// Add all URL's to Curl
$this -> info ( " Download ICAL feeds " );
2014-10-07 23:37:06 +02:00
$T = new Timer ;
$T -> start ();
2014-10-06 20:42:07 +02:00
2014-10-07 23:37:06 +02:00
$T -> lap ( " Get Event Sources " );
$event_sources = TimesheetEventSource :: all (); // TODO: Filter based on ical feeds
$T -> lap ( " Get ICAL responses " );
2014-10-06 03:00:31 +02:00
$urls = [];
$event_sources -> map ( function ( $item ) use ( & $urls ) {
$urls [] = $item -> url ;
});
2014-10-07 23:37:06 +02:00
$icalresponses = TimesheetUtils :: curlGetUrls ( $urls );
$T -> lap ( " Fetch all codes so we can do a quick lookup " );
2014-10-06 20:42:07 +02:00
$codes = array ();
2014-10-06 03:00:31 +02:00
ProjectCode :: all () -> map ( function ( $item ) use ( & $codes ) {
$codes [ $item -> name ] = $item ;
});
2014-10-07 23:37:06 +02:00
2014-10-06 03:00:31 +02:00
$this -> info ( " Start parsing ICAL files " );
2014-10-06 20:42:07 +02:00
foreach ( $event_sources as $i => $event_source ) {
2014-10-07 23:37:06 +02:00
if ( ! is_array ( $icalresponses [ $i ])) {
2014-10-06 20:42:07 +02:00
$this -> info ( " Find events in " . $event_source -> name );
2014-10-07 23:37:06 +02:00
file_put_contents ( " /tmp/ " . $event_source -> name . " .ical " , $icalresponses [ $i ]); // FIXME: Remove
$T -> lap ( " Split on events for " . $event_source -> name );
2014-10-20 20:59:46 +02:00
// Check if the file is complete
if ( ! preg_match ( " /^ \ s*BEGIN:VCALENDAR/ " , $icalresponses [ $i ]) || ! preg_match ( " /END:VCALENDAR \ s* $ / " , $icalresponses [ $i ])) {
$this -> error ( " Missing start or end of ical file " );
continue ;
}
// Extract all events from ical file
2014-10-07 23:37:06 +02:00
if ( preg_match_all ( '/BEGIN:VEVENT\r?\n(.+?)\r?\nEND:VEVENT/s' , $icalresponses [ $i ], $icalmatches )) {
$this -> info ( " Found " . ( count ( $icalmatches [ 1 ]) - 1 ) . " events " );
2014-10-20 20:59:46 +02:00
$T -> lap ( " Fetch all uids and last updated at so we can do a quick lookup to find out if the event needs to be updated in the database " . $event_source -> name );
2014-10-06 03:20:47 +02:00
$uids = [];
2014-10-20 20:59:46 +02:00
$org_deleted = []; // Create list of events we know are deleted on the source, but still have in the db
$event_source -> events () -> withTrashed () -> get ([ 'uid' , 'org_updated_at' , 'updated_data_at' , 'org_deleted_at' ]) -> map ( function ( $item ) use ( & $uids , & $org_deleted ) {
2014-10-07 23:37:06 +02:00
if ( $item -> org_updated_at > $item -> updated_data_at ) {
$uids [ $item -> uid ] = $item -> org_updated_at ;
} else {
$uids [ $item -> uid ] = $item -> updated_data_at ;
}
2014-10-20 20:59:46 +02:00
if ( $item -> org_deleted_at > '0000-00-00 00:00:00' ) {
$org_deleted [ $item -> uid ] = $item -> updated_data_at ;
}
2014-10-06 23:59:32 +02:00
});
2014-10-07 23:37:06 +02:00
$deleted = $uids ;
2014-10-06 23:59:32 +02:00
// Loop over all the found events
2014-10-07 23:37:06 +02:00
$T -> lap ( " Parse events for " . $event_source -> name );
2014-10-06 20:42:07 +02:00
foreach ( $icalmatches [ 1 ] as $eventstr ) {
2014-10-06 03:00:31 +02:00
//print "---\n";
//print $eventstr."\n";
//print "---\n";
//$this->info("Match event");
# Fix lines broken by 76 char limit
$eventstr = preg_replace ( '/\r?\n\s/s' , '' , $eventstr );
//$this->info("Parse data");
2014-10-06 20:42:07 +02:00
$data = TimesheetUtils :: parseICALEvent ( $eventstr );
if ( $data ) {
2014-10-06 03:00:31 +02:00
// Extract code for summary so we only import events we use
2014-10-06 20:42:07 +02:00
list ( $codename , $tags , $title ) = TimesheetUtils :: parseEventSummary ( $data [ 'summary' ]);
if ( $codename != null ) {
$event = TimesheetEvent :: createNew ( $user );
2014-10-20 20:59:46 +02:00
// Copy data to new object
2014-10-06 20:42:07 +02:00
$event -> uid = $data [ 'uid' ];
2014-10-20 20:59:46 +02:00
$event -> summary = $title ;
$event -> org_data = $eventstr ;
$event -> org_code = $codename ;
if ( isset ( $data [ 'description' ])) {
$event -> description = $data [ 'description' ];
}
$event -> owner = $event_source -> owner ;
$event -> timesheet_event_source_id = $event_source -> id ;
if ( isset ( $codes [ $codename ])) {
$event -> project_id = $codes [ $codename ] -> project_id ;
$event -> project_code_id = $codes [ $codename ] -> id ;
}
if ( isset ( $data [ 'location' ])) {
$event -> location = $data [ 'location' ];
}
2014-10-06 23:59:32 +02:00
2014-10-06 20:42:07 +02:00
# Add RECURRENCE-ID to the UID to make sure the event is unique
if ( isset ( $data [ 'recurrence-id' ])) {
2014-10-06 23:59:32 +02:00
$event -> uid .= " :: " . $data [ 'recurrence-id' ];
2014-10-06 20:42:07 +02:00
}
2014-10-06 23:59:32 +02:00
2014-10-20 20:59:46 +02:00
//TODO: Add support for recurring event, make limit on number of events created : https://github.com/tplaner/When
// Bail on RRULE as we don't support that
if ( isset ( $event [ 'rrule' ])) {
die ( " Recurring event not supported: { $event [ 'summary' ] } - { $event [ 'dtstart' ] } " );
}
2014-10-06 20:42:07 +02:00
// Convert to DateTime objects
foreach ([ 'dtstart' , 'dtend' , 'created' , 'last-modified' ] as $key ) {
// Parse and create DataTime object from ICAL format
list ( $dt , $timezone ) = TimesheetUtils :: parseICALDate ( $data [ $key ]);
// Handle bad dates in created and last-modified
2014-10-06 23:59:32 +02:00
if ( $dt == null || $dt < $unix_epoch ) {
2014-10-06 20:42:07 +02:00
if ( $key == 'created' || $key == 'last-modified' ) {
2014-10-06 23:59:32 +02:00
$dt = $unix_epoch ; // Default to UNIX epoch
2014-10-20 20:59:46 +02:00
$event -> import_warning = " Could not parse date for $key : ' " . $data [ $key ] . " ' so default to UNIX Epoc \n " ;
2014-10-06 03:00:31 +02:00
} else {
2014-10-20 20:59:46 +02:00
$event -> import_error = " Could not parse date for $key : ' " . $data [ $key ] . " ' so default to UNIX Epoc \n " ;
// TODO: Bail on this event or write to error table
die ( " Could not parse date for $key : ' " . $data [ $key ] . " ' \n " );
2014-10-06 03:00:31 +02:00
}
}
2014-10-06 20:42:07 +02:00
// Assign DateTime object to
switch ( $key ) {
case 'dtstart' :
$event -> start_date = $dt ;
2014-10-07 23:37:06 +02:00
if ( $timezone ) {
$event -> org_start_date_timezone = $timezone ;
}
2014-10-06 20:42:07 +02:00
break ;
case 'dtend' :
$event -> end_date = $dt ;
2014-10-07 23:37:06 +02:00
if ( $timezone ) {
$event -> org_end_date_timezone = $timezone ;
}
2014-10-06 20:42:07 +02:00
break ;
2014-10-06 23:59:32 +02:00
case 'created' :
$event -> org_created_at = $dt ;
2014-10-06 20:42:07 +02:00
break ;
2014-10-06 23:59:32 +02:00
case 'last-modified' :
$event -> org_updated_at = $dt ;
2014-10-06 20:42:07 +02:00
break ;
}
}
// Check that we are witin the range
if ( $event_source -> from_date != null ) {
$from_date = new DateTime ( $event_source -> from_date , new DateTimeZone ( 'UTC' ));
if ( $from_date > $event -> end_date ) {
// Skip this event
echo " Skiped: $codename : $title\n " ;
continue ;
}
}
2014-10-07 23:37:06 +02:00
2014-10-06 20:42:07 +02:00
// Calculate number of hours
$di = $event -> end_date -> diff ( $event -> start_date );
$event -> hours = $di -> h + $di -> i / 60 ;
2014-10-20 20:59:46 +02:00
2014-10-07 23:37:06 +02:00
// Check for events we already have
if ( isset ( $uids [ $event -> uid ])) {
// Remove from deleted list
unset ( $deleted [ $event -> uid ]);
// See if the event has been updated compared to the one in the database
$db_event_org_updated_at = new DateTime ( $uids [ $event -> uid ], new DateTimeZone ( 'UTC' ));
2014-10-20 20:59:46 +02:00
// Check if same or older version of new event then skip
2014-10-07 23:37:06 +02:00
if ( $event -> org_updated_at <= $db_event_org_updated_at ) {
// SKIP
// Updated version of the event
} else {
// Get the old event from the database
/* @var $db_event TimesheetEvent */
$db_event = $event_source -> events () -> where ( 'uid' , $event -> uid ) -> firstOrFail ();
$changes = $db_event -> toChangesArray ( $event );
// Make sure it's more than the org_updated_at that has been changed
if ( count ( $changes ) > 1 ) {
2014-10-20 20:59:46 +02:00
// Check if we have manually changed the event in the database or used it in a timesheet
2014-10-07 23:37:06 +02:00
if ( $db_event -> manualedit || $db_event -> timesheet ) {
$this -> info ( " Updated Data " );
$db_event -> updated_data = $event -> org_data ;
$db_event -> updated_data_at = $event -> org_updated_at ;
// Update the db_event with the changes
} else {
$this -> info ( " Updated Event " );
foreach ( $changes as $key => $value ) {
if ( $value == null ) {
unset ( $db_event -> $key );
} else {
$db_event -> $key = $value ;
}
}
}
} else {
$this -> info ( " Nothing Changed " );
// Nothing has been changed so update the org_updated_at
$db_event -> org_updated_at = $changes [ 'org_updated_at' ];
}
$db_event -> save ();
}
} else {
try {
$this -> info ( " New event: " . $event -> summary );
2014-10-06 23:59:32 +02:00
$event -> save ();
2014-10-07 23:37:06 +02:00
} catch ( Exception $ex ) {
echo " ' " . $event -> summary . " ' \n " ;
var_dump ( $data );
echo $ex -> getMessage ();
echo $ex -> getTraceAsString ();
exit ( 0 );
}
2014-10-06 03:00:31 +02:00
}
2014-10-07 23:37:06 +02:00
// Add new uid to know uids
$uids [ $event -> uid ] = $event -> org_updated_at ;
2014-10-06 03:00:31 +02:00
}
}
}
2014-10-20 20:59:46 +02:00
// Delete events in database that no longer exists in the source
2014-10-07 23:37:06 +02:00
foreach ( $deleted as $uid => $lastupdated_date ) {
2014-10-20 20:59:46 +02:00
// Skip we already marked this a deleted
if ( isset ( $org_deleted [ $uid ])) {
unset ( $deleted [ $uid ]);
continue ;
}
// Delete or update event in db
$db_event = $event_source -> events () -> where ( 'uid' , $uid ) -> firstOrFail ();
if ( $db_event -> timesheet_id === null && ! $db_event -> manualedit ) {
// Hard delete if this event has not been assigned to a timesheet or have been manually edited
$db_event -> forceDelete ();
} else {
// Mark as deleted in source
$db_event -> org_deleted_at = new DateTime ( 'now' , new DateTimeZone ( 'UTC' ));
$db_event -> save ();
}
2014-10-07 23:37:06 +02:00
}
2014-10-20 20:59:46 +02:00
$this -> info ( " Deleted " . count ( $deleted ) . " events " );
2014-10-07 23:37:06 +02:00
2014-10-06 03:00:31 +02:00
} else {
2014-10-20 20:59:46 +02:00
// TODO: Parse error
2014-10-06 03:00:31 +02:00
}
2014-10-07 23:37:06 +02:00
2014-10-06 03:00:31 +02:00
} else {
2014-10-20 20:59:46 +02:00
// TODO: Curl Error
2014-10-06 03:00:31 +02:00
}
}
2014-10-06 20:42:07 +02:00
2014-10-07 23:37:06 +02:00
foreach ( $T -> end ()[ 'laps' ] as $lap ) {
echo number_format ( $lap [ 'total' ], 3 ) . " : { $lap [ 'name' ] } \n " ;
2014-10-06 03:00:31 +02:00
}
2014-10-07 23:37:06 +02:00
$this -> info ( 'Done' );
2014-10-06 03:00:31 +02:00
}
2014-10-06 20:42:07 +02:00
protected function getArguments () {
return array (
);
}
protected function getOptions () {
return array (
);
}
}