UID of the diplome * @img_url URL of the string to extract from * @start_d Date of the first day * * @return instance Instance * ---------------------------------------------------------*/ public function __construct($d_uid=null, $img_url=null, $start_d=null){ /* [1] Check arguments =========================================================*/ { /* (1) Check type */ if( !is_string($img_url) || !is_string($start_d) ) throw new \Exception("CalendarExtractor.__construct(, , ) expected but CalendarExtractor.__construct(<".gettype($d_uid).">, <".gettype($img_url).">, <".gettype($start_d).">) received"); /* (2) Check @img_url link availability */ if( !($img_head=@get_headers($img_url)) ) throw new \Exception("CalendarExtractor.__construct(, , ) received but cannot reach "); if( !preg_match('@HTTP.+200@', $img_head[0]) ) throw new \Exception("CalendarExtractor.__construct(, , ) received but cannot reach "); /* (3) Check @start_d format */ if( !preg_match("@^\d{1,2}-\d{1,2}-\d{3,}$@", $start_d) ) throw new \Exception("CalendarExtractor.__construct(, , ) received has not the correct format"); /* (4) Check @d_uid format */ if( !preg_match("@^T\d+$@", $d_uid) ) throw new \Exception("CalendarExtractor.__construct(, , ) received has not the correct format"); } /* [2] Fetch file =========================================================*/ { /* (1) Try to open file with GD */ $this->img_res = @imagecreatefrompng($img_url); /* (2) Manage error */ if( !$this->img_res ) throw new \Exception("URL is not a JPEG image, or is unreachable"); /* (3) Register data */ $this->img_url = $img_url; $this->d_uid = $d_uid; $this->start_d = $start_d; } } /* (2) Extracts calendar data from image * * @return error FALSE on error * ---------------------------------------------------------*/ public function process(){ /* [1] Global variables =========================================================*/ { /* (1) Image size */ { // {1} Request for size // $img_siz = @getimagesize($this->img_url); // {2} If error // if( !$img_siz ) throw new \Exception("Cannot get image size"); $img_siz = [ 'w' => $img_siz[0], 'h' => $img_siz[1] ]; } } /* [2] Extract day limits =========================================================*/ { /* (1) Will contain the column+1 w index */ $col_ind = []; /* (2) Extract column indexes */ for( $x = 0 ; $x < $img_siz['w'] ; $x++ ){ if( $this->getColor($x, 62) <= 10 ) $col_ind[] = $x+1; } } /* [3] For each day -> get events =========================================================*/ { $uid = 0; /* (1) For each day */ for( $day_n = 0 ; $day_n < count($col_ind)-1 ; $day_n++ ){ $col_x = $col_ind[$day_n]; /* (2) For each y pixel -> exctract event */ for( $y = self::$start_y ; $y < self::$stop_y ; $y++ ){ /* (3) Get current color + next */ $p = $this->getColor($col_x, $y); $p1 = $this->getColor($col_x, $y+1); $color = '#'.str_pad(dechex($p1), 6, '0', STR_PAD_LEFT); /* (4) If on black pixel and next not white */ if( $p == 0 && $p1 != 0xffffff ){ // {1} calculate time // $start_y = $y; $time = $this->yToTime($day_n, $start_y); // {2} Incr uid // $uid++; // {3} Store event start // $this->event[$uid][$time] = []; // {4} Seek end of event // $y++; while( $y < self::$stop_y && $this->getColor($col_x, $y) != 0 ) $y++; // {5} If end reached // $this->event[$uid][$time] = [ $this->yToTime($day_n, $y) ]; // {6} Exctract event's image // $ev = $this->extractEvent("$time-$uid", [$col_x, $start_y+1], [$col_ind[$day_n+1]-1, $y]); /* {7} Check @event if already exists */ { $read_name = is_null($ev['name']) ? '?' : $ev['name']; $sqlr = DatabaseDriver::getPDO()->prepare("SELECT id_event FROM event WHERE basename = :bn AND color = :c AND id_diplome = :idd"); $sqlr->execute([ ':bn' => $read_name, ':c' => $color, ':idd' => $this->d_uid ]); $fetched = $sqlr->fetch(); // {7.1} If not found in db -> insert // if( !$fetched ){ // {7.2} Insert new event // $sqlr = DatabaseDriver::getPDO()->prepare("INSERT INTO event(id_event,basename,color,id_diplome,name) VALUES(DEFAULT,:bn,:c,:idd,:n)"); $sqlr->execute([ ':c' => $color, ':idd' => $this->d_uid, ':bn' => $read_name, ':n' => $read_name ]); // {7.3} Store id in current event // $event_id = DatabaseDriver::getPDO()->lastInsertId(); }else $event_id = $fetched['id_event']; } /* (8) Check @location if already exists */ { $read_location = is_null($ev['location']) ? '?' : $ev['location']; $sqlr = DatabaseDriver::getPDO()->prepare("SELECT id_location FROM location WHERE basename = :bn AND id_event = :ide"); $sqlr->execute([ ':bn' => $read_location, ':ide' => $event_id ]); $fetched = $sqlr->fetch(); // {8.1} If not found in db -> insert // if( !$fetched ){ // {8.2} Insert new location // $sqlr = DatabaseDriver::getPDO()->prepare("INSERT INTO location(id_location,id_event,basename,name) VALUES(DEFAULT,:ide,:bn,:n)"); $sqlr->execute([ ':ide' => $event_id, ':bn' => $read_location, ':n' => $read_location ]); // {8.3} Store id in current location // $location_id = DatabaseDriver::getPDO()->lastInsertId(); }else $location_id = $fetched['id_location']; } /* (9) Store local data for ics */ $this->event[$uid][$time][1] = $location_id; } } } } } /* (3) Exctracts an event using OCR * * @uid Image uid * @start Start rect (x, y) * @stop Stop rect (x, y) * ---------------------------------------------------------*/ public function extractEvent($uid, $start=null, $stop=null){ $link = __ROOT__."/tmp/$uid.jpeg"; $width = $stop[0]-$start[0]; $height = $stop[1]-$start[1]; $resize_factor = 2; /* [1] Get the right clip =========================================================*/ { /* (1) Create clipped copy */ $clip = \imagecreatetruecolor($width*$resize_factor, $height*$resize_factor); $copied = \imagecopyresized( $clip, // destin img $this->img_res, // source img 0, // dest x 0, // dest y $start[0], // src x $start[1], // src y $width*$resize_factor, // dest w $height*$resize_factor, // dest h $width, // src w $height // src h ); /* (2) Manage copy error */ if( !$copied ) return [ 'name' => null, 'location' => null ]; /* (3) Save to jpeg */ \imagesavealpha($clip, true); // ob_start(); \imagejpeg($clip, $link); // $image_data = \base64_encode(ob_get_contents()); // ob_end_clean(); } /* Remove file */ unlink($link); /* [2] Apply Tesseract =========================================================*/ { /* (1) Load image with tesseract */ try{ $tesseract = new Tesseract($link); $read = $tesseract->read(); /* (2) Manage error */ }catch(\Exception $e){ return [ 'name' => null, 'location' => null ]; } } /* [3] End procedure =========================================================*/ /* (1) Return read value */ return $read; } /* (3) Get a pixel's color * * @x X coordinate * @y Y coordinate * * @return color Raw color * ---------------------------------------------------------*/ private function getColor($x, $y){ return imagecolorat($this->img_res, $x, $y); } /* (4) Get time from a y-coordinate * * @day_n Day relative index * @y y Coordinate * * @return time Formatted time (HH:mm) * ---------------------------------------------------------*/ private function yToTime($day_n, $y){ /* [1] Get the date =========================================================*/ { /* (1) Get the day's date */ $day_ts = strtotime($this->start_d." + $day_n days"); /* (2) Format it */ $day = date('Ymd', $day_ts); } /* [2] Get the time =========================================================*/{ /* (1) Get the time */ $time = self::$start_h + (self::$stop_h-self::$start_h) * ($y-self::$start_y) / (self::$stop_y-self::$start_y); /* (2) Calculate hour form time */ $hour = floor($time); /* (3) Calculate min from time */ $min = round( 60 * ($time-$hour) ); /* (4) Round minutes to 10min */ $min = round($min/10)*10; /* (5) Format to 2-digit */ $hour = ( $hour < 10 ) ? "0$hour" : $hour; $min = ( $min < 10 ) ? "0$min" : $min ; } /* [3] Convert to GMT (UTC+0) =========================================================*/ /* (1) Set fixed timezone offset */ $tz_offset = +2; /* (2) Get GMT (UTC+0) timestamp */ $ts = strtotime("${day} $hour:$min:00") - (3600*$tz_offset); /* (3) Return GMT date */ return date("Ymd\THis\Z", $ts); } /* (5) Generate ICS output * * @return ics ICS Representation of the events * ---------------------------------------------------------*/ public function toIcs(){ $RAW = ""; /* [1] For each event =========================================================*/ foreach($this->event as $event_col=>$events){ /* (2) For each event of each type ---------------------------------------------------------*/ foreach($events as $start_t=>$data){ /* (1) If a name -> Search if there is a correction in the database */ $sqlr = DatabaseDriver::getPDO()->prepare("SELECT e.id_event as ide, l.id_location as idl, l.id_location as idl, e.name as ename, l.name as lname FROM event as e, location as l WHERE e.id_event = l.id_event AND l.id_location= :idl"); $sqlr->execute([ ':idl' => $data[1] ]); /* (2) Default values */ $ide = -1; $idl = -1; $name = '?'; $location = '?'; /* (2) If a match found -> set it */ if( ($fetched=$sqlr->fetch()) ){ $ide = $fetched['ide']; $idl = $fetched['idl']; $name = $fetched['ename']; $location = $fetched['lname']; } /* (3) Build ICS event */ $RAW .= "BEGIN:VEVENT\n"; $RAW .= "DTSTAMP:".gmdate("Ymd\THis\Z", time())."\n"; // required $RAW .= "DTSTART:${start_t}\n"; $RAW .= "DTEND:${data[0]}\n"; $RAW .= "UID:$start_t\n"; // required $RAW .= "DESCRIPTION:event@$ide\n"; // event@id_event $RAW .= "SUMMARY:$name\n"; $RAW .= "DESCRIPTION:location@$idl\n"; // location@id_location $RAW .= "LOCATION:$location\n"; $RAW .= "CATEGORIES: UPPA Calendar\n"; $RAW .= "END:VEVENT\n"; } } return $RAW; } }