Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 51 |
|
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 | use MediaWiki\Logger\LoggerFactory; |
22 | use MediaWiki\Status\Status; |
23 | use MediaWiki\Storage\BlobStore; |
24 | |
25 | /** |
26 | * Helper for storage of metadata. Sharing the code between LocalFile and ArchivedFile |
27 | * |
28 | * @internal |
29 | * @ingroup FileAbstraction |
30 | */ |
31 | class MetadataStorageHelper { |
32 | /** @var LocalRepo */ |
33 | private $repo; |
34 | |
35 | public function __construct( LocalRepo $repo ) { |
36 | $this->repo = $repo; |
37 | } |
38 | |
39 | /** |
40 | * Get metadata in JSON format ready for DB insertion, optionally splitting |
41 | * items out to BlobStore. |
42 | * |
43 | * @param LocalFile|ArchivedFile $file |
44 | * @param array $envelope |
45 | * @return array |
46 | */ |
47 | public function getJsonMetadata( $file, $envelope ) { |
48 | // Try encoding |
49 | $s = $this->jsonEncode( $envelope ); |
50 | |
51 | // Decide whether to try splitting the metadata. |
52 | // Return early if it's not going to happen. |
53 | if ( !$this->repo->isSplitMetadataEnabled() |
54 | || !$file->getHandler() |
55 | || !$file->getHandler()->useSplitMetadata() |
56 | ) { |
57 | return [ $s, [] ]; |
58 | } |
59 | $threshold = $this->repo->getSplitMetadataThreshold(); |
60 | if ( !$threshold || strlen( $s ) <= $threshold ) { |
61 | return [ $s, [] ]; |
62 | } |
63 | $blobStore = $this->repo->getBlobStore(); |
64 | if ( !$blobStore ) { |
65 | return [ $s, [] ]; |
66 | } |
67 | |
68 | // The data as a whole is above the item threshold. Look for |
69 | // large items that can be split out. |
70 | $blobAddresses = []; |
71 | foreach ( $envelope['data'] as $name => $value ) { |
72 | $encoded = $this->jsonEncode( $value ); |
73 | if ( strlen( $encoded ) > $threshold ) { |
74 | $blobAddresses[$name] = $blobStore->storeBlob( |
75 | $encoded, |
76 | [ BlobStore::IMAGE_HINT => $file->getName() ] |
77 | ); |
78 | } |
79 | } |
80 | // Remove any items that were split out |
81 | $envelope['data'] = array_diff_key( $envelope['data'], $blobAddresses ); |
82 | $envelope['blobs'] = $blobAddresses; |
83 | $s = $this->jsonEncode( $envelope ); |
84 | |
85 | return [ $s, $blobAddresses ]; |
86 | } |
87 | |
88 | /** |
89 | * Do JSON encoding with local flags. Callers must make sure that the data can be serialized. |
90 | * |
91 | * @param mixed $data |
92 | * @return string |
93 | */ |
94 | public function jsonEncode( $data ): string { |
95 | $s = json_encode( $data, |
96 | JSON_INVALID_UTF8_IGNORE | |
97 | JSON_UNESCAPED_SLASHES | |
98 | JSON_UNESCAPED_UNICODE ); |
99 | if ( $s === false ) { |
100 | throw new InvalidArgumentException( __METHOD__ . ': metadata is not JSON-serializable ' ); |
101 | } |
102 | return $s; |
103 | } |
104 | |
105 | /** |
106 | * @param array $addresses |
107 | * @return array |
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 | } |