Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
MetadataStorageHelper
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 5
380
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getJsonMetadata
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
90
 jsonEncode
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getMetadataFromBlobStore
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 jsonDecode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
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
21namespace MediaWiki\FileRepo\File;
22
23use InvalidArgumentException;
24use MediaWiki\FileRepo\LocalRepo;
25use MediaWiki\Logger\LoggerFactory;
26use MediaWiki\Status\Status;
27use 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 */
35class 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 */
155class_alias( MetadataStorageHelper::class, 'MetadataStorageHelper' );