1..9 => Finding::toArray() list, anomalies first * counted array Catalogue code of a COUNTED finding => count * not_looked array[] NotLookedEntry::toArray() list * capped array code => findings counted beyond FINDINGS_PER_CODE_MAX, not listed * (D-13); they count in the summary * * @package Timaub\AfterHackChecklist */ namespace Timaub\AfterHackChecklist\Domain; final class Report { /** @var array */ public $controls = array(); /** @var array */ public $counted = array(); /** @var NotLookedEntry[] */ public $notLooked = array(); /** @var array */ public $capped = array(); /** * @param Finding[] $findings Reported findings (COUNTED and member codes excluded by the caller). * @param NotLookedEntry[] $notLooked Entries, the fixed ones included. * @param array $counted Counters. * @param array $capped Code => findings counted but not listed (D-13). */ public static function build( array $findings, array $notLooked, array $counted, array $capped = array() ): self { $r = new self(); for ( $i = 1; $i <= 9; $i++ ) { $r->controls[ $i ] = array(); } $rank = array_flip( Level::REPORTED ); foreach ( $findings as $f ) { $r->controls[ $f->control ][] = $f; } foreach ( $r->controls as $i => $list ) { // Stable sort by level. $keyed = array(); foreach ( $list as $n => $f ) { $keyed[] = array( $rank[ $f->level ], $n, $f ); } sort( $keyed ); $r->controls[ $i ] = array_map( function ( $k ) { return $k[2]; }, $keyed ); } $r->notLooked = $notLooked; $r->counted = $counted; $r->capped = array_map( 'intval', $capped ); return $r; } /** @return array{anomaly:int, suspect:int, to_identify:int, not_looked:int} */ public function summary(): array { $s = array( Level::ANOMALY => 0, Level::SUSPECT => 0, Level::TO_IDENTIFY => 0, ); foreach ( $this->controls as $list ) { foreach ( $list as $f ) { ++$s[ $f->level ]; } } foreach ( $this->capped as $code => $n ) { $level = Catalogue::finding( (string) $code )[1]; if ( isset( $s[ $level ] ) ) { $s[ $level ] += $n; } } $s['not_looked'] = count( $this->notLooked ); return $s; } public function summaryText(): string { $s = $this->summary(); return Catalogue::render( Catalogue::SUMMARY, array( $s[ Level::ANOMALY ], $s[ Level::SUSPECT ], $s[ Level::TO_IDENTIFY ], $s['not_looked'] ) ); } public function toArray(): array { $controls = array(); foreach ( $this->controls as $i => $list ) { $controls[ $i ] = array_map( function ( Finding $f ) { return $f->toArray(); }, $list ); } return array( 'summary' => $this->summary(), 'summary_text' => $this->summaryText(), 'controls' => $controls, 'counted' => $this->counted, 'not_looked' => array_map( function ( NotLookedEntry $e ) { return $e->toArray(); }, $this->notLooked ), 'capped' => $this->capped, ); } public static function fromArray( array $a ): self { $findings = array(); foreach ( (array) $a['controls'] as $list ) { foreach ( (array) $list as $f ) { $findings[] = Finding::fromArray( $f ); } } $notLooked = array_map( array( NotLookedEntry::class, 'fromArray' ), (array) $a['not_looked'] ); return self::build( $findings, $notLooked, isset( $a['counted'] ) ? (array) $a['counted'] : array(), isset( $a['capped'] ) ? (array) $a['capped'] : array() ); } }