diff --git a/model/Item.inc.php b/model/Item.inc.php index 55bc3126..58159bbf 100644 --- a/model/Item.inc.php +++ b/model/Item.inc.php @@ -4146,6 +4146,9 @@ public function getUncachedResponseProps($requestParams, Zotero_Permissions $per // Link to publications download URL in My Publications if ($downloadDetails && $requestParams['publications']) { $downloadDetails['url'] = str_replace("/items/", "/publications/items/", $downloadDetails['url']); + if (!empty($downloadDetails['zip'])) { + $downloadDetails['zip']['url'] = str_replace("/items/", "/publications/items/", $downloadDetails['zip']['url']); + } } } } @@ -4196,7 +4199,7 @@ public function toResponseJSON(array $requestParams, Zotero_Permissions $permiss $requestParams['schemaVersion'] ?? null ); - $cacheVersion = 8; + $cacheVersion = 9; $cacheKey = "jsonEntry_" . $this->libraryID . "/" . $this->id . "_" . md5( $version @@ -4349,6 +4352,15 @@ public function toResponseJSON(array $requestParams, Zotero_Permissions $permiss if (isset($details['size'])) { $json['links']['enclosure']['length'] = $details['size']; } + if (!empty($details['zip'])) { + $json['links']['zip'] = [ + 'type' => 'application/zip', + 'href' => $details['zip']['url'], + 'title' => $details['zip']['filename'], + 'length' => (int) $details['zip']['size'], + 'md5' => $details['zip']['md5'], + ]; + } } // 'meta' diff --git a/model/Items.inc.php b/model/Items.inc.php index 3cfd4920..7da61d90 100644 --- a/model/Items.inc.php +++ b/model/Items.inc.php @@ -1181,6 +1181,14 @@ public static function convertItemToAtom(Zotero_Item $item, $queryParams, $permi if (isset($details['size'])) { $link['length'] = $details['size']; } + if (!empty($details['zip'])) { + $link = $xml->addChild('link'); + $link['rel'] = 'zip'; + $link['type'] = 'application/zip'; + $link['href'] = $details['zip']['url']; + $link['title'] = $details['zip']['filename']; + $link['length'] = $details['zip']['size']; + } } $xml->addChild('zapi:key', $item->key, Zotero_Atom::$nsZoteroAPI); diff --git a/model/Storage.inc.php b/model/Storage.inc.php index 3b56b554..df24e34f 100644 --- a/model/Storage.inc.php +++ b/model/Storage.inc.php @@ -46,8 +46,17 @@ public static function getDownloadDetails($item) { $url = Zotero_API::getItemURI($item) . "/file/view"; $info = self::getFileInfoByID($storageFileID); if ($info['zip']) { + // Top-level 'filename'/'size' are omitted because 'links.enclosure' points + // at /file/view (viewer output, dynamic length). + // Wrapper identifiers go under 'zip' for the sibling 'links.zip' link. return array( - 'url' => $url + 'url' => $url, + 'zip' => array( + 'url' => Zotero_API::getItemURI($item) . "/file", + 'filename' => $info['filename'], + 'size' => $info['size'], + 'md5' => $info['hash'] + ) ); } else { diff --git a/tests/remote-php/tests/API/3/FileTest.php b/tests/remote-php/tests/API/3/FileTest.php index 17a8f7f5..00002b22 100644 --- a/tests/remote-php/tests/API/3/FileTest.php +++ b/tests/remote-php/tests/API/3/FileTest.php @@ -1220,9 +1220,23 @@ public function testAddFileClientV5Zip() { $this->assert200($response); // S3 should return ZIP content type $this->assertEquals('application/zip', $response->getHeader("Content-Type")); + + // Verify links.zip on the item JSON view + $response = API::userGet(self::$config['userID'], "items/$key"); + $this->assert200($response); + $json = API::getJSONFromResponse($response); + $this->assertArrayHasKey('zip', $json['links']); + $this->assertEquals('application/zip', $json['links']['zip']['type']); + $this->assertEquals($zipFilename, $json['links']['zip']['title']); + $this->assertSame($zipSize, $json['links']['zip']['length']); + $this->assertEquals($zipHash, $json['links']['zip']['md5']); + $this->assertRegExp( + "%^https?://[^/]+/users/" . self::$config['userID'] . "/items/$key/file$%", + $json['links']['zip']['href'] + ); } - - + + public function test_should_reject_file_in_personal_library_if_it_would_put_user_over_quota() { API::userClear(self::$config['userID']); diff --git a/tests/remote/tests/3/file.test.js b/tests/remote/tests/3/file.test.js index 6fe3a4b9..e09ba16e 100644 --- a/tests/remote/tests/3/file.test.js +++ b/tests/remote/tests/3/file.test.js @@ -1074,6 +1074,20 @@ describe('File', function () { assert200(response); // S3 should return ZIP content type assert.equal(response.getHeader('Content-Type'), 'application/zip'); + + // Verify links.zip on the item JSON view + response = await API.userGet(config.get('userID'), `items/${key}`); + assert200(response); + json = API.getJSONFromResponse(response); + assert.property(json.links, 'zip'); + assert.equal(json.links.zip.type, 'application/zip'); + assert.equal(json.links.zip.title, zipFilename); + assert.strictEqual(json.links.zip.length, zipSize); + assert.equal(json.links.zip.md5, zipHash); + assert.match( + json.links.zip.href, + new RegExp(`^https?://[^/]+/users/${config.get('userID')}/items/${key}/file$`) + ); }); // PHP: test_should_reject_file_in_personal_library_if_it_would_put_user_over_quota