base_url = get_option('hermes_nextcloud_url', ''); $this->username = get_option('hermes_nextcloud_user', ''); $this->password = get_option('hermes_nextcloud_pass', ''); $this->webdav_url = rtrim($this->base_url, '/') . '/remote.php/dav/files/' . $this->username . '/'; } /** * List files in Nextcloud directory. */ public function list_files($path = '/') { if (!$this->is_configured()) return new \WP_Error('not_configured', 'Nextcloud not configured'); $url = $this->webdav_url . ltrim($path, '/'); $response = $this->propfind($url); if (is_wp_error($response)) return $response; $files = []; $dom = new \DOMDocument(); $dom->loadXML($response); foreach ($dom->getElementsByTagNameNS('DAV:', 'response') as $node) { $href = $node->getElementsByTagNameNS('DAV:', 'href')->item(0)->nodeValue ?? ''; if ($href === $this->webdav_url) continue; // skip root $files[] = [ 'path' => $href, 'name' => basename($href), 'size' => $node->getElementsByTagNameNS('DAV:', 'getcontentlength')->item(0)->nodeValue ?? 0, 'modified' => $node->getElementsByTagNameNS('DAV:', 'getlastmodified')->item(0)->nodeValue ?? '', 'type' => $node->getElementsByTagNameNS('DAV:', 'getcontenttype')->item(0)->nodeValue ?? 'directory', ]; } return $files; } /** * Upload a file to Nextcloud from WordPress Media Library. */ public function upload_from_media($attachment_id, $destination_path = '/') { if (!$this->is_configured()) return new \WP_Error('not_configured', 'Nextcloud not configured'); $file_path = get_attached_file($attachment_id); if (!$file_path || !file_exists($file_path)) { return new \WP_Error('file_not_found', 'Attachment file not found on disk'); } $filename = basename($file_path); $dest_url = $this->webdav_url . ltrim($destination_path, '/') . '/' . $filename; $response = wp_remote_request($dest_url, [ 'method' => 'PUT', 'headers' => [ 'Authorization' => 'Basic ' . base64_encode($this->username . ':' . $this->password), 'Content-Type' => get_post_mime_type($attachment_id) ?: 'application/octet-stream', ], 'body' => file_get_contents($file_path), 'timeout' => 60, ]); if (is_wp_error($response)) return $response; $status = wp_remote_retrieve_response_code($response); if ($status < 200 || $status >= 300) { return new \WP_Error('upload_failed', "WebDAV upload returned $status"); } RestController::log('info', 'File uploaded to Nextcloud', ['file' => $filename, 'dest' => $dest_url]); return ['success' => true, 'path' => $dest_url]; } /** * Download a file from Nextcloud into WordPress Media Library. */ public function import_to_media($nextcloud_path) { if (!$this->is_configured()) return new \WP_Error('not_configured', 'Nextcloud not configured'); $url = $this->webdav_url . ltrim($nextcloud_path, '/'); $response = wp_remote_get($url, [ 'headers' => [ 'Authorization' => 'Basic ' . base64_encode($this->username . ':' . $this->password), ], 'timeout' => 60, ]); if (is_wp_error($response)) return $response; $body = wp_remote_retrieve_body($response); $filename = basename($nextcloud_path); $upload = wp_upload_bits($filename, null, $body); if ($upload['error']) { return new \WP_Error('upload_failed', $upload['error']); } $attachment = [ 'post_mime_type' => wp_check_filetype($filename)['type'] ?: 'application/octet-stream', 'post_title' => sanitize_file_name($filename), 'post_content' => '', 'post_status' => 'inherit', 'guid' => $upload['url'], ]; $attach_id = wp_insert_attachment($attachment, $upload['file']); require_once ABSPATH . 'wp-admin/includes/image.php'; wp_generate_attachment_metadata($attach_id, $upload['file']); RestController::log('info', 'File imported from Nextcloud', ['file' => $filename, 'attachment_id' => $attach_id]); return ['success' => true, 'attachment_id' => $attach_id, 'url' => $upload['url']]; } protected function propfind($url) { $response = wp_remote_request($url, [ 'method' => 'PROPFIND', 'headers' => [ 'Authorization' => 'Basic ' . base64_encode($this->username . ':' . $this->password), 'Depth' => '1', ], 'timeout' => 30, ]); if (is_wp_error($response)) return $response; return wp_remote_retrieve_body($response); } protected function is_configured() { return !empty($this->base_url) && !empty($this->username) && !empty($this->password); } }