setHandlers(self::$handlers); $l->info('Initialized'); return $l; } } class PhotoPrism { protected string $base_url = 'https://photos.phlaym.net'; protected string $api_url = ''; protected ?string $session_id = null; protected array $config; protected LoggerInterface $logger; public function __construct( array $config, ?string $log_path = null ) { $this->api_url = $this->base_url.'/api/v1'; $this->config = $config; if (empty($log_path)) { $log_path = __DIR__.'/logs/log.log'; } LoggerFactory::addHandler(new RotatingFileHandler($log_path, 5, Logger::DEBUG, true)); $this->logger = LoggerFactory::create('PhotoPrismUpload'); if (isset($_SESSION['pp_sessionid'])) { $this->session_id = $_SESSION['pp_sessionid']; } } private function parseHeaders(string $response): array { $response = explode("\r\n\r\n", $response, 2); $headers = $response[0]; if ($headers === 'HTTP/1.1 100 Continue') { $response = explode("\r\n\r\n", $response[1], 2); $headers = $response[0]; } if (isset($response[1])) { $content = $response[1]; } else { $content = ''; } // this is not a good way to parse http headers // it will not (for example) take into account multiline headers // but what we're looking for is pretty basic, so we can ignore those shortcomings $response_headers = explode("\r\n", $headers); $header_arr = []; foreach ($response_headers as $header) { $header = explode(': ', $header, 2); if (count($header) < 2) { continue; } list($k,$v) = $header; $header_arr[$k] = $v; } return ['headers' => $header_arr, 'content' => $content]; } private function makeRequest( string $method, string $path, array $data = [], string $content_type = 'application/json' ): string { $url = $this->api_url.$path; $method = strtolower($method); $query_data = []; $headers = []; $result = null; try { if (is_array($data) && $method !== 'post-raw') { if ($content_type === 'application/json') { $query_data = json_encode($data); } elseif ($content_type === 'multipart/form-data') { $query_data = $data; } else { $query_data = http_build_query($data); } } $ch = curl_init(); if ($method === 'get' && !empty($query_data)) { $url .= '?'.$query_data; } if ($method !== 'get') { $headers[] = 'Content-Type: '.$content_type; } if (!empty($this->session_id)) { $headers[] = 'X-Session-Id: ' . $this->session_id; } $this->logger->info($method .' request to ' . $url); $this->logger->debug('Headers ' . json_encode($headers)); $this->logger->debug('postfields data ' . json_encode($data)); $this->logger->debug('postfields ' . json_encode($query_data)); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, $method !== 'get'); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLINFO_HEADER_OUT, true); if ($method !== 'get' && !empty($query_data)) { curl_setopt($ch, CURLOPT_POSTFIELDS, $query_data); } $output = curl_exec($ch); $request = curl_getinfo($ch, CURLINFO_HEADER_OUT); $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($output === false) { throw new NetworkException("Error sending request to " . $url, 0); } if (empty($output) || $output === false) { $e = new NetworkException("No answer from" . $url, 0); $this->logger->error("Error sending request", ['Exception' => $e]); throw $e; } if ($http_status === 0) { throw new NetworkException('Unable to connect to API ' . $url); } if ($http_status >= 400) { throw new NetworkException('Invalid response ' . $url . ': ' . $http_status); } $result = $this->parseHeaders($output); } catch (\Exception $e) { $this->logger->error("Error sending request", ['Exception' => $e]); throw new NetworkException("Error sending request to " . $url, 0, $e); } return $result['content']; } public function login(bool $force = false) { if (!empty($this->session_id) && !$force) { $this->logger->info('Skipping login, already logged in'); return; } $data = [ 'username' => $this->config['username'], 'password' => $this->config['password'], ]; $res = $this->makeRequest('POST', '/session', $data); $this->logger->info('Login result: ' . $res); $response = json_decode($res, true); if (!empty($response['error'])) { throw new AuthenticationException($response['error']); } $this->session_id = $response['id']; $_SESSION['pp_sessionid'] = $this->session_id; $this->logger->debug('Session ID: ' . $this->session_id); } public function getAlbums(int $count = 1000, int $offset = 0): array { $data = [ 'count' => $count, 'offset' => $offset, 'type' => 'album' ]; $res = $this->makeRequest('GET', '/albums', $data, 'text/plain'); $response = json_decode($res, true); if (!empty($response['error'])) { throw new NetworkException($response['error']); } $albums = array_map( function ($entry) { return new Album($entry); }, $response ); $this->logger->debug('Albums' . json_encode($albums)); return $albums; } public function getAlbumsByTokens(array $tokens, int $count = 1000, int $offset = 0): array { $this->logger->debug('getAlbumsByToken'); $albums = $this->getAlbums($count, 0); $visibleAlbums = []; foreach ($albums as $album) { $token = $this->getAlbumToken($album); $album->token = $token; if ($token != null && in_array($album->token, $tokens)) { $visibleAlbums[] = $album; } else { $this->logger->debug('Skipping album without access: ' . $album->title); } } $this->logger->debug('getAlbumsByToken' . json_encode($visibleAlbums)); return $visibleAlbums; } public function getAlbumToken($album): ?string { $uid = is_string($album) ? $album : $album->uid; $res = $this->makeRequest('GET', '/albums/' . $uid . '/links'); $response = json_decode($res, true)[0]; if (!empty($response['error'])) { throw new NetworkException($response['error']); } $this->logger->debug('Token response: ' . $res); if (!in_array('Token', $response)) { return null; } return $response['Token']; } public function uploadPhotos(?string $album = null) { $path = time(); $url = '/upload/'.$path; $import_url = '/import'.$url; foreach ($_FILES['files']['tmp_name'] as $key => $value) { $file_tmpname = $_FILES['files']['tmp_name'][$key]; $this->logger->info('Uploading ' . $file_tmpname . ' to ' . $url); $filename = basename($_FILES['files']['name'][$key]); $cFile = curl_file_create($file_tmpname, $_FILES['files']['type'][$key], $filename); $data = ['files' => $cFile]; $res = $this->makeRequest('POST', $url, $data, 'multipart/form-data'); $this->logger->info('Upload result: ' . $res); } $this->logger->info('Importing files'); $albums = empty($album) ? [] : [$album]; $import_data = ["move" => true, "albums" => $albums]; $res = $this->makeRequest('POST', $import_url, $import_data); $this->logger->info('Import result: ' . $res); } }