Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
MessageChangeStorage
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 8
272
0.00% covered (danger)
0.00%
0 / 1
 writeChanges
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 isValidCdbName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCdbPath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupChanges
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 writeGroupChanges
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getCdbReader
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getLastModifiedTime
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isModifiedSince
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Synchronization;
5
6use Cdb\Reader;
7use Cdb\Writer;
8use InvalidArgumentException;
9use MediaWiki\Extension\Translate\MessageSync\MessageSourceChange;
10use MediaWiki\Extension\Translate\Utilities\Utilities;
11
12/**
13 * Handles storage / retrieval of data from message change files.
14 * @author Niklas Laxström
15 * @license GPL-2.0-or-later
16 */
17class MessageChangeStorage {
18    public const DEFAULT_NAME = 'default';
19
20    /**
21     * Writes change array as a serialized file.
22     * @param MessageSourceChange[] $changes Array of changes as returned by processGroup
23     * indexed by message group id.
24     * @param string $file Which file to use.
25     */
26    public static function writeChanges( array $changes, string $file ): void {
27        $cache = Writer::open( $file );
28        $keys = array_keys( $changes );
29        $cache->set( '#keys', Utilities::serialize( $keys ) );
30
31        foreach ( $changes as $key => $change ) {
32            $value = Utilities::serialize( $change->getAllModifications() );
33            $cache->set( $key, $value );
34        }
35        $cache->close();
36    }
37
38    /** Validate a file name. */
39    public static function isValidCdbName( string $fileName ): bool {
40        return (bool)preg_match( '/^[a-z_-]{1,100}$/i', $fileName );
41    }
42
43    /** Get a full path to file in a known location. */
44    public static function getCdbPath( string $fileName ): string {
45        return Utilities::cacheFile( "messagechanges.$fileName.cdb" );
46    }
47
48    public static function getGroupChanges( string $cdbPath, string $groupId ): MessageSourceChange {
49        $reader = self::getCdbReader( $cdbPath );
50        if ( $reader === null ) {
51            return MessageSourceChange::loadModifications( [] );
52        }
53
54        $groups = Utilities::deserialize( $reader->get( '#keys' ) );
55
56        if ( !in_array( $groupId, $groups, true ) ) {
57            throw new InvalidArgumentException( "Group Id - '$groupId' not found in cdb file " .
58                "(path: $cdbPath)." );
59        }
60
61        return MessageSourceChange::loadModifications(
62            Utilities::deserialize( $reader->get( $groupId ) )
63        );
64    }
65
66    /**
67     * Writes changes for a group. Has to read the changes first from the file
68     * and then re-write them to the file.
69     */
70    public static function writeGroupChanges(
71        MessageSourceChange $changes,
72        string $groupId,
73        string $cdbPath
74    ): void {
75        $reader = self::getCdbReader( $cdbPath );
76        if ( $reader === null ) {
77            return;
78        }
79
80        $groups = Utilities::deserialize( $reader->get( '#keys' ) );
81
82        $allChanges = [];
83        foreach ( $groups as $id ) {
84            $allChanges[$id] = MessageSourceChange::loadModifications(
85                Utilities::deserialize( $reader->get( $id ) )
86            );
87        }
88        $allChanges[$groupId] = $changes;
89
90        self::writeChanges( $allChanges, $cdbPath );
91    }
92
93    /** Validate and return a reader reference to the CDB file */
94    private static function getCdbReader( string $cdbPath ): ?Reader {
95        // File not found, probably no changes.
96        if ( !file_exists( $cdbPath ) ) {
97            return null;
98        }
99
100        return Reader::open( $cdbPath );
101    }
102
103    /**
104     * Gets the last modified time for the CDB file.
105     * @return int|null time of last modification (Unix timestamp)
106     */
107    public static function getLastModifiedTime( string $cdbPath ): ?int {
108        // File not found
109        if ( !file_exists( $cdbPath ) ) {
110            return null;
111        }
112
113        $stat = stat( $cdbPath );
114
115        return $stat['mtime'];
116    }
117
118    /** Checks if the CDB file has been modified since the time given. */
119    public static function isModifiedSince( string $cdbPath, int $unixTimestamp ): bool {
120        $lastModifiedTime = self::getLastModifiedTime( $cdbPath );
121
122        if ( $lastModifiedTime === null ) {
123            throw new InvalidArgumentException( "CDB file not found - $cdbPath" );
124        }
125
126        return $lastModifiedTime <= $unixTimestamp;
127    }
128}