Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F5957769
MessageSourceChange.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
MessageSourceChange.php
View Options
<?php
/**
* Contains a class to track changes to the messages when importing messages from remote source.
* @author Abijeet Patro
* @license GPL-2.0-or-later
* @file
*/
namespace
MediaWiki\Extension\Translate\MessageSync
;
use
InvalidArgumentException
;
/**
* Class is used to track the changes made when importing messages from the remote sources
* using importExternalTranslations.php. Also provides an interface to query these changes, and
* update them.
* @since 2019.10
*/
class
MessageSourceChange
{
/**
* @var array[][][]
* @phpcs:ignore Generic.Files.LineLength
* @phan-var array<string,array<string,array<string|int,array{key:string,content:string,similarity?:float,matched_to?:string,previous_state?:string}>>>
*/
protected
$changes
=
[];
public
const
ADDITION
=
'addition'
;
public
const
CHANGE
=
'change'
;
public
const
DELETION
=
'deletion'
;
public
const
RENAME
=
'rename'
;
public
const
NONE
=
'none'
;
private
const
SIMILARITY_THRESHOLD
=
0.9
;
/**
* Contains a mapping of message type, and the corresponding addition function
* @var callable[]
*/
protected
$addFunctionMap
;
/**
* Contains a mapping of message type, and the corresponding removal function
* @var callable[]
*/
protected
$removeFunctionMap
;
/** @param array[][][] $changes */
public
function
__construct
(
$changes
=
[]
)
{
$this
->
changes
=
$changes
;
$this
->
addFunctionMap
=
[
self
::
ADDITION
=>
[
$this
,
'addAddition'
],
self
::
DELETION
=>
[
$this
,
'addDeletion'
],
self
::
CHANGE
=>
[
$this
,
'addChange'
]
];
$this
->
removeFunctionMap
=
[
self
::
ADDITION
=>
[
$this
,
'removeAdditions'
],
self
::
DELETION
=>
[
$this
,
'removeDeletions'
],
self
::
CHANGE
=>
[
$this
,
'removeChanges'
]
];
}
/**
* Add a change under a message group for a specific language
* @param string $language
* @param string $key
* @param string $content
*/
public
function
addChange
(
$language
,
$key
,
$content
)
{
$this
->
addModification
(
$language
,
self
::
CHANGE
,
$key
,
$content
);
}
/**
* Add an addition under a message group for a specific language
* @param string $language
* @param string $key
* @param string $content
*/
public
function
addAddition
(
$language
,
$key
,
$content
)
{
$this
->
addModification
(
$language
,
self
::
ADDITION
,
$key
,
$content
);
}
/**
* Adds a deletion under a message group for a specific language
* @param string $language
* @param string $key
* @param string $content
*/
public
function
addDeletion
(
$language
,
$key
,
$content
)
{
$this
->
addModification
(
$language
,
self
::
DELETION
,
$key
,
$content
);
}
/**
* Adds a rename under a message group for a specific language
* @param string $language
* @param string[] $addedMessage
* @param string[] $deletedMessage
* @param float $similarity
*/
public
function
addRename
(
$language
,
$addedMessage
,
$deletedMessage
,
$similarity
=
0
)
{
$this
->
changes
[
$language
][
self
::
RENAME
][
$addedMessage
[
'key'
]]
=
[
'content'
=>
$addedMessage
[
'content'
],
'similarity'
=>
$similarity
,
'matched_to'
=>
$deletedMessage
[
'key'
],
'previous_state'
=>
self
::
ADDITION
,
'key'
=>
$addedMessage
[
'key'
]
];
$this
->
changes
[
$language
][
self
::
RENAME
][
$deletedMessage
[
'key'
]]
=
[
'content'
=>
$deletedMessage
[
'content'
],
'similarity'
=>
$similarity
,
'matched_to'
=>
$addedMessage
[
'key'
],
'previous_state'
=>
self
::
DELETION
,
'key'
=>
$deletedMessage
[
'key'
]
];
}
public
function
setRenameState
(
$language
,
$msgKey
,
$state
)
{
$possibleStates
=
[
self
::
ADDITION
,
self
::
CHANGE
,
self
::
DELETION
,
self
::
NONE
,
self
::
RENAME
];
if
(
!
in_array
(
$state
,
$possibleStates
)
)
{
throw
new
InvalidArgumentException
(
"Invalid state passed - '$state'. Possible states - "
.
implode
(
', '
,
$possibleStates
)
);
}
$languageChanges
=
null
;
if
(
isset
(
$this
->
changes
[
$language
]
)
)
{
$languageChanges
=
&
$this
->
changes
[
$language
];
}
if
(
$languageChanges
!==
null
&&
isset
(
$languageChanges
[
'rename'
][
$msgKey
]
)
)
{
$languageChanges
[
'rename'
][
$msgKey
][
'previous_state'
]
=
$state
;
}
}
/**
* @param string $language
* @param string $type
* @param string $key
* @param string $content
*/
protected
function
addModification
(
$language
,
$type
,
$key
,
$content
)
{
$this
->
changes
[
$language
][
$type
][]
=
[
'key'
=>
$key
,
'content'
=>
$content
,
];
}
/**
* Fetch changes for a message group under a language
* @param string $language
* @return array[]
*/
public
function
getChanges
(
$language
)
{
return
$this
->
getModification
(
$language
,
self
::
CHANGE
);
}
/**
* Fetch deletions for a message group under a language
* @param string $language
* @return array[]
*/
public
function
getDeletions
(
$language
)
{
return
$this
->
getModification
(
$language
,
self
::
DELETION
);
}
/**
* Fetch additions for a message group under a language
* @param string $language
* @return array[]
*/
public
function
getAdditions
(
$language
)
{
return
$this
->
getModification
(
$language
,
self
::
ADDITION
);
}
/**
* Finds a message with the given key across different types of modifications.
* @param string $language
* @param string $key
* @param string[] $possibleStates
* @param string|null &$modificationType
* @return array|null
*/
public
function
findMessage
(
$language
,
$key
,
$possibleStates
=
[],
&
$modificationType
=
null
)
{
$allChanges
=
[];
$allChanges
[
self
::
ADDITION
]
=
$this
->
getAdditions
(
$language
);
$allChanges
[
self
::
DELETION
]
=
$this
->
getDeletions
(
$language
);
$allChanges
[
self
::
CHANGE
]
=
$this
->
getChanges
(
$language
);
$allChanges
[
self
::
RENAME
]
=
$this
->
getRenames
(
$language
);
if
(
$possibleStates
===
[]
)
{
$possibleStates
=
[
self
::
ADDITION
,
self
::
CHANGE
,
self
::
DELETION
,
self
::
RENAME
];
}
foreach
(
$allChanges
as
$type
=>
$modifications
)
{
if
(
!
in_array
(
$type
,
$possibleStates
)
)
{
continue
;
}
if
(
$type
===
self
::
RENAME
)
{
if
(
isset
(
$modifications
[
$key
]
)
)
{
$modificationType
=
$type
;
return
$modifications
[
$key
];
}
continue
;
}
foreach
(
$modifications
as
$modification
)
{
$currentKey
=
$modification
[
'key'
];
if
(
$currentKey
===
$key
)
{
$modificationType
=
$type
;
return
$modification
;
}
}
}
$modificationType
=
null
;
return
null
;
}
/**
* Break renames, and put messages back into their previous state.
* @param string $languageCode
* @param string $msgKey
* @return string|null previous state of the message
*/
public
function
breakRename
(
$languageCode
,
$msgKey
)
{
$msg
=
$this
->
findMessage
(
$languageCode
,
$msgKey
,
[
self
::
RENAME
]
);
if
(
$msg
===
null
)
{
return
null
;
}
$matchedMsg
=
$this
->
getMatchedMessage
(
$languageCode
,
$msg
[
'key'
]
);
if
(
$matchedMsg
===
null
)
{
return
null
;
}
// Remove them from the renames array
$this
->
removeRenames
(
$languageCode
,
[
$matchedMsg
[
'key'
],
$msg
[
'key'
]
]
);
$matchedMsgState
=
$matchedMsg
[
'previous_state'
];
$msgState
=
$msg
[
'previous_state'
];
// Add them to the changes under the appropriate state
if
(
$matchedMsgState
!==
self
::
NONE
)
{
if
(
$matchedMsgState
===
self
::
CHANGE
)
{
$matchedMsg
[
'key'
]
=
$msg
[
'key'
];
}
call_user_func
(
$this
->
addFunctionMap
[
$matchedMsgState
],
$languageCode
,
$matchedMsg
[
'key'
],
$matchedMsg
[
'content'
]
);
}
if
(
$msgState
!==
self
::
NONE
)
{
if
(
$msgState
===
self
::
CHANGE
)
{
$msg
[
'key'
]
=
$matchedMsg
[
'key'
];
}
call_user_func
(
$this
->
addFunctionMap
[
$msgState
],
$languageCode
,
$msg
[
'key'
],
$msg
[
'content'
]
);
}
return
$msgState
;
}
/**
* Fetch renames for a message group under a language
* @param string $language
* @return array[]
*/
public
function
getRenames
(
$language
)
{
$renames
=
$this
->
getModification
(
$language
,
self
::
RENAME
);
foreach
(
$renames
as
$key
=>
&
$rename
)
{
$rename
[
'key'
]
=
$key
;
}
return
$renames
;
}
/**
* @param string $language
* @param string $type
* @return array[]
*/
protected
function
getModification
(
$language
,
$type
)
{
return
$this
->
changes
[
$language
][
$type
]
??
[];
}
/**
* Remove additions for a language under the group.
* @param string $language
* @param array|null $keysToRemove
*/
public
function
removeAdditions
(
$language
,
$keysToRemove
)
{
$this
->
removeModification
(
$language
,
self
::
ADDITION
,
$keysToRemove
);
}
/**
* Remove deletions for a language under the group.
* @param string $language
* @param array|null $keysToRemove
*/
public
function
removeDeletions
(
$language
,
$keysToRemove
)
{
$this
->
removeModification
(
$language
,
self
::
DELETION
,
$keysToRemove
);
}
/**
* Remove changes for a language under the group.
* @param string $language
* @param array|null $keysToRemove
*/
public
function
removeChanges
(
$language
,
$keysToRemove
)
{
$this
->
removeModification
(
$language
,
self
::
CHANGE
,
$keysToRemove
);
}
/**
* Remove renames for a language under the group.
* @param string $language
* @param array|null $keysToRemove
*/
public
function
removeRenames
(
$language
,
$keysToRemove
)
{
$this
->
removeModification
(
$language
,
self
::
RENAME
,
$keysToRemove
);
}
/**
* Remove modifications based on the type. Avoids usage of ugly if / switch
* statement.
* @param string $language
* @param array $keysToRemove
* @param string $type One of ADDITION, CHANGE, DELETION
*/
public
function
removeBasedOnType
(
$language
,
$keysToRemove
,
$type
)
{
$callable
=
$this
->
removeFunctionMap
[
$type
]
??
null
;
if
(
$callable
===
null
)
{
throw
new
InvalidArgumentException
(
'Type should be one of '
.
implode
(
', '
,
[
self
::
ADDITION
,
self
::
CHANGE
,
self
::
DELETION
]
)
.
". Invalid type $type passed."
);
}
call_user_func
(
$callable
,
$language
,
$keysToRemove
);
}
/**
* Remove all language related changes for a group.
* @param string $language
*/
public
function
removeChangesForLanguage
(
$language
)
{
unset
(
$this
->
changes
[
$language
]
);
}
protected
function
removeModification
(
$language
,
$type
,
$keysToRemove
=
null
)
{
if
(
!
isset
(
$this
->
changes
[
$language
][
$type
]
)
||
$keysToRemove
===
[]
)
{
return
;
}
if
(
$keysToRemove
===
null
)
{
unset
(
$this
->
changes
[
$language
][
$type
]
);
return
;
}
if
(
$type
===
self
::
RENAME
)
{
$this
->
changes
[
$language
][
$type
]
=
array_diff_key
(
$this
->
changes
[
$language
][
$type
],
array_flip
(
$keysToRemove
)
);
}
else
{
$this
->
changes
[
$language
][
$type
]
=
array_filter
(
$this
->
changes
[
$language
][
$type
],
static
function
(
$change
)
use
(
$keysToRemove
)
{
return
!
in_array
(
$change
[
'key'
],
$keysToRemove
,
true
);
}
);
}
}
/**
* Return all modifications for the group.
* @return array[][][]
*/
public
function
getAllModifications
()
{
return
$this
->
changes
;
}
/**
* Get all for a language under the group.
* @param string $language
* @return array[][]
*/
public
function
getModificationsForLanguage
(
$language
)
{
return
$this
->
changes
[
$language
]
??
[];
}
/**
* Loads the changes, and returns an instance of the class.
* @param array $changesData
* @return self
*/
public
static
function
loadModifications
(
$changesData
)
{
return
new
self
(
$changesData
);
}
/**
* Get all language keys with modifications under the group
* @return string[]
*/
public
function
getLanguages
()
{
return
array_keys
(
$this
->
changes
);
}
/**
* Determines if the group has only a certain type of change under a language.
*
* @param string $language
* @param string $type
* @return bool
*/
public
function
hasOnly
(
$language
,
$type
)
{
$deletions
=
$this
->
getDeletions
(
$language
);
$additions
=
$this
->
getAdditions
(
$language
);
$renames
=
$this
->
getRenames
(
$language
);
$changes
=
$this
->
getChanges
(
$language
);
$hasOnlyAdditions
=
$hasOnlyRenames
=
$hasOnlyChanges
=
$hasOnlyDeletions
=
true
;
if
(
$deletions
)
{
$hasOnlyAdditions
=
$hasOnlyRenames
=
$hasOnlyChanges
=
false
;
}
if
(
$renames
)
{
$hasOnlyDeletions
=
$hasOnlyAdditions
=
$hasOnlyChanges
=
false
;
}
if
(
$changes
)
{
$hasOnlyAdditions
=
$hasOnlyRenames
=
$hasOnlyDeletions
=
false
;
}
if
(
$additions
)
{
$hasOnlyDeletions
=
$hasOnlyRenames
=
$hasOnlyChanges
=
false
;
}
if
(
$type
===
self
::
DELETION
)
{
$response
=
$hasOnlyDeletions
;
}
elseif
(
$type
===
self
::
RENAME
)
{
$response
=
$hasOnlyRenames
;
}
elseif
(
$type
===
self
::
CHANGE
)
{
$response
=
$hasOnlyChanges
;
}
elseif
(
$type
===
self
::
ADDITION
)
{
$response
=
$hasOnlyAdditions
;
}
else
{
throw
new
InvalidArgumentException
(
"Unknown $type passed."
);
}
return
$response
;
}
/**
* Checks if the previous state of a renamed message matches a given value
* @param string $languageCode
* @param string $key
* @param string[] $types
* @return bool
*/
public
function
isPreviousState
(
$languageCode
,
$key
,
array
$types
)
{
$msg
=
$this
->
findMessage
(
$languageCode
,
$key
,
[
self
::
RENAME
]
);
return
isset
(
$msg
[
'previous_state'
]
)
&&
in_array
(
$msg
[
'previous_state'
],
$types
);
}
/**
* Get matched rename message for a given key
* @param string $languageCode
* @param string $key
* @return array|null Matched message if found, else null
*/
public
function
getMatchedMessage
(
$languageCode
,
$key
)
{
$matchedKey
=
$this
->
getMatchedKey
(
$languageCode
,
$key
);
if
(
$matchedKey
)
{
return
$this
->
changes
[
$languageCode
][
self
::
RENAME
][
$matchedKey
]
??
null
;
}
return
null
;
}
/**
* Get matched rename key for a given key
* @param string $languageCode
* @param string $key
* @return string|null Matched key if found, else null
*/
public
function
getMatchedKey
(
$languageCode
,
$key
)
{
return
$this
->
changes
[
$languageCode
][
self
::
RENAME
][
$key
][
'matched_to'
]
??
null
;
}
/**
* Returns the calculated similarity for a rename
* @param string $languageCode
* @param string $key
* @return float|null
*/
public
function
getSimilarity
(
$languageCode
,
$key
)
{
$msg
=
$this
->
findMessage
(
$languageCode
,
$key
,
[
self
::
RENAME
]
);
return
$msg
[
'similarity'
]
??
null
;
}
/**
* Checks if a given key is equal to matched rename message
* @param string $languageCode
* @param string $key
* @return bool
*/
public
function
isEqual
(
$languageCode
,
$key
)
{
$msg
=
$this
->
findMessage
(
$languageCode
,
$key
,
[
self
::
RENAME
]
);
return
$msg
&&
$this
->
areStringsEqual
(
$msg
[
'similarity'
]
);
}
/**
* Checks if a given key is similar to matched rename message
*
* @param string $languageCode
* @param string $key
* @return bool
*/
public
function
isSimilar
(
$languageCode
,
$key
)
{
$msg
=
$this
->
findMessage
(
$languageCode
,
$key
,
[
self
::
RENAME
]
);
return
$msg
&&
$this
->
areStringsSimilar
(
$msg
[
'similarity'
]
);
}
/**
* Checks if the similarity percent passed passes the min threshold
* @param float $similarity
* @return bool
*/
public
function
areStringsSimilar
(
$similarity
)
{
return
$similarity
>=
self
::
SIMILARITY_THRESHOLD
;
}
/**
* Checks if the similarity percent passed
* @param float $similarity
* @return bool
*/
public
function
areStringsEqual
(
$similarity
)
{
return
$similarity
===
1
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Oct 3, 05:20 (2 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0a/c6/b1bf9a4888f7fb7ae6348c6f1931
Default Alt Text
MessageSourceChange.php (14 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment