Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 52 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
MetadataStorageHelper | |
0.00% |
0 / 51 |
|
0.00% |
0 / 5 |
380 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getJsonMetadata | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
90 | |||
jsonEncode | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getMetadataFromBlobStore | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
42 | |||
jsonDecode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\FileRepo\File; |
22 | |
23 | use InvalidArgumentException; |
24 | use MediaWiki\FileRepo\LocalRepo; |
25 | use MediaWiki\Logger\LoggerFactory; |
26 | use MediaWiki\Status\Status; |
27 | use MediaWiki\Storage\BlobStore; |
28 | |
29 | /** |
30 | * Helper for storage of metadata. Sharing the code between LocalFile and ArchivedFile |
31 | * |
32 | * @internal |
33 | * @ingroup FileAbstraction |
34 | */ |
35 | class MetadataStorageHelper { |
36 | /** @var LocalRepo */ |
37 | private $repo; |
38 | |
39 | public function __construct( LocalRepo $repo ) { |
40 | $this->repo = $repo; |
41 | } |
42 | |
43 | /** |
44 | * Get metadata in JSON format ready for DB insertion, optionally splitting |
45 | * items out to BlobStore. |
46 | * |
47 | * @param LocalFile|ArchivedFile $file |
48 | * @param array $envelope |
49 | * @return array |
50 | */ |
51 | public function getJsonMetadata( $file, $envelope ) { |
52 | // Try encoding |
53 | $s = $this->jsonEncode( $envelope ); |
54 | |
55 | // Decide whether to try splitting the metadata. |
56 | // Return early if it's not going to happen. |
57 | if ( !$this->repo->isSplitMetadataEnabled() |
58 | || !$file->getHandler() |
59 | || !$file->getHandler()->useSplitMetadata() |
60 | ) { |
61 | return [ $s, [] ]; |
62 | } |
63 | $threshold = $this->repo->getSplitMetadataThreshold(); |
64 | if ( !$threshold || strlen( $s ) <= $threshold ) { |
65 | return [ $s, [] ]; |
66 | } |
67 | $blobStore = $this->repo->getBlobStore(); |
68 | if ( !$blobStore ) { |
69 | return [ $s, [] ]; |
70 | } |
71 | |
72 | // The data as a whole is above the item threshold. Look for |
73 | // large items that can be split out. |
74 | $blobAddresses = []; |
75 | foreach ( $envelope['data'] as $name => $value ) { |
76 | $encoded = $this->jsonEncode( $value ); |
77 | if ( strlen( $encoded ) > $threshold ) { |
78 | $blobAddresses[$name] = $blobStore->storeBlob( |
79 | $encoded, |
80 | [ BlobStore::IMAGE_HINT => $file->getName() ] |
81 | ); |
82 | } |
83 | } |
84 | // Remove any items that were split out |
85 | $envelope['data'] = array_diff_key( $envelope['data'], $blobAddresses ); |
86 | $envelope['blobs'] = $blobAddresses; |
87 | $s = $this->jsonEncode( $envelope ); |
88 | |
89 | return [ $s, $blobAddresses ]; |
90 | } |
91 | |
92 | /** |
93 | * Do JSON encoding with local flags. Callers must make sure that the data can be serialized. |
94 | * |
95 | * @param mixed $data |
96 | * @return string |
97 | */ |
98 | public function jsonEncode( $data ): string { |
99 | $s = json_encode( $data, |
100 | JSON_INVALID_UTF8_IGNORE | |
101 | JSON_UNESCAPED_SLASHES | |
102 | JSON_UNESCAPED_UNICODE ); |
103 | if ( $s === false ) { |
104 | throw new InvalidArgumentException( __METHOD__ . ': metadata is not JSON-serializable ' ); |
105 | } |
106 | return $s; |
107 | } |
108 | |
109 | public function getMetadataFromBlobStore( array $addresses ): array { |
110 | $result = []; |
111 | if ( $addresses ) { |
112 | $blobStore = $this->repo->getBlobStore(); |
113 | if ( !$blobStore ) { |
114 | LoggerFactory::getInstance( 'LocalFile' )->warning( |
115 | "Unable to load metadata: repo has no blob store" ); |
116 | return $result; |
117 | } |
118 | $status = $blobStore->getBlobBatch( $addresses ); |
119 | if ( !$status->isGood() ) { |
120 | $msg = Status::wrap( $status )->getWikiText( |
121 | false, false, 'en' ); |
122 | LoggerFactory::getInstance( 'LocalFile' )->warning( |
123 | "Error loading metadata from BlobStore: $msg" ); |
124 | } |
125 | foreach ( $addresses as $itemName => $address ) { |
126 | $json = $status->getValue()[$address] ?? null; |
127 | if ( $json !== null ) { |
128 | $value = $this->jsonDecode( $json ); |
129 | $result[$itemName] = $value; |
130 | } |
131 | } |
132 | } |
133 | return $result; |
134 | } |
135 | |
136 | /** |
137 | * Do JSON decoding with local flags. |
138 | * |
139 | * This doesn't use JsonCodec because JsonCodec can construct objects, |
140 | * which we don't want. |
141 | * |
142 | * Does not throw. Returns false on failure. |
143 | * |
144 | * @param string $s |
145 | * @return mixed The decoded value, or false on failure |
146 | */ |
147 | public function jsonDecode( string $s ) { |
148 | // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged |
149 | return @json_decode( $s, true, 512, JSON_INVALID_UTF8_IGNORE ); |
150 | } |
151 | |
152 | } |
153 | |
154 | /** @deprecated class alias since 1.44 */ |
155 | class_alias( MetadataStorageHelper::class, 'MetadataStorageHelper' ); |