'GenderCache' => 'includes/cache/GenderCache.php',
'GlobalDependency' => 'includes/cache/CacheDependency.php',
'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
- 'HTMLCacheUpdateJob' => 'includes/cache/HTMLCacheUpdate.php',
'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
'LinkBatch' => 'includes/cache/LinkBatch.php',
'LinkCache' => 'includes/cache/LinkCache.php',
'TitleDependency' => 'includes/cache/CacheDependency.php',
'TitleListDependency' => 'includes/cache/CacheDependency.php',
- # includes/conf
- 'Conf' => 'includes/conf/Conf.php',
- 'DatabaseConf' => 'includes/conf/DatabaseConf.php',
- 'DefaultSettings' => 'includes/conf/DefaultSettings.php',
-
# includes/context
'ContextSource' => 'includes/context/ContextSource.php',
'DerivativeContext' => 'includes/context/DerivativeContext.php',
'WebInstallerPage' => 'includes/installer/WebInstallerPage.php',
# includes/job
- 'DoubleRedirectJob' => 'includes/job/DoubleRedirectJob.php',
- 'EmaillingJob' => 'includes/job/EmaillingJob.php',
- 'EnotifNotifyJob' => 'includes/job/EnotifNotifyJob.php',
'Job' => 'includes/job/Job.php',
'JobQueue' => 'includes/job/JobQueue.php',
'JobQueueDB' => 'includes/job/JobQueueDB.php',
'JobQueueGroup' => 'includes/job/JobQueueGroup.php',
- 'RefreshLinksJob' => 'includes/job/RefreshLinksJob.php',
- 'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php',
- 'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php',
+
+ # includes/job/jobs
+ 'DoubleRedirectJob' => 'includes/job/jobs/DoubleRedirectJob.php',
+ 'EmaillingJob' => 'includes/job/jobs/EmaillingJob.php',
+ 'EnotifNotifyJob' => 'includes/job/jobs/EnotifNotifyJob.php',
+ 'HTMLCacheUpdateJob' => 'includes/job/jobs/HTMLCacheUpdateJob.php',
+ 'NullJob' => 'includes/job/jobs/NullJob.php',
+ 'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php',
+ 'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php',
+ 'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php',
# includes/json
'FormatJson' => 'includes/json/FormatJson.php',
'PatrolLog' => 'includes/logging/PatrolLog.php',
'PatrolLogFormatter' => 'includes/logging/LogFormatter.php',
'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php',
+ 'RightsLogFormatter' => 'includes/logging/LogFormatter.php',
# includes/media
'BitmapHandler' => 'includes/media/Bitmap.php',
'TestRecorder' => 'tests/testHelpers.inc',
# tests/phpunit
- 'DummyContentHandlerForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
- 'DummyContentForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
- 'JavascriptContentTest' => 'tests/phpunit/includes/JavascriptContentTest.php',
'RevisionStorageTest' => 'tests/phpunit/includes/RevisionStorageTest.php',
- 'TextContentTest' => 'tests/phpunit/includes/TextContentTest.php',
'WikiPageTest' => 'tests/phpunit/includes/WikiPageTest.php',
+ # tests/phpunit/content
+ 'DummyContentHandlerForTesting' => 'tests/phpunit/includes/content/ContentHandlerTest.php',
+ 'DummyContentForTesting' => 'tests/phpunit/includes/content/ContentHandlerTest.php',
+ 'JavascriptContentTest' => 'tests/phpunit/includes/content/JavascriptContentTest.php',
+ 'TextContentTest' => 'tests/phpunit/includes/content/TextContentTest.php',
+
# tests/phpunit/includes
'GenericArrayObjectTest' => 'tests/phpunit/includes/libs/GenericArrayObjectTest.php',
* @since 1.21
*/
$wgContentHandlers = array(
- CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case
- CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting
- CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting
- CONTENT_MODEL_TEXT => 'TextContentHandler', // plain text, for use by extensions etc
+ // the usual case
+ CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler',
+ // dumb version, no syntax highlighting
+ CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler',
+ // dumb version, no syntax highlighting
+ CONTENT_MODEL_CSS => 'CssContentHandler',
+ // plain text, for use by extensions etc
+ CONTENT_MODEL_TEXT => 'TextContentHandler',
);
/**
$wgLocalMessageCacheSerialized = true;
/**
- * Instead of caching everything, keep track which messages are requested and
- * load only most used messages. This only makes sense if there is lots of
- * interface messages customised in the wiki (like hundreds in many languages).
+ * Instead of caching everything, only cache those messages which have
+ * been customised in the site content language. This means that
+ * MediaWiki:Foo/ja is ignored if MediaWiki:Foo doesn't exist.
+ * This option is probably only useful for translatewiki.net.
*/
$wgAdaptiveMessageCache = false;
/**
* A subtitle to add to the tagline, for skins that have it/
*/
-$wgExtraSubtitle = '';
+$wgExtraSubtitle = '';
/**
* If this is set, a "donate" link will appear in the sidebar. Set it to a URL.
*/
-$wgSiteSupportPage = '';
+$wgSiteSupportPage = '';
/**
* Validate the overall output using tidy and refuse
* can add to this to provide custom jobs
*/
$wgJobClasses = array(
- 'refreshLinks' => 'RefreshLinksJob',
- 'refreshLinks2' => 'RefreshLinksJob2',
- 'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
+ 'refreshLinks' => 'RefreshLinksJob',
+ 'refreshLinks2' => 'RefreshLinksJob2',
+ 'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
'html_cache_update' => 'HTMLCacheUpdateJob', // backwards-compatible
- 'sendMail' => 'EmaillingJob',
- 'enotifNotify' => 'EnotifNotifyJob',
+ 'sendMail' => 'EmaillingJob',
+ 'enotifNotify' => 'EnotifNotifyJob',
'fixDoubleRedirect' => 'DoubleRedirectJob',
- 'uploadFromUrl' => 'UploadFromUrlJob',
+ 'uploadFromUrl' => 'UploadFromUrlJob',
+ 'null' => 'NullJob'
);
/**
* These settings should be global to all wikis.
*/
$wgJobTypeConf = array(
- 'default' => array( 'class' => 'JobQueueDB' ),
+ 'default' => array( 'class' => 'JobQueueDB', 'order' => 'random' ),
);
/**
'protect/modify' => 'modifiedarticleprotection',
'protect/unprotect' => 'unprotectedarticle',
'protect/move_prot' => 'movedarticleprotection',
- 'rights/rights' => 'rightslogentry',
- 'rights/autopromote' => 'rightslogentry-autopromote',
'upload/upload' => 'uploadedimage',
'upload/overwrite' => 'overwroteimage',
'upload/revert' => 'uploadedimage',
* @see LogFormatter
*/
$wgLogActionsHandlers = array(
- 'move/move' => 'MoveLogFormatter',
- 'move/move_redir' => 'MoveLogFormatter',
- 'delete/delete' => 'DeleteLogFormatter',
- 'delete/restore' => 'DeleteLogFormatter',
- 'delete/revision' => 'DeleteLogFormatter',
- 'delete/event' => 'DeleteLogFormatter',
- 'suppress/revision' => 'DeleteLogFormatter',
- 'suppress/event' => 'DeleteLogFormatter',
- 'suppress/delete' => 'DeleteLogFormatter',
- 'patrol/patrol' => 'PatrolLogFormatter',
+ 'move/move' => 'MoveLogFormatter',
+ 'move/move_redir' => 'MoveLogFormatter',
+ 'delete/delete' => 'DeleteLogFormatter',
+ 'delete/restore' => 'DeleteLogFormatter',
+ 'delete/revision' => 'DeleteLogFormatter',
+ 'delete/event' => 'DeleteLogFormatter',
+ 'suppress/revision' => 'DeleteLogFormatter',
+ 'suppress/event' => 'DeleteLogFormatter',
+ 'suppress/delete' => 'DeleteLogFormatter',
+ 'patrol/patrol' => 'PatrolLogFormatter',
+ 'rights/rights' => 'RightsLogFormatter',
+ 'rights/autopromote' => 'RightsLogFormatter',
);
/**
* will be loaded once more from the database when accessing them.
*
* @param $row Array A row from the user table
+ * @param $data Array Further data to load into the object (see User::loadFromRow for valid keys)
* @return User
*/
- public static function newFromRow( $row ) {
+ public static function newFromRow( $row, $data = null ) {
$user = new User;
- $user->loadFromRow( $row );
+ $user->loadFromRow( $row, $data );
return $user;
}
* Initialize this object from a row from the user table.
*
* @param $row Array Row from the user table to load.
+ * @param $data Array Further user data to load into the object
+ *
+ * user_groups Array with groups out of the user_groups table
+ * user_properties Array with properties out of the user_properties table
*/
- public function loadFromRow( $row ) {
+ public function loadFromRow( $row, $data = null ) {
$all = true;
$this->mGroups = null; // deferred
if ( $all ) {
$this->mLoadedItems = true;
}
+
+ if ( is_array( $data ) ) {
+ if ( is_array( $data['user_groups'] ) ) {
+ $this->mGroups = $data['user_groups'];
+ }
+ if ( is_array( $data['user_properties'] ) ) {
+ $this->loadOptions( $data['user_properties'] );
+ }
+ }
}
/**
}
$newGroups = array_merge( $oldGroups, $toPromote ); // all groups
- $log = new LogPage( 'rights', $wgAutopromoteOnceLogInRC /* in RC? */ );
- $log->addEntry( 'autopromote',
- $this->getUserPage(),
- '', // no comment
- // These group names are "list to texted"-ed in class LogPage.
- array( implode( ', ', $oldGroups ), implode( ', ', $newGroups ) )
- );
+ $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
+ $logEntry->setPerformer( $this );
+ $logEntry->setTarget( $this->getUserPage() );
+ $logEntry->setParameters( array(
+ '4::oldgroups' => $oldGroups,
+ '5::newgroups' => $newGroups,
+ ) );
+ $logid = $logEntry->insert();
+ if ( $wgAutopromoteOnceLogInRC ) {
+ $logEntry->publish( $logid );
+ }
}
}
return $toPromote;
public static function getDefaultOptions() {
global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
+ static $defOpt = null;
+ if ( !defined( 'MW_PHPUNIT_TEST' ) && $defOpt !== null ) {
+ // Disabling this for the unit tests, as they rely on being able to change $wgContLang
+ // mid-request and see that change reflected in the return value of this function.
+ // Which is insane and would never happen during normal MW operation
+ return $defOpt;
+ }
+
$defOpt = $wgDefaultUserOptions;
# default language setting
$defOpt['variant'] = $wgContLang->getCode();
}
$defOpt['skin'] = $wgDefaultSkin;
- // FIXME: Ideally we'd cache the results of this function so the hook is only run once,
- // but that breaks the parser tests because they rely on being able to change $wgContLang
- // mid-request and see that change reflected in the return value of this function.
- // Which is insane and would never happen during normal MW operation, but is also not
- // likely to get fixed unless and until we context-ify everything.
- // See also https://www.mediawiki.org/wiki/Special:Code/MediaWiki/101488#c25275
wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
return $defOpt;
global $wgHiddenPrefs;
$this->loadOptions();
- if ( is_null( $this->mOptions ) ) {
- if($defaultOverride != '') {
- return $defaultOverride;
- }
- $this->mOptions = User::getDefaultOptions();
- }
-
# We want 'disabled' preferences to always behave as the default value for
# users, even if they have set the option explicitly in their settings (ie they
# set it, and then it was disabled removing their ability to change it). But
* @param $val mixed New value to set
*/
public function setOption( $oname, $val ) {
- $this->load();
$this->loadOptions();
// Explicitly NULL values should refer to defaults
if( is_null( $val ) ) {
- $defaultOption = self::getDefaultOption( $oname );
- if( !is_null( $defaultOption ) ) {
- $val = $defaultOption;
- }
+ $val = self::getDefaultOption( $oname );
}
$this->mOptions[$oname] = $val;
}
/**
- * @todo document
+ * Load the user options either from cache, the database or an array
+ *
+ * @param $data Rows for the current user out of the user_properties table
*/
- protected function loadOptions() {
+ protected function loadOptions( $data = null ) {
global $wgContLang;
$this->load();
$this->mOptions[$key] = $value;
}
} else {
- wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
- // Load from database
- $dbr = wfGetDB( DB_SLAVE );
-
- $res = $dbr->select(
- 'user_properties',
- array( 'up_property', 'up_value' ),
- array( 'up_user' => $this->getId() ),
- __METHOD__
- );
+ if( !is_array( $data ) ) {
+ wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
+ // Load from database
+ $dbr = wfGetDB( DB_SLAVE );
- $this->mOptionOverrides = array();
- foreach ( $res as $row ) {
- $this->mOptionOverrides[$row->up_property] = $row->up_value;
- $this->mOptions[$row->up_property] = $row->up_value;
+ $res = $dbr->select(
+ 'user_properties',
+ array( 'up_property', 'up_value' ),
+ array( 'up_user' => $this->getId() ),
+ __METHOD__
+ );
+
+ $this->mOptionOverrides = array();
+ $data = array();
+ foreach ( $res as $row ) {
+ $data[$row->up_property] = $row->up_value;
+ }
+ }
+ foreach ( $data as $property => $value ) {
+ $this->mOptionOverrides[$property] = $value;
+ $this->mOptions[$property] = $value;
}
}
* Add a rights log entry for an action.
*/
function addLogEntry( $user, $oldGroups, $newGroups, $reason ) {
- $log = new LogPage( 'rights' );
-
- $log->addEntry( 'rights',
- $user->getUserPage(),
- $reason,
- array(
- $this->makeGroupNameListForLog( $oldGroups ),
- $this->makeGroupNameListForLog( $newGroups )
- )
- );
+ $logEntry = new ManualLogEntry( 'rights', 'rights' );
+ $logEntry->setPerformer( $this->getUser() );
+ $logEntry->setTarget( $user->getUserPage() );
+ $logEntry->setComment( $reason );
+ $logEntry->setParameters( array(
+ '4::oldgroups' => $oldGroups,
+ '5::newgroups' => $newGroups,
+ ) );
+ $logid = $logEntry->insert();
+ $logEntry->publish( $logid );
}
/**
}
}
+ /**
+ * Make a list of group names to be stored as parameter for log entries
+ *
+ * @deprecated in 1.21; use LogFormatter instead.
+ * @param $ids array
+ * @return string
+ */
function makeGroupNameListForLog( $ids ) {
+ wfDeprecated( __METHOD__, '1.21' );
+
if( empty( $ids ) ) {
return '';
} else {
*/
protected function showEditUserGroupsForm( $user, $groups ) {
$list = array();
+ $membersList = array();
foreach( $groups as $group ) {
$list[] = self::buildGroupLink( $group );
+ $membersList[] = self::buildGroupMemberLink( $group );
}
- $autolist = array();
+ $autoList = array();
+ $autoMembersList = array();
if ( $user instanceof User ) {
foreach( Autopromote::getAutopromoteGroups( $user ) as $group ) {
- $autolist[] = self::buildGroupLink( $group );
+ $autoList[] = self::buildGroupLink( $group );
+ $autoMembersList[] = self::buildGroupMemberLink( $group );
}
}
+ $language = $this->getLanguage();
+ $displayedList = $this->msg( 'userrights-groupsmember-type',
+ $language->listToText( $list ),
+ $language->listToText( $membersList )
+ )->plain();
+ $displayedAutolist = $this->msg( 'userrights-groupsmember-type',
+ $language->listToText( $autoList ),
+ $language->listToText( $autoMembersList )
+ )->plain();
+
$grouplist = '';
$count = count( $list );
- if( $count > 0 ) {
+ if ( $count > 0 ) {
$grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse();
- $grouplist = '<p>' . $grouplist . ' ' . $this->getLanguage()->listToText( $list ) . "</p>\n";
+ $grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n";
}
- $count = count( $autolist );
- if( $count > 0 ) {
+ $count = count( $autoList );
+ if ( $count > 0 ) {
$autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )->parse();
- $grouplist .= '<p>' . $autogrouplistintro . ' ' . $this->getLanguage()->listToText( $autolist ) . "</p>\n";
+ $grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n";
}
$userToolLinks = Linker::userToolLinks(
* @return string
*/
private static function buildGroupLink( $group ) {
- static $cache = array();
- if( !isset( $cache[$group] ) )
- $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupName( $group ) ) );
- return $cache[$group];
+ return User::makeGroupLinkHtml( $group, User::getGroupName( $group ) );
+ }
+
+ /**
+ * Format a link to a group member description page
+ *
+ * @param $group string
+ * @return string
+ */
+ private static function buildGroupMemberLink( $group ) {
+ return User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) );
}
/**
}
# Build the HTML table
- $ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) .
+ $ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) .
"<tr>\n";
foreach( $columns as $name => $column ) {
if( $column === array() )
'note' => "'''Note:'''",
'previewnote' => "'''Remember that this is only a preview.'''
Your changes have not yet been saved!",
-'continue-editing' => 'Continue editing',
+'continue-editing' => 'Go to editing area',
'previewconflict' => 'This preview reflects the text in the upper text editing area as it will appear if you choose to save.',
'session_fail_preview' => "'''Sorry! We could not process your edit due to a loss of session data.'''
Please try again.
'saveusergroups' => 'Save user groups',
'userrights-groupsmember' => 'Member of:',
'userrights-groupsmember-auto' => 'Implicit member of:',
+'userrights-groupsmember-type' => '$1', # only translate this message to other languages if you have to change it
'userrights-groups-help' => 'You may alter the groups this user is in:
* A checked box means the user is in that group.
* An unchecked box means the user is not in that group.
'right-passwordreset' => 'View password reset e-mails',
# User rights log
- 'rightslog' => 'User rights log',
- 'rightslogtext' => 'This is a log of changes to user rights.',
- 'rightslogentry' => 'changed group membership for $1 from $2 to $3',
- 'rightslogentry-autopromote' => 'was automatically promoted from $2 to $3',
- 'rightsnone' => '(none)',
+ 'rightslog' => 'User rights log',
+ 'rightslogtext' => 'This is a log of changes to user rights.',
+ 'rightslogentry' => 'changed group membership for $1 from $2 to $3',
+ 'rightslogentry-autopromote' => 'was automatically promoted from $2 to $3',
+ 'logentry-rights-rights' => '$1 changed group membership for $3 from $4 to $5',
+ 'logentry-rights-rights-legacy' => '$1 changed group membership for $3',
+ 'logentry-rights-autopromote' => '$1 was automatically promoted from $4 to $5',
+ 'rightsnone' => '(none)',
# Associated actions - in the sentence "You do not have permission to X"
'action-read' => 'read this page',
'emailuser-title-notarget' => 'E-mail user',
'emailuser-summary' => '', # do not translate or duplicate this message to other languages
'emailpage' => 'E-mail user',
-'emailpagetext' => 'You can use the form below to send an e-mail message to this user.
+// Dummy GENDER to prevent warnings at translatewiki
+'emailpagetext' => 'You can use the form below to send an e-mail message to this {{GENDER:$1|user}}.
The e-mail address you entered in [[Special:Preferences|your user preferences]] will appear as the "From" address of the e-mail, so the recipient will be able to reply directly to you.',
'usermailererror' => 'Mail object returned error:',
'defemailsubject' => '{{SITENAME}} e-mail from user "$1"',
'standard.css' => '/* CSS placed here will affect users of the Standard skin */', # only translate this message to other languages if you have to change it
'nostalgia.css' => '/* CSS placed here will affect users of the Nostalgia skin */', # only translate this message to other languages if you have to change it
'cologneblue.css' => '/* CSS placed here will affect users of the Cologne Blue skin */', # only translate this message to other languages if you have to change it
-'monobook.css' => '/* CSS placed here will affect users of the Monobook skin */', # only translate this message to other languages if you have to change it
+'monobook.css' => '/* CSS placed here will affect users of the MonoBook skin */', # only translate this message to other languages if you have to change it
'myskin.css' => '/* CSS placed here will affect users of the MySkin skin */', # only translate this message to other languages if you have to change it
'chick.css' => '/* CSS placed here will affect users of the Chick skin */', # only translate this message to other languages if you have to change it
'simple.css' => '/* CSS placed here will affect users of the Simple skin */', # only translate this message to other languages if you have to change it
# Info page
'pageinfo-header' => '-', # do not translate or duplicate this message to other languages
'pageinfo-title' => 'Information for "$1"',
-'pageinfo-not-current' => 'Information may only be displayed for the current revision.',
+'pageinfo-not-current' => 'Sorry, it\'s impossible to provide this information for old revisions.',
'pageinfo-header-basic' => 'Basic information',
'pageinfo-header-edits' => 'Edit history',
'pageinfo-header-restrictions' => 'Page protection',
'pageinfo-default-sort' => 'Default sort key',
'pageinfo-length' => 'Page length (in bytes)',
'pageinfo-article-id' => 'Page ID',
+'pageinfo-language' => 'Page content language',
'pageinfo-robot-policy' => 'Search engine status',
'pageinfo-robot-index' => 'Indexable',
'pageinfo-robot-noindex' => 'Not indexable',
'version-license' => 'License',
'version-poweredby-credits' => "This wiki is powered by '''[//www.mediawiki.org/ MediaWiki]''', copyright © 2001-$1 $2.",
'version-poweredby-others' => 'others',
+'version-credits-summary' => 'We would like to recognize the following persons for their contribution to [[Special:Version|MediaWiki]].',
'version-license-info' => 'MediaWiki is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
MediaWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
'saveusergroups',
'userrights-groupsmember',
'userrights-groupsmember-auto',
+ 'userrights-groupsmember-type',
'userrights-groups-help',
'userrights-reason',
'userrights-no-interwiki',
'rightslogtext',
'rightslogentry',
'rightslogentry-autopromote',
+ 'logentry-rights-rights',
+ 'logentry-rights-rights-legacy',
+ 'logentry-rights-autopromote',
'rightsnone',
),
'action' => array(
'pageinfo-default-sort',
'pageinfo-length',
'pageinfo-article-id',
+ 'pageinfo-language',
'pageinfo-robot-policy',
'pageinfo-robot-index',
'pageinfo-robot-noindex',
'version-license',
'version-poweredby-credits',
'version-poweredby-others',
+ 'version-credits-summary',
'version-license-info',
'version-software',
'version-software-product',