store = $store; $this->box = $box; } /** * Creates an account. * * @param string $channel Channel ID. * @param string $country Two-letter country code. * @param string $label Label. * @param array $credentials Application credentials (e.g. client_id, client_secret). * @return int Account ID. * @throws \InvalidArgumentException On invalid data. */ public function create( string $channel, string $country, string $label, array $credentials = array() ): int { return $this->store->insert( array( 'channel' => $channel, 'country' => $country, 'label' => $label, 'credentials' => $this->seal( $credentials ), ) ); } /** * One account with secrets decrypted, or null. * * @param int $id Account ID. */ public function get( int $id ): ?Account { $row = $this->store->find( $id ); return null === $row ? null : $this->hydrate( $row ); } /** * All accounts (secrets decrypted). * * @param string|null $channel Only this channel. * @return Account[] */ public function all( ?string $channel = null ): array { return array_map( array( $this, 'hydrate' ), $this->store->all( $channel ) ); } /** * Replaces the application credentials. * * @param int $id Account ID. * @param array $credentials Credentials. */ public function save_credentials( int $id, array $credentials ): void { $this->store->update( $id, array( 'credentials' => $this->seal( $credentials ) ) ); } /** * Stores OAuth tokens. * * @param int $id Account ID. * @param array $tokens Tokens. */ public function save_tokens( int $id, array $tokens ): void { $this->store->update( $id, array( 'tokens' => $this->seal( $tokens ) ) ); } /** * Disconnects: removes tokens and the stored user name. * * @param int $id Account ID. */ public function clear_tokens( int $id ): void { $this->store->update( $id, array( 'tokens' => '', 'account_name' => '', 'is_business' => 0, ) ); } /** * Stores who is connected. * * @param int $id Account ID. * @param string $name User name. * @param bool $is_business Business account flag. */ public function save_profile( int $id, string $name, bool $is_business ): void { $this->store->update( $id, array( 'account_name' => $name, 'is_business' => $is_business, ) ); } /** * Changes the label. * * @param int $id Account ID. * @param string $label Label. */ public function rename( int $id, string $label ): void { $this->store->update( $id, array( 'label' => $label ) ); } /** * Deletes an account. * * @param int $id Account ID. */ public function delete( int $id ): void { $this->store->delete( $id ); } /** * Encrypts an array (empty array = empty string). * * @param array $data Data. */ private function seal( array $data ): string { return $data ? $this->box->encrypt( Json::encode( $data ) ) : ''; } /** * Decrypts a sealed string; null if it cannot be read. * * @param string $sealed Sealed string. */ private function unseal( string $sealed ): ?array { if ( '' === $sealed ) { return array(); } $plain = $this->box->decrypt( $sealed ); if ( null === $plain ) { return null; } $data = json_decode( $plain, true ); return is_array( $data ) ? $data : null; } /** * Account from a stored row. * * @param array $row Row. */ private function hydrate( array $row ): Account { $account = new Account(); $account->id = (int) $row['id']; $account->channel = (string) $row['channel']; $account->country = (string) $row['country']; $account->label = (string) $row['label']; $account->status = (string) $row['status']; $account->account_name = (string) ( $row['account_name'] ?? '' ); $account->is_business = ! empty( $row['is_business'] ); $credentials = $this->unseal( (string) ( $row['credentials'] ?? '' ) ); $tokens = $this->unseal( (string) ( $row['tokens'] ?? '' ) ); $account->readable = null !== $credentials && null !== $tokens; $account->credentials = $credentials ?? array(); $account->tokens = $tokens ?? array(); return $account; } }