*/ class ParTCP_Events { public $fileSystem; public $dataDir; public $groupsClass; public $lastInvalidField; public function __construct( $fileSystem, $dataDir ){ $this->fileSystem = $fileSystem; $this->dataDir = $dataDir; } public function get_dir( $id ){ if ( is_object( $this->groupsClass ) && strpos( $id, '/' ) ){ $groupDir = $this->groupsClass->get_dir( dirname( $id ) ); if ( ! $groupDir ){ return FALSE; } $id = basename( $id ); } return ( isset( $groupDir ) ? "{$groupDir}/" : '' ) . "events/{$id}"; } public function get_list( $groupId = FALSE, $status = NULL ){ $dir = 'events'; if ( is_object( $this->groupsClass ) && $groupId ){ $dir = $this->groupsClass->get_dir( $groupId ) . "/{$dir}"; } if ( ! $this->fileSystem->exists( $dir ) ){ return array (); } $eventList = $this->fileSystem->get_listing( $dir ); $events = array (); foreach ( $eventList as $eventId ){ $eventId = ( $groupId ? "{$groupId}/" : '' ) . $eventId; if ( ! ( $data = $this->get_data( $eventId ) ) ){ continue; } $this->update_calculated_fields( $data ); if ( empty( $status ) || $status == 'all' || in_array( $data['status'], (array) $status ) ){ $events[] = $data; } } return $events; } public function get_data( $id ){ $dir = $this->get_dir( $id ); $msg = $this->fileSystem->get_recent_contents( $dir, '[0-9]*-{update,definition,declaration}*' ); if ( empty( $msg ) ){ return FALSE; } $receipt = yaml_parse( $msg ); if ( empty( $receipt['Event-Data'] ) ){ return FALSE; } $data = $receipt['Event-Data']; $data['lot_codes'] = $this->fileSystem->is_not_empty( "{$dir}/lots" ); if ( ! array_key_exists( 'date_open', $data ) ){ $data['date_open'] = $data['created_on']; } $this->update_calculated_fields( $data ); return $data; } public function purge_data( $data ){ $validKeys = [ 'id' => 0, 'name' => 0, 'date' => 0, 'short_description' => 0, 'description' => 0, 'link_url' => 0, 'client_data' => 0, 'estimated_turnout' => 0, 'voting_sort_key' => 0, 'voting_sort_order' => 0, 'issue_service' => 0, 'naming_rules' => 0, 'credential_rules' => 0, 'lot_code_rules' => 0, 'is_demo' => 0, 'is_live' => 0, 'is_local' => 0, 'is_non_anonymous' => 0, 'voting_sort_key' => 0, 'voting_sort_order' => 0, 'wlan_ssid' => 0, 'date_open' => 0, 'date_close' => 0, 'date_finish' => 0, 'badge_categories' => 0, 'badge_expiration' => 0, 'active_sessions' => 0, 'mutex_event' => 0, // event_server, event_id, issue_server, ptcpid_regex, ptcpid_template ]; return array_intersect_key( $data, $validKeys ); } public function validate_fields( $data ){ if ( ! empty( $data['date'] ) ){ $date = strtotime( $data['date'] ); if ( ! $date || date( 'Ymd', $date ) < date('Ymd') ){ $this->lastInvalidField = 'date'; return FALSE; } } if ( ! empty( $data['estimated_turnout'] ) ){ if ( ! is_int( $data['estimated_turnout'] ) || $data['estimated_turnout'] < 1 || $data['estimated_turnout'] > 1000000 ){ $this->lastInvalidField = 'estimated_turnout'; return FALSE; } } if ( ! empty( $data['date_open'] ) ){ $dateOpen = strtotime( $data['date_open'] ); if ( ! $dateOpen || $dateOpen < time() ){ $this->lastInvalidField = 'date_open'; return FALSE; } } if ( ! empty( $data['date_close'] ) ){ $dateClose = strtotime( $data['date_close'] ); if ( ! $dateClose || $dateClose < time() || ( ! empty( $dateOpen ) && $dateClose <= $dateOpen ) ){ $this->lastInvalidField = 'date_close'; return FALSE; } } if ( ! empty( $data['date_finish'] ) ){ $dateFinish = strtotime( $data['date_finish'] ); if ( ! $dateFinish || $dateFinish < time() || ( ! empty( $dateClose ) && $dateFinish <= $dateClose ) ){ $this->lastInvalidField = 'date_finish'; return FALSE; } } return TRUE; } public function set_status( &$data, $newStatus, $timestamp ){ $allowedStatus = [ 'opened' => [ 'planned' ], 'closed' => [ 'opened' ], 'finished' => [ 'opened', 'closed' ], ]; $dateField = [ 'opened' => 'date_open', 'closed' => 'date_close', 'finished' => 'date_finish', ]; if ( ! in_array( $newStatus, array_keys( $allowedStatus ) ) ){ return -1; } if ( ! in_array( $data['status'], $allowedStatus[ $newStatus ] ) ){ return -2; } $data[ $dateField[ $newStatus ] ] = date( 'c', $timestamp ); $data['status'] = $newStatus; return 1; } public function update_calculated_fields( &$data ){ $data['naming_rules']['counter_width'] = strlen( $data['estimated_turnout'] ); $r = $data['naming_rules']; $pattern = str_repeat( '#', strlen( $r['prefix'] ) + $r['counter_width'] + $r['crc_length'] ); $pattern = chunk_split( $pattern, $r['group_length'] ?: 99, $r['group_separator'] ); $data['naming_rules']['pattern'] = substr( $pattern, 0, -1 ); $r = $data['credential_rules']; $pattern = str_repeat( '#', $r['final_length'] ); $pattern = chunk_split( $pattern, $r['group_length'] ?: 99, $r['group_separator'] ); $data['credential_rules']['pattern'] = substr( $pattern, 0, -1 ); $r = $data['lot_code_rules']; $pattern = str_repeat( '#', $r['final_length'] ); $pattern = chunk_split( $pattern, $r['group_length'] ?: 99, $r['group_separator'] ); $data['lot_code_rules']['pattern'] = substr( $pattern, 0, -1 ); $now = time(); if ( ! empty( $data['date_finish'] ) && strtotime( $data['date_finish'] ) <= $now ){ $data['status'] = 'finished'; } elseif ( ! empty( $data['date_close'] ) && strtotime( $data['date_close'] ) <= $now ){ $data['status'] = 'closed'; } elseif ( ! empty( $data['date_open'] ) && strtotime( $data['date_open'] ) <= $now ){ $data['status'] = 'opened'; } else { $data['status'] = 'planned'; } } public function generate_code( $args = [] ){ $charList = (string) ( $args['char_list'] ?? 'ABCDEFGHJKLMNPQRSTUVWXYZ123456789' ); $finalLength = (int) ( $args['final_length'] ?? 16 ); $groupLength = $args['group_length'] ?? 4; $groupSeparator = $args['group_separator'] ?? '-'; $crcLength = $args['crc_length'] ?? 0; $netLength = $finalLength - $crcLength; $charListLength = strlen( $charList ) - 1; $code = ''; for ( $x = 1; $x <= $netLength; $x++ ){ $code .= $charList[ rand( 0, $charListLength ) ]; } if ( $groupLength ){ $code .= str_repeat( ' ', $crcLength ); $code = substr( chunk_split( $code, $groupLength, $groupSeparator ), 0, -1 ); $code = rtrim( $code, ' ' ); } if ( $crcLength ){ $code .= substr( crc32( $code ), - $crcLength ); } elseif ( $groupLength ){ $code = rtrim( $code, $groupSeparator ); } return $code; } public function string_to_name( $str ){ $str = strtolower( $str ); $str = preg_replace( '/\s+/', '-', $str ); $str = str_replace( ['ä','ö','ü','ß'], ['ae','oe','ue','ss'], $str ); $str = html_entity_decode( $str, ENT_QUOTES, 'utf-8' ); $str = htmlentities( $str, ENT_QUOTES, 'utf-8' ); $str = preg_replace( '/(&)([a-z])([a-z]+;)/i', '$2', $str ); $str = preg_replace( '/[^a-zA-Z0-9\-\._]/', '', $str ); return $str; } public function get_statistics( $eventId, $ignoreCounters = FALSE ){ global $Counter; $baseDir = $this->get_dir( $eventId ); $lots = $deposits = $invalidations = $pickUps = $submissions = 0; if ( ! $this->fileSystem->exists( "{$baseDir}/lots" ) || ! $this->fileSystem->exists( "{$baseDir}/participants" ) ){ return [ 'lots_created' => 0, 'lots_invalidated' => 0, 'lotcodes_deposited' => 0, 'lots_redeemed' => 0, 'keys_submitted' => 0 ]; } if ( $ignoreCounters ){ $lots = $this->fileSystem->count_items_in_recent('{$baseDir}/lots'); if ( $lots ){ $pickUps = $this->fileSystem->count_items_in_recent( '{$baseDir}/lots', 'pick-up-note', TRUE ); $invalidations = $this->fileSystem->count_items_in_recent( '{$baseDir}/lots', 'lot-invalidation', TRUE ); $submissions = $this->fileSystem->count_items_in_recent( '{$baseDir}/lots', '*-key-submission', TRUE ); } } else { $Counter->set_base_dir( $baseDir ); $lots = $Counter->get_value( 'lots' ); $deposits = $Counter->get_value( 'deposits' ); $invalidations = $Counter->get_value( 'invalidations' ); $pickUps = $Counter->get_value( 'pick-ups' ); $submissions = $Counter->get_value( 'key-submissions' ); } return [ 'lots_created' => $lots, 'lots_invalidated' => $invalidations, 'lotcodes_deposited' => $deposits, 'lots_redeemed' => $pickUps, 'keys_submitted' => $submissions ]; } public function generate_secret( $eventId ){ $eventDir = $this->get_dir( $eventId ); $path = "{$this->dataDir}/{$eventDir}/secret"; if ( ! file_exists( dirname( $path ) ) ){ mkdir( dirname( $path ), 0700, TRUE ); } file_put_contents( $path, random_bytes( 64 ) ); chmod( $path, 0600 ); return TRUE; } public function get_secret( $eventId ){ $eventDir = $this->get_dir( $eventId ); $path = "{$this->dataDir}/{$eventDir}/secret"; if ( ! file_exists( $path ) ){ return FALSE; } return file_get_contents( $path ); } public function update_issue_service( $eventNew, $eventOld = NULL ){ global $Partcp, $ServerData; $hostOld = $eventOld['issue_service']['host'] ?? ''; $hostNew = $eventNew['issue_service']['host'] ?? ''; if ( ! $hostOld && ! $hostNew ){ return; } if ( $hostOld ){ $host = $hostOld == 'localhost' ? $ServerData['name'] : $hostOld; $Partcp->set_remote_id ( $host ); $data = [ 'voting_server' => in_array( $hostOld, [ $ServerData['name'], 'localhost' ] ) ? 'localhost' : $ServerData['name'], 'event_id' => $eventOld['id'] ]; $msg = [ 'Message-Type' => 'issue-service-update-request', 'Issue-Service-Data' => $data ]; $Partcp->send_message( $msg ); } if ( $hostNew && $hostNew != $hostOld ){ $host = $hostNew == 'localhost' ? $ServerData['name'] : $hostNew; $Partcp->set_remote_id ( $host ); $data = [ 'voting_server' => in_array( $hostNew, [ $ServerData['name'], 'localhost' ] ) ? 'localhost' : $ServerData['name'], 'event_id' => $eventNew['id'], 'id_provider' => $eventNew['issue_service']['id_provider'], 'terms' => $eventNew['issue_service']['terms'] ]; $msg = [ 'Message-Type' => 'issue-service-definition', 'Issue-Service-Data' => $data ]; $Partcp->send_message( $msg ); } } } // end of file models/events.class.php