From 15ecc206e2da3615029d23b1e0a20cffd8e5f12b Mon Sep 17 00:00:00 2001 From: Looxloox <59655204+Looxloox@users.noreply.github.com> Date: Tue, 21 Jan 2020 10:32:04 +0100 Subject: [PATCH 01/15] Update README.md description update --- README.md | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 872d68e..9909541 100644 --- a/README.md +++ b/README.md @@ -5,25 +5,16 @@ Personnalisations pour MGWiki Cette extension permet de personnaliser le site [MGWiki](https://mgwiki.univ-lyon1.fr), wiki privé dédié au partage d’expérience entre internes en médecine et médecins. -Les personnalisations concernent : +LA BRANCHE "MGWiki-dev" EST UN BAC A SABLE -* la gestion des droits entre différents niveaux d’utilisateurs ; -* la création d’utilisateurs au moyen de formulaires Semantic MediaWiki ; -* l’envoi d’une invitation aux nouveaux inscrits, invitation qui les connecte directement et les redirigent vers le formulaire de leur profil utilisateur ; -* la synchronisation entre les formulaires des profils utilisateur et MediaWiki (groupes utilisateur et adresse de courriel). +Pour la branche officielle en production, voir: https://github.com/WikiValley/MGWiki -Cette extension (et son versant Semantic MediaWiki) a été réalisée par l’entreprise [Wiki Valley](http://wiki-valley.com) pour MGWiki, et est publiée sous licence GPL-3.0+. Il est peu probable qu’elle serve directement à d’autres réutilisateurs, étant très spécialisée, mais elle est publiée dans l’espoir que certaines parties puissent aider d’autres développeurs ayant des besoins similaires. MGWiki customisations ===================== This extension customise the website [MGWiki](https://mgwiki.univ-lyon1.fr), private wiki dedicated to experience sharing between medecine interns and physicians. -These customisations are: +THE BRANCH "MGWiki-dev" IS A SANDBOX. -* the rights management between different user levels; -* the creation of users by means of Semantic MediaWiki forms; -* the sending of an invitation to new users, which logs them directly in and redirects them to the user profile form; -* the synchronisation between the user profile forms and MediaWiki (user groups and email address). - -This extension (and its counterpart Semantic MediaWiki) was realised by the company [Wiki Valley](http://wiki-valley.com) for MGWiki, and is published under the GPL-3.0+ licence. It is unlikely it will be directely reused by other people, being very specialised, but it is published in the hope some parts can help other developers with similar needs. +For official fork, see: https://github.com/WikiValley/MGWiki From 765488155562a3ab32a6f15df6997c8a6f3d7039 Mon Sep 17 00:00:00 2001 From: Looxloox Date: Fri, 24 Jan 2020 12:43:51 +0100 Subject: [PATCH 02/15] =?UTF-8?q?correction=20de=20onuserCan=20(d=C3=A9fau?= =?UTF-8?q?t=20dans=20la=20condition=20ligne=2046)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MGWiki.php | 14 +++++++------- README.md | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/MGWiki.php b/MGWiki.php index 48af016..86ae330 100644 --- a/MGWiki.php +++ b/MGWiki.php @@ -43,7 +43,7 @@ public static function onuserCan( Title &$title, User &$user, $action, &$result if ( $title->getNamespace() == NS_USER && $title->getText() == $user->getName() && array_key_exists( 'EditOwnUserpage', $params ) && $params['EditOwnUserpage'] === true ) { return true; } - if ( array_key_exists( 'RequiredRight', $params ) && is_string( $params['RequiredRight'] && !$user->isAllowed( $params['RequiredRight'] ) ) ) { + if ( array_key_exists( 'RequiredRight', $params ) && is_string( $params['RequiredRight'] ) && !$user->isAllowed( $params['RequiredRight'] ) ) { # Unauthorised user, and all further permissions hooks must be skipped since this result is authoritative $result = false; return false; @@ -115,7 +115,7 @@ public static function onPrefsEmailAudit( $user, $oldaddr, $newaddr ) { * * Only the user or 'admins' with the right 'mgwikimanagelevel1' can report the email address in the user preferences. * If $wgNewUserLog is true (default), add an entry in the 'newusers' log when a user is created. - * + * * @param SMWSQLStore3 $store SemanticMediaWiki store * @param SMWSemanticData $semanticData Semantic data * @param SMW\SQLStore\CompositePropertyTableDiffIterator $compositePropertyTableDiffIterator Differences on property values @@ -164,7 +164,7 @@ public static function onSMW_SQLStore_AfterDataUpdateComplete( SMWSQLStore3 $sto /** * Update the property on the userpage “last-date-edited-by-user-her/himself” - * + * * @param WikiPage $wikiPage The WikiPage (object) being saved. * @param User $user The User (object) saving the article. * @param Content $content The new article content, as a Content object. @@ -220,7 +220,7 @@ static function onPostLoginRedirect( &$returnTo, &$returnToQuery, &$type ) { } /** - * + * * * @param SpecialPage $specialPage * @param string|null $subpage @@ -485,7 +485,7 @@ private static function addMediaWikiGroups( $user, $groups, $editOwnUserpage ) { continue; } - # Else remove the user from the groups + # Else remove the user from the groups $removedGroups = []; foreach( $effectiveGroups as $g => $v ) { if ( $g && $v ) { @@ -576,7 +576,7 @@ public static function createUser( string $username, $userData = [], array $grou $properties = []; if ( array_key_exists( $wgMGWikiUserProperties['email'], $userData ) && is_string( $userData[$wgMGWikiUserProperties['email']] ) ) $properties['email'] = $userData[$wgMGWikiUserProperties['email']]; - + # Create the user and add log entry if ( false && version_compare( $wgVersion, '1.27.0' ) >= 0 ) { //$data = $properties; // Not to send the confirmation email through AuthManager since I want to customise it @@ -601,7 +601,7 @@ public static function createUser( string $username, $userData = [], array $grou 'AuthManager does not support such simplified account creation' ); } - + } else { $user = User::createNew( $username, $properties ); if ( !$user instanceof User ) diff --git a/README.md b/README.md index 9909541..872d68e 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,25 @@ Personnalisations pour MGWiki Cette extension permet de personnaliser le site [MGWiki](https://mgwiki.univ-lyon1.fr), wiki privé dédié au partage d’expérience entre internes en médecine et médecins. -LA BRANCHE "MGWiki-dev" EST UN BAC A SABLE +Les personnalisations concernent : -Pour la branche officielle en production, voir: https://github.com/WikiValley/MGWiki +* la gestion des droits entre différents niveaux d’utilisateurs ; +* la création d’utilisateurs au moyen de formulaires Semantic MediaWiki ; +* l’envoi d’une invitation aux nouveaux inscrits, invitation qui les connecte directement et les redirigent vers le formulaire de leur profil utilisateur ; +* la synchronisation entre les formulaires des profils utilisateur et MediaWiki (groupes utilisateur et adresse de courriel). +Cette extension (et son versant Semantic MediaWiki) a été réalisée par l’entreprise [Wiki Valley](http://wiki-valley.com) pour MGWiki, et est publiée sous licence GPL-3.0+. Il est peu probable qu’elle serve directement à d’autres réutilisateurs, étant très spécialisée, mais elle est publiée dans l’espoir que certaines parties puissent aider d’autres développeurs ayant des besoins similaires. MGWiki customisations ===================== This extension customise the website [MGWiki](https://mgwiki.univ-lyon1.fr), private wiki dedicated to experience sharing between medecine interns and physicians. -THE BRANCH "MGWiki-dev" IS A SANDBOX. +These customisations are: -For official fork, see: https://github.com/WikiValley/MGWiki +* the rights management between different user levels; +* the creation of users by means of Semantic MediaWiki forms; +* the sending of an invitation to new users, which logs them directly in and redirects them to the user profile form; +* the synchronisation between the user profile forms and MediaWiki (user groups and email address). + +This extension (and its counterpart Semantic MediaWiki) was realised by the company [Wiki Valley](http://wiki-valley.com) for MGWiki, and is published under the GPL-3.0+ licence. It is unlikely it will be directely reused by other people, being very specialised, but it is published in the hope some parts can help other developers with similar needs. From 087b0f847c216fb969ed97d757a8c0ba26649239 Mon Sep 17 00:00:00 2001 From: Looxloox <59655204+Looxloox@users.noreply.github.com> Date: Fri, 24 Jan 2020 14:49:01 +0100 Subject: [PATCH 03/15] onuserCan deny new user page without account --- MGWiki.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MGWiki.php b/MGWiki.php index 86ae330..8c0b0ef 100644 --- a/MGWiki.php +++ b/MGWiki.php @@ -31,6 +31,14 @@ public static function onuserCan( Title &$title, User &$user, $action, &$result if ( $action != 'edit' ) { return true; } + + # Deny creation of a user page without existing account + if ($title->getNamespace() == (2 OR 3)){ + $userfromTitle = User::newFromName( $title->getText() )->getId(); + if (!$userfromTitle){ + return false; + } + } # Check permissions for forms $titleEnglish = ''; From bbce62e0f671195866e559855ca1f0d48b76b4ee Mon Sep 17 00:00:00 2001 From: Looxloox Date: Fri, 23 Oct 2020 13:06:15 +0200 Subject: [PATCH 04/15] =?UTF-8?q?formulaire=20de=20demande=20de=20cr=C3=A9?= =?UTF-8?q?ation=20de=20compte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ApiADEPULGroupMember.php | 141 --- ApiNewADEPULGroup.php | 115 --- EmailTokenAuthenticationRequest.php | 42 - EmailTokenPrimaryAuthenticationProvider.php | 134 --- MGWiki.magic.php | 14 - MGWiki.php | 827 ------------------ MGWikiMarguerite.php | 30 - MGWikiParserFunctions.php | 63 -- SpecialInvitation.php | 160 ---- config.schema.json | 137 --- extension.json | 262 +----- i18n/en.json | 43 +- i18n/fr.json | 42 +- i18n/qqq.json | 41 +- .../MGWikiDev.alias.php | 6 +- includes/MGWikiDev.php | 21 + includes/SpecialAccountRequest.php | 99 +++ phpcs.xml | 8 - resources/ext.mgwiki.edit.js | 196 ----- resources/ext.mgwiki.js | 97 -- resources/mgwiki-dev.css | 30 + resources/mgwiki-dev.js | 42 + resources/specialaccountrequest.css | 20 + resources/specialaccountrequest.js | 52 ++ 24 files changed, 312 insertions(+), 2310 deletions(-) delete mode 100644 ApiADEPULGroupMember.php delete mode 100644 ApiNewADEPULGroup.php delete mode 100644 EmailTokenAuthenticationRequest.php delete mode 100644 EmailTokenPrimaryAuthenticationProvider.php delete mode 100644 MGWiki.magic.php delete mode 100644 MGWiki.php delete mode 100644 MGWikiMarguerite.php delete mode 100644 MGWikiParserFunctions.php delete mode 100644 SpecialInvitation.php delete mode 100644 config.schema.json rename MGWiki.alias.php => includes/MGWikiDev.alias.php (62%) create mode 100644 includes/MGWikiDev.php create mode 100644 includes/SpecialAccountRequest.php delete mode 100644 phpcs.xml delete mode 100644 resources/ext.mgwiki.edit.js delete mode 100644 resources/ext.mgwiki.js create mode 100644 resources/mgwiki-dev.css create mode 100644 resources/mgwiki-dev.js create mode 100644 resources/specialaccountrequest.css create mode 100644 resources/specialaccountrequest.js diff --git a/ApiADEPULGroupMember.php b/ApiADEPULGroupMember.php deleted file mode 100644 index e966c31..0000000 --- a/ApiADEPULGroupMember.php +++ /dev/null @@ -1,141 +0,0 @@ -extractRequestParams(); - $apiResult = $this->getResult(); - - $code_action = $params['code_action']; - $membres = $params['membres'] ?: $params['membre']; - $cle = $params['cle']; - - if( $wgMGWikiSecretKeyADEPUL === null || !hash_equals( $wgMGWikiSecretKeyADEPUL, $cle ) ) { - $this->dieWithError( 'mgwiki-wrong-secret-key' ); - } - - // Get ADEPUL group page - $groupTitle = MGWiki::getADEPULGroup( $code_action ); - if( $groupTitle === null ) { - $this->dieWithError( 'mgwiki-unknown-group' ); - } - $groupArticle = WikiPage::factory( $groupTitle ); - $groupContent = $groupArticle->getContent(); - if( $groupContent === null || $groupContent->getModel() !== CONTENT_MODEL_WIKITEXT ) { - $this->dieWithError( 'mgwiki-bad-group' ); - } - $groupWikitext = $groupContent->getText(); - if( !preg_match( '/\{\{Groupe[\n |](?:[^}]+\})+\}/', $groupWikitext, $groupTemplate, PREG_OFFSET_CAPTURE ) ) { - $this->dieWithError( 'mgwiki-bad-adepul-group' ); - } - if( !preg_match( '/^\| *' . $wgMGWikiUserProperties['moderator'] . ' *= *([^|]*)/m', $groupTemplate[0][0], $formateurUsername ) ) { - $this->dieWithError( 'mgwiki-bad-adepul-group' ); - } - if( !preg_match( '/^\| *' . $wgMGWikiUserProperties['membersList'] . ' *= *([^|]*)/m', $groupTemplate[0][0], $membersList, PREG_OFFSET_CAPTURE ) ) { - $this->dieWithError( 'mgwiki-bad-adepul-group' ); - } - $formateurUsername = trim( $formateurUsername[1] ); - $formateurUser = User::newFromName( $formateurUsername ); - - // Get existing members - $existingMembers = explode( ',', $membersList[1][0] ); - $existingMembers = array_map( function($s) { return trim($s); }, $existingMembers ); - if( count( $existingMembers ) === 1 && $existingMembers[0] === '' ) { - $existingMembers = []; - } - - // Retrieve members to be added - $membres = explode( ',', $membres ); - $errorsMembers = []; - $listMembers = []; - foreach( $membres as $membre ) { - try { - $memberUser = MGWiki::getUserByADEPUL( $membre, $formateurUsername ); - $memberUser = $memberUser->getName(); - if( !in_array( $memberUser, $existingMembers ) ) { - $listMembers[] = $memberUser; - } - } catch( Exception $e ) { - $errorsMembers[] = $membre; - } - } - if( count( $listMembers ) === 0 ) { - $apiResult->addValue( null, "result", "success" ); - return; - } - - $newWikitext = substr( $groupWikitext, 0, $groupTemplate[0][1] ) . // Before template Groupe - substr( $groupTemplate[0][0], 0, $membersList[1][1] ) . // In template Groupe, before the parameter 'Membres' and including this parameter name until its '=' - implode( ', ', $listMembers ) . ( count( $existingMembers ) && $existingMembers[0] ? ', ' : '' ) . // Add our new members - substr( $groupTemplate[0][0], $membersList[1][1] ) . // In template Groupe, just after the parameter 'Membres' - substr( $groupWikitext, $groupTemplate[0][1] + strlen( $groupTemplate[0][0] ) ); // After template Groupe - - $newGroupContent = new WikitextContent( $newWikitext ); - - $summary = wfMessage( 'mgwiki-add-member-grouppage' )->inContentLanguage()->text(); - $groupArticle->doEditContent( $newGroupContent, $summary, 0, false, $formateurUser ); - - $apiResult->addValue( null, "result", "success" ); - } - - public function mustBePosted() { - return true; - } - - public function isReadMode() { - return false; - } - - public function isWriteMode() { - return true; - } - - public function getAllowedParams() { - return [ - 'code_action' => [ - ApiBase::PARAM_TYPE => 'string', - ], - 'membre' => [ - ApiBase::PARAM_TYPE => 'string', - ], - 'membres' => [ - ApiBase::PARAM_TYPE => 'string', - ], - 'cle' => [ - ApiBase::PARAM_TYPE => 'string', - ], - ]; - } - - public function needsToken() { - return false; - } -} diff --git a/ApiNewADEPULGroup.php b/ApiNewADEPULGroup.php deleted file mode 100644 index edeeb3a..0000000 --- a/ApiNewADEPULGroup.php +++ /dev/null @@ -1,115 +0,0 @@ -extractRequestParams(); - $apiResult = $this->getResult(); - - $code_action = $params['code_action']; - $intitule_ac = $params['intitule_ac']; - $intitule_long_ac = $params['intitule_long_ac']; - $formateur = $params['formateur']; - $cle = $params['cle']; - - if( $wgMGWikiSecretKeyADEPUL === null || !hash_equals( $wgMGWikiSecretKeyADEPUL, $cle ) ) { - $this->dieWithError( 'mgwiki-wrong-secret-key' ); - } - - $annee = intval( preg_replace( '/^[A-Z]([0-9]{2}).*/', '$1', $code_action ) ); - $annee = $annee ? '20' . $annee : ''; - - try { - $formateurUser = MGWiki::getUserByADEPUL( $formateur ); - $formateurUsername = $formateurUser->getName(); - } catch( Exception $e ) { - $this->dieWithError( 'mgwiki-wrong-secret-key' ); - } - - $title = "DPC ADEPUL $annee - $intitule_ac"; - $wikitext = "{{Groupe -|Archivé=Non -|Description=$intitule_long_ac -|Institution de rattachement=ADEPUL -|Tuteur ou modérateur=$formateurUsername -|Membres= -|Type de groupe=DPC -|Titre de la formation=$intitule_ac -|Année=$annee -|Code action=$code_action -}} -{{Groupe2}}"; - $groupTitle = Title::newFromText( $title, NS_MAIN ); - $groupArticle = WikiPage::factory( $groupTitle ); - $content = new WikitextContent( $wikitext ); - $summary = wfMessage( 'mgwiki-create-grouppage' )->inContentLanguage()->text(); - $flags = EDIT_NEW; - $groupArticle->doEditContent( $content, $summary, $flags, false, $formateurUser ); - - $apiResult->addValue( null, "result", "success" ); - } - - public function mustBePosted() { - return true; - } - - public function isReadMode() { - return false; - } - - public function isWriteMode() { - return true; - } - - public function getAllowedParams() { - return [ - 'code_action' => [ - ApiBase::PARAM_TYPE => 'string', - ], - 'intitule_ac' => [ - ApiBase::PARAM_TYPE => 'string', - ], - 'intitule_long_ac' => [ - ApiBase::PARAM_TYPE => 'string', - ], - 'formateur' => [ - ApiBase::PARAM_TYPE => 'string', - ], - 'cle' => [ - ApiBase::PARAM_TYPE => 'string', - ], - ]; - } - - public function needsToken() { - return false; - } -} diff --git a/EmailTokenAuthenticationRequest.php b/EmailTokenAuthenticationRequest.php deleted file mode 100644 index fc733fd..0000000 --- a/EmailTokenAuthenticationRequest.php +++ /dev/null @@ -1,42 +0,0 @@ - - * @license GPL-3.0+ - */ - -namespace MediaWiki\Auth; - -/** - * This represents the field emailtoken aiming at connecting the user after - * s/he is redirected through Special:Invitation. - * - * @ingroup Auth - */ -class EmailTokenAuthenticationRequest extends AuthenticationRequest { - /** @var string|null Email address token */ - public $emailtoken; - - /** - * Return the field required for this authentication type. - * - * @return array[] The fields with their properties. - */ - public function getFieldInfo() { - $config = \ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); - $ret = [ - 'emailtoken' => [ - 'type' => 'hidden', - 'label' => wfMessage( 'authmanager-emailtoken-label' ), - 'help' => wfMessage( 'authmanager-emailtoken-help' ), - 'optional' => true, - ], - ]; - - if ( !$config->get( 'EnableEmail' ) ) { - unset( $ret['emailtoken'] ); - } - - return $ret; - } -} diff --git a/EmailTokenPrimaryAuthenticationProvider.php b/EmailTokenPrimaryAuthenticationProvider.php deleted file mode 100644 index 61e5280..0000000 --- a/EmailTokenPrimaryAuthenticationProvider.php +++ /dev/null @@ -1,134 +0,0 @@ - - * @license GPL-3.0+ - */ - -namespace MediaWiki\Auth; - -use Config; -use User; - -class EmailTokenPrimaryAuthenticationProvider - extends AbstractPrimaryAuthenticationProvider -{ - /** @var bool */ - protected $sendConfirmationEmail; - - /** - * Construct this authentication provider. - * - * @param array $params - * - sendConfirmationEmail: (bool) send an email asking the user to confirm their email - * address to finalize its account creation - */ - public function __construct( $params = [] ) { - if ( isset( $params['sendConfirmationEmail'] ) ) { - $this->sendConfirmationEmail = (bool)$params['sendConfirmationEmail']; - } - } - - /** - * Set the internal configuration: check if email is activated. - * - * @param Config $config Global MediaWiki configuration. - * @return void - */ - public function setConfig( Config $config ) { - parent::setConfig( $config ); - - if ( $this->sendConfirmationEmail === null ) { - $this->sendConfirmationEmail = $this->config->get( 'EnableEmail' ) - && $this->config->get( 'EmailAuthentication' ); - } - } - - /** - * Return the authentication requests for this specific authentication type. - * - * @param string $action Authentication action, one of AuthManager::ACTION_*. - * @param array $options Options (unused here). - * @return AuthenticationRequest[] The email authentication request for login only. - */ - public function getAuthenticationRequests( $action, array $options ) { - switch ( $action ) { - case AuthManager::ACTION_LOGIN: - case AuthManager::ACTION_LOGIN_CONTINUE: - return [ new EmailTokenAuthenticationRequest() ]; - - default: - return []; - } - } - - public function beginPrimaryAuthentication( array $reqs ) { - $req = AuthenticationRequest::getRequestByClass( $reqs, EmailTokenAuthenticationRequest::class ); - if ( !$req || !$req->emailtoken ) { - return AuthenticationResponse::newAbstain(); - } - - $user = User::newFromConfirmationCode( $req->emailtoken, User::READ_LATEST ); - if ( !( $user instanceof User ) || !$user->getId() ) { - return AuthenticationResponse::newFail( wfMessage( 'mgwiki-bad-email-token' ) ); - } - $username = $user->getName(); - $user->setEmail( $user->getEmail() ); - $user->confirmEmail(); - return AuthenticationResponse::newPass( $username ); - } - - public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) { - /*if ( - $this->sendConfirmationEmail - && $user->getEmail() - && !$this->manager->getAuthenticationSessionData( 'no-email' ) - ) { - $status = $user->sendConfirmationMail(); - $user->saveSettings(); - if ( $status->isGood() ) { - // TODO show 'confirmemail_oncreate' success message - } else { - // TODO show 'confirmemail_sendfailed' error message - $this->logger->warning( 'Could not send confirmation email: ' . - $status->getWikiText( false, false, 'en' ) ); - } - }*/ - - return AuthenticationResponse::newPass(); - } - - public function accountCreationType() { - return self::TYPE_NONE; - } - - public function testUserExists( $username, $flags = User::READ_NORMAL ) { - $username = User::getCanonicalName( $username, 'usable' ); - if ( $username === false ) { - return false; - } - - list( $db, $options ) = \DBAccessObjectUtils::getDBOptions( $flags ); - return (bool)wfGetDB( $db )->selectField( - [ 'user' ], - [ 'user_id' ], - [ 'user_name' => $username ], - __METHOD__, - $options - ); - } - - public function testUserCanAuthenticate( $username ) { - $user = User::newFromName( $username ); - return $user->isEmailConfirmationPending(); - } - - public function providerAllowsAuthenticationDataChange( AuthenticationRequest $req, - $checkData = true - ) { - return \StatusValue::newGood( 'ignored' ); - } - - public function providerChangeAuthenticationData( AuthenticationRequest $req ) { - } -} diff --git a/MGWiki.magic.php b/MGWiki.magic.php deleted file mode 100644 index 6dc50e1..0000000 --- a/MGWiki.magic.php +++ /dev/null @@ -1,14 +0,0 @@ - array( 0, 'isusersysop' ), -); diff --git a/MGWiki.php b/MGWiki.php deleted file mode 100644 index 8c0b0ef..0000000 --- a/MGWiki.php +++ /dev/null @@ -1,827 +0,0 @@ - - * @license GPL-3.0+ - * @package MediaWiki-extension-MGWiki - */ - -declare(strict_types=1); - -use MediaWiki\Auth\AuthManager; -use MediaWiki\Auth\AuthenticationRequest; -use MediaWiki\Auth\AuthenticationResponse; - -class MGWiki { - - /** - * Check permissions for actions. - * - * @param Title $title Title of the page - * @param User $user User about to do the action - * @param string $action Requested action - * @param bool|mixed $result True or false to authorize or deny the action - */ - public static function onuserCan( Title &$title, User &$user, $action, &$result ) { - - global $wgMGWikiForms; - - # Only edit permission is checked - if ( $action != 'edit' ) { - return true; - } - - # Deny creation of a user page without existing account - if ($title->getNamespace() == (2 OR 3)){ - $userfromTitle = User::newFromName( $title->getText() )->getId(); - if (!$userfromTitle){ - return false; - } - } - - # Check permissions for forms - $titleEnglish = ''; - $ns = MWNamespace::getCanonicalName( $title->getNamespace() ); - if ( $ns ) $titleEnglish .= $ns . ':'; - $titleEnglish .= $title->getText(); - foreach ( $wgMGWikiForms as $form => $params ) { - # For each registered form type is associated a resulting page name, check the permissions on this page, not the form itself - if ( array_key_exists( 'RegexPageName', $params ) && preg_match( $params['RegexPageName'], $titleEnglish ) ) { - if ( $title->getNamespace() == NS_USER && $title->getText() == $user->getName() && array_key_exists( 'EditOwnUserpage', $params ) && $params['EditOwnUserpage'] === true ) { - return true; - } - if ( array_key_exists( 'RequiredRight', $params ) && is_string( $params['RequiredRight'] ) && !$user->isAllowed( $params['RequiredRight'] ) ) { - # Unauthorised user, and all further permissions hooks must be skipped since this result is authoritative - $result = false; - return false; - } - } - } - - return true; - } - - /** - * Display a warning if the user account and user page don’t together exist or are missing. - * - * @param Title $targetTitle Page title - * @param string $pre_form_html String displayed just before the form - * @return true - */ - public static function onsfHTMLBeforeForm( $targetTitle, &$pre_form_html ) { - - # Only use this hook on user pages - if ( empty( $targetTitle ) || $targetTitle->getNamespace() != NS_USER ) return; - - # Get the user account - $user = User::newFromName( $targetTitle->getText() )->getId(); - - if ( $targetTitle->exists() xor $user ) { - - $pre_form_html = '
'; - if ( $targetTitle->exists() ) $pre_form_html .= wfMessage( 'mgwiki-userpage-without-useraccount' )->escaped(); - else $pre_form_html .= wfMessage( 'mgwiki-useraccount-without-userpage' )->escaped(); - $pre_form_html .= "
\n"; - } - - return true; - } - - public static function onPrefsEmailAudit( $user, $oldaddr, $newaddr ) { - - global $wgMGWikiUserProperties; - - # Normalise value - $emailProperty = $wgMGWikiUserProperties['email']; - - # Get the wiki page - $title = Title::newFromText( $user->getName(), NS_USER ); - if ( $title->getArticleID() == -1 ) return; - $article = WikiPage::factory( $title ); - $summary = wfMessage( 'mgwiki-changed-email' )->inContentLanguage()->text(); - - # Get the content - $oldContent = $article->getContent(); - if( $oldContent->getModel() != CONTENT_MODEL_WIKITEXT ) { - return; - } - - # Update the content - $content = new WikitextContent( preg_replace( '/\| *'.preg_quote($emailProperty,'/').' *=[a-zA-Z0-9@._+-]+/', "|$emailProperty = $newaddr\n", $oldContent->getNativeData() ) ); - - # And edit - $flags = EDIT_MINOR | EDIT_SUPPRESS_RC | EDIT_UPDATE; - $status = $article->doEditContent( $content, $summary, $flags, false, $wgUser ); - if( !$status->isOK() ) { - return; - } - } - - /** - * When a user page is modified by SemanticMediaWiki, create the corresponding MediaWiki user or update the email - * - * Only the user or 'admins' with the right 'mgwikimanagelevel1' can report the email address in the user preferences. - * If $wgNewUserLog is true (default), add an entry in the 'newusers' log when a user is created. - * - * @param SMWSQLStore3 $store SemanticMediaWiki store - * @param SMWSemanticData $semanticData Semantic data - * @param SMW\SQLStore\CompositePropertyTableDiffIterator $compositePropertyTableDiffIterator Differences on property values - * @return bool|void - */ - public static function onSMW_SQLStore_AfterDataUpdateComplete( SMWSQLStore3 $store, SMWSemanticData $semanticData, SMW\SQLStore\CompositePropertyTableDiffIterator $compositePropertyTableDiffIterator ) { - - global $wgMGWikiForms; - - # Get title with namespace in English - $title = $semanticData->getSubject()->getTitle(); - $titleEnglish = ''; - $ns = MWNamespace::getCanonicalName( $title->getNamespace() ); - if ( $ns ) $titleEnglish .= $ns . ':'; - $titleEnglish .= $title->getText(); - - # Get the user who made the change - # It is executed as a job, so $wgUser is not the real user who made the change - $statements = self::collectSemanticData( [ '_LEDT' ], $semanticData, $complete ); - if ( !array_key_exists( '_LEDT', $statements ) || !$statements['_LEDT'] instanceof Title || $statements['_LEDT']->getNamespace() != 2 ) - return; - $editor = User::newFromName( $statements['_LEDT']->getText() ); - if( !$editor ) { - return false; - } - $editor->load(); - - # Search the form - foreach ( $wgMGWikiForms as $form => $params ) { - if ( array_key_exists( 'RegexPageName', $params ) && preg_match( $params['RegexPageName'], $titleEnglish ) ) { - - self::synchroniseMediaWikiGroups( $title, $editor, $form, $params, $store, $semanticData, $compositePropertyTableDiffIterator ); - - # Delete the page if requested - if ( array_key_exists( 'EphemeralPage', $params ) && $params['EphemeralPage'] ) { - $page = WikiPage::factory( $title ); - $deleteStatus = $page->doDeleteArticleReal( wfMessage( 'mgwiki-ephemeral-page-deleted' )->inContentLanguage()->text() ); - } - - break; - } - } - - return true; - } - - /** - * Update the property on the userpage “last-date-edited-by-user-her/himself” - * - * @param WikiPage $wikiPage The WikiPage (object) being saved. - * @param User $user The User (object) saving the article. - * @param Content $content The new article content, as a Content object. - * @param string $summary The article summary (comment). - * @param integer $isMinor Minor flag. - * @param null $isWatch Watch flag (not used, aka always null). - * @param null $section Section number (not used, aka always null). - * @param integer $flags See WikiPage::doEditContent documentation for flags' definition. - * @param Status $status Status (object). - */ - public static function onPageContentSave( &$wikiPage, &$user, &$content, &$summary, $isMinor, $isWatch, $section, &$flags, &$status ) { - - global $wgMGWikiUserProperties; - - # If not the userpage, not in the scope of this hook - if( !$user->getUserPage()->equals( $wikiPage->getTitle() ) ) { - return true; - } - - # Always update the “last-date-edited-by-user-her/himself” - if( preg_match( "/(^|\r?\n) *\| *" . preg_quote( $wgMGWikiUserProperties['timestamp'], '/' ) . " *=.*(\r?\n|$)/", $content->getNativeData() ) ) { - $content = new WikitextContent( preg_replace( "/(^|\r?\n) *\| *" . preg_quote( $wgMGWikiUserProperties['timestamp'], '/' ) . " *=.*(\r?\n|$)/", - '$1|' . $wgMGWikiUserProperties['timestamp'] . '=' . wfTimestamp() . '$2', - $content->getNativeData() ) ); - } else { - $content = new WikitextContent( preg_replace( "/((^|\r?\n) *\| *" . preg_quote( $wgMGWikiUserProperties['email'], '/' ) . " *=.*)(\r?\n|$)/", - "\$1\n|" . $wgMGWikiUserProperties['timestamp'] . '=' . wfTimestamp() . '$3', - $content->getNativeData() ) ); - } - } - - /** - * Redirect the user just after login if her/his semantic property says - * s/he should update her/his informations. - */ - static function onPostLoginRedirect( &$returnTo, &$returnToQuery, &$type ) { - - global $wgUser; - global $wgMGWikiUserProperties; - - $store = &smwfGetStore(); - $complete = null; - - $update = self::collectSemanticData( [ $wgMGWikiUserProperties['requiredUserUpdate'] ], $store->getSemanticData( SMW\DIWikiPage::newFromTitle( $wgUser->getUserPage() ) ), $complete ); - - if( count( $update ) == 1 && $update[$wgMGWikiUserProperties['requiredUserUpdate']] ) { - $returnTo = $wgUser->getUserPage()->getPrefixedText(); - $returnToQuery = [ 'action' => 'formedit' ]; - $type = 'successredirect'; - } - - return true; - } - - /** - * - * - * @param SpecialPage $specialPage - * @param string|null $subpage - */ - static function onSpecialPageAfterExecute( $specialPage, $subpage ) { - - global $wgUser, $wgOut; - - # After the user has changed her/his password, send her/him to her/his userpage in form-edition to confirm her/his data - if( $specialPage->getName() == 'ChangeCredentials' && $specialPage->getRequest()->wasPosted() ) { - - $wgOut->redirect( $wgUser->getUserPage()->getFullURL( [ 'action' => 'formedit' ] ) ); - $wgOut->output(); - } - - return true; - } - - /** - * Synchronise the requested groups from the semantic form with MediaWiki groups. - * - * @param Title $title Title of the subject page. - * @param User $editor Last editor of the page. - * @param string $form Name of this form. - * @param array $paramsForm Parameters for this form type. - * @param SMWSQLStore3 $store SemanticMediaWiki store. - * @param SMWSemanticData $semanticData Semantic data. - * @param SMW\SQLStore\CompositePropertyTableDiffIterator $compositePropertyTableDiffIterator Differences on property values. - * @return true - */ - private static function synchroniseMediaWikiGroups( Title $title, User $editor, $form, array $paramsForm, $store, $semanticData, $compositePropertyTableDiffIterator ) { - - global $wgMGWikiFieldsGroups, $wgMGWikiUserProperties, $wgMGWikiGroups; - global $wgUser; - - # Default groups to be added - $groups = []; - $editOwnUserpage = false; - $complete = null; - - # Check if the user edits her/his own userpage - if ( $title->getNamespace() === NS_USER ) { - if( strpos( $title->getDBkey(), '/' ) !== false ) { - return; - } - $user = User::newFromName( $title->getDBkey() ); - if( $user instanceof User ) { - $user->load(); - if ( $editor->isLoggedIn() && $editor->getId() == $user->getId() ) - $editOwnUserpage = array_key_exists( 'EditOwnUserpage', $paramsForm ) && $paramsForm['EditOwnUserpage']; - } - } - - # Check permissions - if ( !$editor->isAllowed( $paramsForm['RequiredRight'] ) && !$editOwnUserpage ) - return; - - # Iterate over the fields groups - $defaultGroups = self::searchFieldsGroups( $title, $editor, $semanticData, $editOwnUserpage ); - - # Get moderator’s institution if defined - $institution = []; - if( array_key_exists( 'InstitutionFromModerator', $paramsForm ) && $paramsForm['InstitutionFromModerator'] ) { - $moderator = self::collectSemanticData( [ $wgMGWikiUserProperties['moderator'] ], $semanticData, $complete ); - if( count( $moderator ) == 1 ) { - $institution = self::collectSemanticData( [ $wgMGWikiUserProperties['institution'] ], - $store->getSemanticData( SMW\DIWikiPage::newFromTitle( $moderator[$wgMGWikiUserProperties['moderator']] ) ), - $complete ); - if( count( $institution ) != 1 ) { - $institution = []; - } - $institution[$wgMGWikiUserProperties['referrer']] = $moderator[$wgMGWikiUserProperties['moderator']]->getText(); - } - } - elseif( array_key_exists( 'InstitutionFromCreator', $paramsForm ) && $paramsForm['InstitutionFromCreator'] ) { - $creator = self::collectSemanticData( [ '_LEDT' ], $semanticData, $complete ); - if( count( $creator ) == 1 ) { - $institution = self::collectSemanticData( [ $wgMGWikiUserProperties['institution'] ], - $store->getSemanticData( SMW\DIWikiPage::newFromTitle( $creator['_LEDT'] ) ), - $complete ); - if( count( $institution ) != 1 ) { - $institution = []; - } - $institution[$wgMGWikiUserProperties['referrer']] = $creator['_LEDT']->getText(); - } - } - - # Iterate over the subobjects - $content = ''; - $templates = []; - if ( array_key_exists( 'SubObjects', $paramsForm ) && $paramsForm['SubObjects'] ) { - if ( $semanticData->hasSubSemanticData() ) { - $subSemanticData = $semanticData->getSubSemanticData(); - $createdUsers = []; - if ( array_key_exists( 'MergeNewUsers', $paramsForm ) && is_array( $paramsForm['MergeNewUsers'] ) ) { - $templates = $paramsForm['MergeNewUsers']; - $article = WikiPage::factory( $title ); - # Get the content - $contentObject = $article->getContent(); - if( $contentObject->getModel() == CONTENT_MODEL_WIKITEXT ) { - $content = $contentObject->getNativeData(); - } - } - foreach ( $subSemanticData as $user => $userSemanticData ) { - - # Create users - $propertiesToBeSearched = array_values( $wgMGWikiUserProperties ); - $userData = self::collectSemanticData( $propertiesToBeSearched, $userSemanticData, $complete ); - $userData = array_merge( $institution, $userData ); - if ( array_key_exists( $wgMGWikiUserProperties['firstname'], $userData ) && array_key_exists( $wgMGWikiUserProperties['lastname'], $userData ) ) { - # Iterate over the fields groups - $userGroups = self::searchFieldsGroups( null, $editor, $userSemanticData, $editOwnUserpage ); - $groups = array_merge( $defaultGroups, $userGroups ); - #echo "userData = ";var_dump($userData); - #echo "defaultGroups = ";var_dump($defaultGroups); - #echo "userGroups = ";var_dump($userGroups); - - # If there is a person status, override add it - #foreach ( $wgMGWikiFieldsGroups as $k => $v ) { - # if ( array_key_exists( $k, $groups ) ) { - # $userData[$k] = array_flip( $v['MapFromProperty'] )[$groups[$k]]; - # } - #} - #echo "userData (bis) = ";var_dump($userData); - - $username = $userData[$wgMGWikiUserProperties['firstname']].' '.$userData[$wgMGWikiUserProperties['lastname']]; - self::createUser( $username, $userData ); - $createdUsers[] = $userData; - - # User groups - self::addMediaWikiGroups( $username, $groups, $editOwnUserpage ); - - # Replace templates with lists - if( $content ) { - foreach( $templates as $template => $list ) { - $content = preg_replace( '/\{\{ *' . $wgMGWikiUserProperties[$template] . "[ \|\n].*?\}\}\n?/s", '', $content ); - if( !preg_match( '/\| *' . $wgMGWikiUserProperties[$list] . " *=(.*?) *([\|\n])/", $content ) ) { - $content = preg_replace( '/\| *' . $wgMGWikiUserProperties['moderator'] . " *=(?:.*?) *\n/", "$0|" . $wgMGWikiUserProperties[$list] . ' = ' . $username . "\n", $content ); - } else { - $content = preg_replace( '/\| *' . $wgMGWikiUserProperties[$list] . " *= *(.*?) *([\|\n])/", '|' . $wgMGWikiUserProperties[$list] . ' = $1, ' . $username . '$2', $content ); - } - } - } - } - } - } - - # Save - if ( $content && count( $templates ) ) { - - # Update the content - $contentObject = new WikitextContent( $content ); - - # And edit - $flags = EDIT_MINOR | EDIT_UPDATE; - $summary = wfMessage( 'mgwiki-summary-rewrite-grouppage' )->inContentLanguage()->text(); - $status = $article->doEditContent( $contentObject, $summary, $flags, false, $wgUser ); - if( !$status->isOK() ) { - # Error - } - } - } - - # Standalone form (Personne) - elseif ( $title->getNamespace() == NS_USER ) { - # Search if there is an email property - $email = ''; - $statements = self::collectSemanticData( [ $wgMGWikiUserProperties['email'] ], $semanticData, $complete ); - if ( array_key_exists( $wgMGWikiUserProperties['email'], $statements ) ) - $email = $statements[$wgMGWikiUserProperties['email']]; - - # If the user doesn’t exist, create it - if ( self::createUser( $title->getText(), [ $wgMGWikiUserProperties['email'] => $email ] ) ); - - # Or just update the email - elseif ( $email && $user->getEmail() != $email ) { - $user->setEmail( $email ); - $user->saveSettings(); - } - - # And update the user groups - self::addMediaWikiGroups( $user, $defaultGroups, $editOwnUserpage ); - } - } - - private static function searchFieldsGroups( $title, $editor, $semanticData, $editOwnUserpage ) { - - global $wgMGWikiFieldsGroups; - - $groups = []; - $complete = null; - - foreach( $wgMGWikiFieldsGroups as $property => $paramsProperty ) { - - # Get data - $statements = self::collectSemanticData( [ $property ], $semanticData, $complete ); - #echo "\$statements ($property) = ";var_dump($statements); - - # Check permissions - $canEditOwnUserpage = array_key_exists( 'EditOwnUserpage', $paramsProperty ) && $paramsProperty['EditOwnUserpage']; - if ( !$editor->isAllowed( $paramsProperty['RequiredRight'] ) && !($editOwnUserpage && $canEditOwnUserpage) ) - continue; - - # Get the group to be added - if ( array_key_exists( $property, $statements ) ) { - $value = $statements[$property]; - // If the property is not explicitely defined (and has the type Page) - if( $value instanceof Title ) { - $value = $value->getText(); - } - if( ! is_string( $value ) ) { // Mainly boolean - $value = (string) $value; - } - $groups[$property] = $paramsProperty['MapFromProperty'][$value]; - } elseif ( $title && array_key_exists( 'MapFromTitle', $paramsProperty ) ) { - #echo "Title = ".$title->getText()." MapFromTitle ($property) =";var_dump($paramsProperty['MapFromTitle']); - foreach( $paramsProperty['MapFromTitle'] as $regex => $group ) { - if ( preg_match( $regex, $title->getText() ) ) - $groups[$property] = $group; - } - } - if ( !array_key_exists( $property, $groups ) && in_array( '', $paramsProperty['Groups'] ) ) - $groups[$property] = ''; - #echo "\$groups ($property) = ";var_dump($groups); - } - - return $groups; - } - - private static function addMediaWikiGroups( $user, $groups, $editOwnUserpage ) { - - global $wgMGWikiFieldsGroups; - - if ( is_string( $user ) ) - $user = User::newFromName( $user ); - - foreach( $groups as $property => $valueProperty ) { - - # Check permissions - if ( !$user->isAllowed( $wgMGWikiFieldsGroups[$property]['RequiredRight'] ) && !( array_key_exists( 'EditOwnUserpage', $wgMGWikiFieldsGroups[$property] ) && $wgMGWikiFieldsGroups[$property]['EditOwnUserpage'] === true && $editOwnUserpage ) ) { - continue; - } - - # Collect currently subscribed groups - $uniqueGroup = null; - $effectiveGroups = []; - foreach( $wgMGWikiFieldsGroups[$property]['Groups'] as $g ) { - $effectiveGroupe[$g] = false; - if ( $g && in_array( $g, $user->getGroups() ) ) { - $effectiveGroups[$g] = true; - if ( $uniqueGroup === null ) $uniqueGroup = $g; - else $uniqueGroup = false; - } - } - if ( in_array( '', $wgMGWikiFieldsGroups[$property]['Groups'] ) && $uniqueGroup === null ) { - $effectiveGroups[''] = true; - $uniqueGroup = ''; - } - - # Is it what we want? If so, continue - if ( $uniqueGroup === $valueProperty ) { - continue; - } - - # Else remove the user from the groups - $removedGroups = []; - foreach( $effectiveGroups as $g => $v ) { - if ( $g && $v ) { - $user->removeGroup( $g ); - $removedGroups[] = $g; - } - } - - # If a group is wanted, add it - if ( !$valueProperty ) - continue; - $user->addGroup( $valueProperty ); - } - } - - /** - * Collect the requested data. - * - * @param string[] $fields Field names - * @param SMW\SemanticData $semanticData Semantic data - * @param bool $complete Is set to true or false depending if all fields were found in the data - * @return array Requested data - */ - protected static function collectSemanticData( array $fields, SMW\SemanticData $semanticData, &$complete ) { - - # Init - $userData = array(); - $count = 0; - - # Retrieve values - $properties = $semanticData->getProperties(); - - # Normalise keys - $mapNormalisation = []; - foreach( $fields as $field ) - $mapNormalisation[str_replace( ' ', '_', $field )] = $field; - #echo "mapNormalisation = ";var_dump($mapNormalisation); - - # Iterate over existing properties and search requested properties - foreach( $properties as $key => $diProperty ) { - $values = $semanticData->getPropertyValues( $diProperty ); - #echo "property ".$diProperty->getKey()." found with ".count( $values )." values and type ".current( $values )->getDIType()."\n"; - if ( !in_array( $diProperty->getKey(), array_keys( $mapNormalisation ) ) ) - continue; - #echo "property ".$diProperty->getKey()." (".$mapNormalisation[$diProperty->getKey()].") found with ".count( $values )." values and type ".current( $values )->getDIType()."\n"; - if ( count( $values ) == 1 && current( $values )->getDIType() == SMWDataItem::TYPE_BLOB ) { - #echo "property ".$diProperty->getKey()." (".$mapNormalisation[$diProperty->getKey()].") = ".current( $values )->getString()."\n"; - $userData[$mapNormalisation[$diProperty->getKey()]] = current( $values )->getString(); - $count++; - } - elseif ( count( $values ) == 1 && current( $values )->getDIType() == SMWDataItem::TYPE_WIKIPAGE ) { - #echo "property ".$diProperty->getKey()." (".$mapNormalisation[$diProperty->getKey()].") = ".current( $values )->getTitle()."\n"; - $userData[$mapNormalisation[$diProperty->getKey()]] = current( $values )->getTitle(); - $count++; - } - elseif ( count( $values ) == 1 && current( $values )->getDIType() == SMWDataItem::TYPE_BOOLEAN ) { - #echo "property ".$diProperty->getKey()." (".$mapNormalisation[$diProperty->getKey()].") = ".(current( $values )->getBoolean()?'true':'false')."\n"; - $userData[$mapNormalisation[$diProperty->getKey()]] = current( $values )->getBoolean(); - $count++; - } - } - - # Check if we have all mandatory values - $complete = false; - if ( $count == count( $fields ) ) $complete = true; - - return $userData; - } - - /** - * Create a user. - * - * @param string $username Username - * @param string|null $email E-mail - * @param array $groups Groups - * @return bool The user was created - */ - public static function createUser( string $username, $userData = [], array $groups = [] ) { - - global $wgUser, $wgNewUserLog, $wgVersion; - global $wgMGWikiUserProperties; - - $username = User::getCanonicalName( $username, 'creatable' ); - $user = User::newFromName( $username ); - if ( $user->getId() ) - return false; - - $properties = []; - if ( array_key_exists( $wgMGWikiUserProperties['email'], $userData ) && is_string( $userData[$wgMGWikiUserProperties['email']] ) ) - $properties['email'] = $userData[$wgMGWikiUserProperties['email']]; - - # Create the user and add log entry - if ( false && version_compare( $wgVersion, '1.27.0' ) >= 0 ) { - //$data = $properties; // Not to send the confirmation email through AuthManager since I want to customise it - $data = []; - $data['username'] = $username; - $data['password'] = ''; - $data['retype'] = ''; - - # This comes from AuthManagerAuthPlugin - $reqs = AuthManager::singleton()->getAuthenticationRequests( AuthManager::ACTION_CREATE ); - $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data ); - $res = AuthManager::singleton()->beginAccountCreation( $wgUser, $reqs, 'null:' ); - switch ( $res->status ) { - case AuthenticationResponse::PASS: - return true; - case AuthenticationResponse::FAIL: - // Hope it's not a PreAuthenticationProvider that failed... - $msg = $res->message instanceof \Message ? $res->message : new \Message( $res->message ); - return false; - default: - throw new \BadMethodCallException( - 'AuthManager does not support such simplified account creation' - ); - } - - } else { - $user = User::createNew( $username, $properties ); - if ( !$user instanceof User ) - return false; - if ( $wgNewUserLog ) { - $logEntry = new ManualLogEntry( 'newusers', 'create2' ); - $logEntry->setPerformer( $wgUser ); - $logEntry->setTarget( $user->getUserPage() ); - $logEntry->setParameters( array( '4::userid' => $user->getId() ) ); - $logid = $logEntry->insert(); - $logEntry->publish( $logid ); - } - } - - # Add template on userpage - $userTitle = Title::newFromText( $username, NS_USER ); - $userArticle = WikiPage::factory( $userTitle ); - $summary = wfMessage( 'mgwiki-create-userpage' )->inContentLanguage()->text(); - $email = array_key_exists( $wgMGWikiUserProperties['email'], $userData ) ? $userData[$wgMGWikiUserProperties['email']] : ''; - $statutPers = array_key_exists( $wgMGWikiUserProperties['statutPersonne'], $userData ) ? $userData[$wgMGWikiUserProperties['statutPersonne']] : ''; - $statutAddPers = array_key_exists( $wgMGWikiUserProperties['statutAdditionnelPersonne'], $userData ) ? $userData[$wgMGWikiUserProperties['statutAdditionnelPersonne']] : ''; - $institution = array_key_exists( $wgMGWikiUserProperties['institution'], $userData ) ? $userData[$wgMGWikiUserProperties['institution']]->getPrefixedText() : ''; - $referrer = array_key_exists( $wgMGWikiUserProperties['referrer'], $userData ) ? $userData[$wgMGWikiUserProperties['referrer']] : ''; - $content = new WikitextContent( wfMessage( 'mgwiki-template-new-userpage', - $username, $userData[$wgMGWikiUserProperties['firstname']], $userData[$wgMGWikiUserProperties['lastname']], - $email, $statutPers, $statutAddPers, $institution, $referrer - )->inContentLanguage()->plain() ); - $flags = EDIT_NEW; - $userArticle->doEditContent( $content, $summary, $flags, false, $wgUser ); - - # Send email - $user->sendConfirmationMail( 'created_by_mgwiki' ); - - return true; - } - - /** - * Get the user from the official ADEPUL database. - * - * @param string $code_adepul ADEPUL id. - * @return object|false|null Object with the various data describing the ADEPUL user, or false if non-existing ADEPUL user, or null if error. - */ - public static function getUserFromOfficialADEPUL( $code_adepul ) { - global $wgMGWikiUserEndpointADEPUL, $wgMGWikiSecretKeyADEPUL; - $infoADEPUL = file_get_contents( $wgMGWikiUserEndpointADEPUL . '?t_idf=' . $code_adepul . '&cle=' . $wgMGWikiSecretKeyADEPUL ); - if( !$infoADEPUL ) { - return null; - } - $infoADEPUL = json_decode( $infoADEPUL ); - if( $infoADEPUL === null ) { - return null; - } - if( $infoADEPUL->existe === 'NON' ) { - return false; - } - return $infoADEPUL; - } - - /** - * Get (or create) the user corresponding to an ADEPUL id. - * - * @param string $code_adepul ADEPUL id. - * @param string|null $creator Username creating a new user, or $wgMGWikiDefaultCreatorNewAccounts if null. - * @return User|null MediaWiki user, possibly just created with the specific MGWiki process. - */ - public static function getUserByADEPUL( $code_adepul, $creator = null ) { - global $wgUser; - global $wgMGWikiUserProperties, $wgMGWikiDefaultCreatorNewAccounts; - - $codeAdepulTitle = Title::newFromText( 'Property:' . $wgMGWikiUserProperties['codeAdepul'] ); - $codeAdepul = $codeAdepulTitle->getDBkey(); - - // Create property instance - $property = new SMWDIProperty( $codeAdepul ); - $property->setPropertyTypeId( SMW\DataValues\StringValue::TYPE_ID ); - $dataItem = new SMWDIBlob( $code_adepul ); - $dataValue = SMW\DataValueFactory::getInstance()->newDataValueByItem( - $dataItem, - $property - ); - - // Create a description that represents the condition - $descriptionFactory = new SMW\Query\DescriptionFactory(); - $namespaceDescription = $descriptionFactory->newNamespaceDescription( - NS_USER - ); - $descriptionAdepul = $descriptionFactory->newSomeProperty( - $property, - $descriptionFactory->newValueDescription( $dataItem ) - ); - $description = $descriptionFactory->newConjunction( array( - $namespaceDescription, - $descriptionAdepul - ) ); - $propertyValue = SMW\DataValueFactory::getInstance()->newPropertyValueByLabel( - $codeAdepul - ); - - $description->addPrintRequest( - new SMW\Query\PrintRequest( SMW\Query\PrintRequest::PRINT_PROP, null, $propertyValue ) - ); - - // Create query object - $query = new SMWQuery( - $description - ); - - $query->querymode = SMWQuery::MODE_INSTANCES; - - // Try to match condition against the store - $queryResult = SMW\ApplicationFactory::getInstance()->getStore()->getQueryResult( $query ); - - if( $queryResult->getCount() === 0 ) { - # Create user - $backupWgUser = $wgUser; - $creator = $creator ?: $wgMGWikiDefaultCreatorNewAccounts; - $wgUser = User::newFromName( $creator ); - if( !$wgUser || $wgUser->getId() === 0 ) { - if( $creator !== $wgMGWikiDefaultCreatorNewAccounts ) { - $wgUser = User::newFromName( $wgMGWikiDefaultCreatorNewAccounts ); - } else { - throw new Exception(); // TODO improve - } - } - $adhAdepul = self::getUserFromOfficialADEPUL( $code_adepul ); - if( !$adhAdepul ) { - throw new Exception(); // TODO improve - } - $prenom = $adhAdepul->prenom; - $nom = $adhAdepul->nom; - $mail = $adhAdepul->mail; - $profession = $adhAdepul->profession; - $specialite = $adhAdepul->specialite; - $username = $prenom . ' ' . strtoupper( $nom ); - $userData = []; - $userData[$wgMGWikiUserProperties['firstname']] = $prenom; - $userData[$wgMGWikiUserProperties['lastname']] = $nom; - $userData[$wgMGWikiUserProperties['institution']] = Title::newFromText( 'ADEPUL', NS_PROJECT ); - $userData[$wgMGWikiUserProperties['codeAdepul']] = $code_adepul; - $userData[$wgMGWikiUserProperties['email']] = $mail; - MGWiki::createUser( $username, $userData ); - $wgUser = $backupWgUser; - $user = User::newFromName( $username ); - return $user; - } elseif( $queryResult->getCount() > 1 ) { - throw new Exception(); // TODO improve - } elseif( $queryResult->getCount() === 1 ) { - $userValue = $queryResult->getResults()[0]; - $username = $userValue->getDBkey(); - $user = User::newFromName( $username ); - return $user; - } - - return null; - } - - /** - * Get an ADEPUL group. - * - * @param string $code_action ADEPUL action id. - * @return Title|null MediaWiki page of the ADEPUL group. - */ - public static function getADEPULGroup( $code_action ) { - global $wgUser; - global $wgMGWikiUserProperties; - - $codeActionTitle = Title::newFromText( 'Property:' . $wgMGWikiUserProperties['codeActionAdepul'] ); - $codeAction = $codeActionTitle->getDBkey(); - - // Create property instance - $property = new SMWDIProperty( $codeAction ); - $property->setPropertyTypeId( SMW\DataValues\StringValue::TYPE_ID ); - $dataItem = new SMWDIBlob( $code_action ); - $dataValue = SMW\DataValueFactory::getInstance()->newDataValueByItem( - $dataItem, - $property - ); - - // Create a description that represents the condition - $descriptionFactory = new SMW\Query\DescriptionFactory(); - $description = $descriptionFactory->newSomeProperty( - $property, - $descriptionFactory->newValueDescription( $dataItem ) - ); - $propertyValue = SMW\DataValueFactory::getInstance()->newPropertyValueByLabel( - $codeAction - ); - - $description->addPrintRequest( - new SMW\Query\PrintRequest( SMW\Query\PrintRequest::PRINT_PROP, null, $propertyValue ) - ); - - // Create query object - $query = new SMWQuery( - $description - ); - - $query->querymode = SMWQuery::MODE_INSTANCES; - - // Try to match condition against the store - $queryResult = SMW\ApplicationFactory::getInstance()->getStore()->getQueryResult( $query ); - - if( $queryResult->getCount() === 0 ) { - return null; - } elseif( $queryResult->getCount() > 1 ) { - throw new Exception(); // TODO improve - } elseif( $queryResult->getCount() === 1 ) { - $groupValue = $queryResult->getResults()[0]; - $group = Title::newFromText( $groupValue->getDBkey() ); - return $group; - } - - return null; - } -} diff --git a/MGWikiMarguerite.php b/MGWikiMarguerite.php deleted file mode 100644 index ab5fc3a..0000000 --- a/MGWikiMarguerite.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @license GPL-3.0+ - * @package MediaWiki-extension-MGWiki - */ - -/** - * Class for the “marguerite” (ox-eye daisy) feature. - */ -class MGWikiMarguerite { - - /** - * Hook function for MediaWiki’s hook "BeforePageDisplay". - * - * This hook simply adds the JavaScript module ext.mgwiki.edit. - * - * @param OutputPage $out - * @param Skin $skin - * @return true - */ - static function onBeforePageDisplay( OutputPage &$out, Skin &$skin ) { - - $out->addModules( 'ext.mgwiki' ); - - return true; - } -} diff --git a/MGWikiParserFunctions.php b/MGWikiParserFunctions.php deleted file mode 100644 index ac3c6d9..0000000 --- a/MGWikiParserFunctions.php +++ /dev/null @@ -1,63 +0,0 @@ - - * @license GPL-3.0+ - * @package MediaWiki-extension-MGWiki - */ - -/** - * Class for some parser functions. - */ -class MGWikiParserFunctions { - - /** - * Hook function for MediaWiki’s hook "ParserFirstCallInit". - * - * @param Parser $parser Parser object where will be registered the new parser functions. - * @return true - */ - public static function onParserFirstCallInit( &$parser ) { - - $parser->setFunctionHook( 'isusersysop', 'MGWikiParserFunctions::pfuncIsUserSysop', Parser::SFH_OBJECT_ARGS ); - - return true; - } - - /** - * Parser function returning 1 if the given user is a sysop, else an empty string. - * - * This result is compatible with SemanticMediaWiki: you can directly use the result to set some semantic - * property, and hence you can get the adminness as a data source in SemanticMediaWiki. - * - * In case of error, returns an HTML red warning, which can be caught by #iferror from ParserFunctions. - * - * @param Parser $parser Parser Parser object where is registered the parser function. - * @param PPFrame $frame - * @param array $args - * @return string Result of the parser function. - */ - public static function pfuncIsUserSysop( $parser, $frame, $args ) { - - try { - $username = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : ''; - if( !$username ) { - return '' . wfMessage( 'mgwiki-isusersysop-nousername' )->text() . ''; - } - if( User::isIP( $username ) ) { - return '0'; - } - $user = User::newFromName( $username ); - if( !$user ) { - return '' . wfMessage( 'mgwiki-isusersysop-badusername' )->text() . ''; - } - if( in_array( 'sysop', $user->getGroups() ) ) { - return '1'; - } - } catch( Exception $e ) { - return '' . wfMessage( 'mgwiki-parserfunction-internalerror' )->text() . ''; - } - return '0'; - } -} diff --git a/SpecialInvitation.php b/SpecialInvitation.php deleted file mode 100644 index 12b0b45..0000000 --- a/SpecialInvitation.php +++ /dev/null @@ -1,160 +0,0 @@ - - * @license GPL-3.0+ - */ - -use MediaWiki\Auth\AuthManager; -use MediaWiki\Auth\AuthenticationResponse; -use MediaWiki\Auth\CreatedAccountAuthenticationRequest; - -/** - * Special page allows users to request email confirmation message, and handles - * processing of the confirmation code when the link in the email is followed - * - * Derived from SpecialConfirmemail.php with heavy customisations. - * @author Brion Vibber - * @author Rob Church - */ -#class Invitation extends AuthManagerSpecialPage { -class Invitation extends LoginSignupSpecialPage { - public function __construct() { - parent::__construct( 'Invitation' ); - $this->setListed( false ); - } - - public function doesWrites() { - return true; - } - - /** - * Get the default action for this special page, if none is given via URL/POST data. - * Subclasses should override this (or override loadAuth() so this is never called). - * @param string $subPage Subpage of the special page. - * @return string an AuthManager::ACTION_* constant. - */ - protected function getDefaultAction( $subPage ) { - return AuthManager::ACTION_LOGIN_CONTINUE; - } - - protected function getLoginSecurityLevel() { - return false; - } - - protected function isSignup() { - return false; - } - - /** - * @param bool $direct True if the action was successful just now; false if that happened - * pre-redirection (so this handler was called already) - * @param StatusValue|null $extraMessages - * @return void - */ - protected function successfulAction( $direct = false, $extraMessages = null ) { - } - - /** - * Logs to the authmanager-stats channel. - * @param bool $success - * @param string|null $status Error message key - */ - protected function logAuthResult( $success, $status = null ) { - } - - protected function beforeExecute( $subPage ) { - $data = []; - $data['emailtoken'] = $subPage; - $data[$this->getTokenName()] = $this->getToken()->toString(); - $this->setRequest( $data, true ); - parent::beforeExecute( $subPage ); - } - - /** - * Main execution point - * - * @param null|string $code Confirmation code passed to the page - * @throws PermissionsError - * @throws ReadOnlyError - * @throws UserNotLoggedIn - */ - function execute( $code ) { - // Ignore things like master queries/connections on GET requests. - // It's very convenient to just allow formless link usage. - Profiler::instance()->getTransactionProfiler()->resetExpectations(); - #$authManager = AuthManager::singleton(); - - $this->setHeaders(); - - $this->checkReadOnly(); - $this->checkPermissions(); - - # Check the code has a good format - if( !$code || !preg_match( '/^[0-9a-f]{32}$/', $code ) ) { - $this->getOutput()->addWikiMsg( 'mgwiki-bad-email-token' ); - $this->getOutput()->returnToMain(); - return; - } - // This could also let someone check the current email address, so - // require both permissions. - #if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) { - # throw new PermissionsError( 'viewmyprivateinfo' ); - #} - - $this->loadAuth( '', AuthManager::ACTION_LOGIN ); - #var_dump($this->authAction); - #var_dump($this->authRequests); - $status = $this->trySubmit(); - #var_dump($this->authRequests); - - $response = $status->getValue(); - #var_dump($status); - #var_dump($response); - switch( $response->status ) { - case AuthenticationResponse::PASS: - # Update session data to immediately connect the user - $this->setSessionUserForCurrentRequest(); - - # Confirm the email (it was the authentication token) - $user = User::newFromName( $response->username ); - $user->confirmEmail(); - $user->saveSettings(); - - $this->successfulAction( true ); - $this->getOutput()->redirect( SpecialPage::getSafeTitleFor( 'ChangePassword' )->getFullURL() ); - break; - case AuthenticationResponse::FAIL: - $this->getOutput()->addWikiMsg( 'mgwiki-bad-email-token' ); - $this->getOutput()->returnToMain(); - break; - default: - throw new LogicException( 'invalid AuthenticationResponse' ); - } - #if( $response->status == AuthenticationResponse::PASS ) { - #foreach( $this->authRequests as &$req ) { - # if( $req instanceof MediaWiki\Auth\EmailTokenAuthenticationRequest ) { - # $req->username = $response->username; - # } - #} - #$returnToUrl = $this->getPageTitle( 'return' ) - # ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS ); - #$name = $response->username; - #$id = User::newFromName( $name ); - #$reqCreation = new CreatedAccountAuthenticationRequest( $id, $name ); - #var_dump($this->authRequests); - #$response2 = $authManager->beginAuthentication( $this->authRequests, $returnToUrl ); - /*if( $response2->status == AuthenticationResponse::PASS ) - var_dump('aull right');*/ - # $this->setSessionUserForCurrentRequest(); - #} - #parent::execute( $code ); - - #if ( $code === null || $code === '' ) { - # $this->requireLogin( 'confirmemail_needlogin' ); - #} else { - # $this->attemptConfirm( $code ); - #} - } -} diff --git a/config.schema.json b/config.schema.json deleted file mode 100644 index 19572a1..0000000 --- a/config.schema.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "$schema": "http://json-schema.org/schema#", - "description": "Schema for MGWiki extension configuration parameters.", - "type": "object", - "properties": { - "MGWikiInitialDelayBeforeInactive": { - "type": "number", - "description": "Number of seconds after which an account is considered inactive." - }, - "MGWikiForms": { - "type": "object", - "description": "Registration of the semantic forms: target pages, rights, and how to read the values.", - "additionalProperties": false, - "patternProperties": { - "^[a-zA-Z0-9 ]+$": { - "type": "object", - "description": "Configuration for one specific form.", - "additionalProperties": false, - "required": ["RegexPageName"], - "properties": { - "RegexPageName": { - "type": "string", - "description": "Regular expression (must be understood by PHP, beginning and ending with delimiters) of the target page of the form (see {{{info|page name=}}} in the SemanticForms). Must be normalised with the canonical namespace (in English) if any." - }, - "RequiredRight": { - "type": "string", - "description": "Required right to edit the target page (apart for the user edit her/his own userpage if EditOwnUserpage is true); this right should be registered in \"AvailableRights\" in extension.json. No required right if not provided." - }, - "InstitutionFromModerator": - "type": "boolean", - "description": "Read the property 'institution' defined for the moderator (defined by the property 'moderator') and apply it to created accounts.", - "default": false - }, - "InstitutionFromCreator": - "type": "boolean", - "description": "Read the property 'institution' defined for the account creator and apply it to created accounts.", - "default": false - }, - "EditOwnUserpage": { - "type": "boolean", - "description": "Exceptional right for a user editing her/his own userpage if s/he don’t have the required right.", - "default": false - }, - "SubObjects": { - "type": "boolean", - "description": "Read subobjects in the form to create users accordingly.", - "default": false - }, - "EphemeralPage": { - "type": "boolean", - "description": "Delete the page created just after treatment and redirect to main page.", - "default": false - } - } - } - } - }, - "MGWikiFieldsGroups": { - "type": "object", - "description": "Registration of the mappings between semantic properties and MediaWiki groups: rights, groups names, and how to get the right group given criteria.", - "additionalProperties": false, - "patternProperties": { - "^[a-zA-Z0-9 ]+$": { - "type": "object", - "description": "Name of the semantic property to be synchronised with MediaWiki groups. At least one property between MapFromProperty and MapFromTitle should be provided.", - "additionalProperties": false, - "required": ["Groups"], - "properties": { - "RequiredRight": { - "type": "string", - "description": "Required right to synchronise the the semantic property with MediaWiki groups (apart for the user editing her/his own userpage if EditOwnUserpage is true); without it, and if the user is authorised to edit the page, change this property will have no effect in the MediaWiki groups; this right should be registered in \"AvailableRights\" in extension.json. In addition to this setting, note that for usability matters it would be better to disable the property in SemanticForms itself ({{{field|restricted}}}) to avoid desynchronising data sources. No required right if not provided." - }, - "EditOwnUserpage": { - "type": "boolean", - "description": "Exceptional right for a user to synchronise the semantic property on her/his own userpage with MediaWiki groups; without it, and if the user is authorised to edit the page, change this property will have no effect in the MGWiki extension realm.", - "default": false - }, - "Groups": { - "type": "array", - "description": "MediaWiki groups which are managed by this semantic property." - }, - "MapFromProperty": { - "type": "object", - "description": "Mapping between semantic property values and their corresponding MediaWiki groups.", - "additionalProperties": false, - "patternProperties": { - "^[a-zA-Z0-9]*$": { - "type": "string", - "description": "Name of the MediaWiki group when the semantic property has the value given by the key." - } - } - }, - "MapFromTitle": { - "type": "object", - "description": "Mapping between the target page title and their corresponding MediaWiki groups.", - "additionalProperties": false, - "patternProperties": { - "^[a-zA-Z0-9/$^]+$": { - "type": "string", - "description": "Name of the MediaWiki group when the target page has a title matching the regular expression given by the key." - } - } - } - } - } - } - }, - "MGWikiUserProperties": { - "type": "object", - "description": "Mapping between internal identifiers and real semantic property names.", - "additionalProperties": false, - "required": ["email", "firstname", "lastname", "statutPersonne", "statutAdditionnelPersonne"], - "patternProperties": { - "email": { - "type": "string", - "description": "Name of the email property, must be unique accross all semantic forms else they will be unrecongnised." - }, - "firstname": { - "type": "string", - "description": "Name of the firstname property, must be unique accross all semantic forms else they will be unrecongnised." - }, - "lastname": { - "type": "string", - "description": "Name of the lastname property, must be unique accross all semantic forms else they will be unrecongnised." - }, - "statutPersonne": { - "type": "string", - "description": "Name of the 'statutPersonne' property, must be unique accross all semantic forms else they will be unrecongnised." - }, - "statutAdditionnelPersonne": { - "type": "string", - "description": "Name of the 'statutAdditionnelPersonne' property, must be unique accross all semantic forms else they will be unrecongnised." - } - } - } - } -} diff --git a/extension.json b/extension.json index d258909..e090612 100644 --- a/extension.json +++ b/extension.json @@ -1,266 +1,46 @@ { "@note": "When updating this file please also update MGWiki.php with the same changes.", - "name": "MGWiki", + "name": "MGWikiDev", "version": "0.1", "author": [ - "Sébastien Beyou" + "Sébastien Beyou", + "Alexandre BRULET" ], "url": "https://mgwiki.univ-lyon1.fr", - "descriptionmsg": "mgwiki-desc", + "descriptionmsg": "mgwiki-dev-desc", "license-name": "GPL-3.0+", "type": "other", - "AvailableRights": [ - "mgwikimanagelevel1", - "mgwikimanagelevel2" - ], - "config": { - "MGWikiSecretKeyADEPUL": null, - "MGWikiInitialDelayBeforeInactive": 1209600, - "MGWikiDefaultCreatorNewAccounts": "Alexandre BRULET", - "MGWikiForms": { - "Personne": { - "RegexPageName": "/^User:[^\\/]+$/", - "RequiredRight": "mgwikimanagelevel1", - "EditOwnUserpage": true, - "SubObjects": false, - "Fields": ["Statut personne", "Statut additionnel personne"] - }, - "Nouveaux utilisateurs": { - "RegexPageName": "/^Nouveaux utilisateurs/", - "RequiredRight": "mgwikimanagelevel1", - "InstitutionFromCreator": true, - "SubObjects": true, - "EphemeralPage": true - }, - "Groupe": { - "RegexPageName": "/^(GEP|GAPP)/", - "RequiredRight": "mgwikimanagelevel1", - "InstitutionFromCreator": true, - "SubObjects": true, - "MergeNewUsers": { - "participantTemplate": "membersList" - } - }, - "GEP": { - "RegexPageName": "/^Groupe:GEP/", - "RequiredRight": "mgwikimanagelevel1", - "InstitutionFromCreator": true, - "SubObjects": true, - "MergeNewUsers": { - "participantTemplate": "membersList" - } - }, - "GAPP": { - "RegexPageName": "/^Groupe:GAPP/", - "RequiredRight": "mgwikimanagelevel1", - "InstitutionFromCreator": true, - "SubObjects": true, - "MergeNewUsers": { - "participantTemplate": "membersList" - } - }, - "Stage praticien": { - "RegexPageName": "/^Groupe:Stage praticien/", - "RequiredRight": "mgwikimanagelevel1", - "InstitutionFromCreator": true, - "SubObjects": true, - "MergeNewUsers": { - "participantTemplate": "membersList" - } - }, - "DPC": { - "RegexPageName": "/^Groupe:DPC/", - "RequiredRight": "mgwikimanagelevel1", - "InstitutionFromCreator": true, - "SubObjects": true, - "MergeNewUsers": { - "participantTemplate": "membersList" - } - }, - "Groupe de niveau 2": { - "RegexPageName": "/^(Tuteurs|Modérateurs)/", - "RequiredRight": "mgwikimanagelevel2", - "InstitutionFromCreator": true, - "SubObjects": true, - "MergeNewUsers": { - "participantTemplate": "membersList" - } - }, - "Nouveaux tuteurs ou modérateurs": { - "RegexPageName": "/^Nouveaux tuteurs ou modérateurs/", - "RequiredRight": "mgwikimanagelevel2", - "InstitutionFromCreator": true, - "SubObjects": true, - "EphemeralPage": true - } - }, - "MGWikiFieldsGroups": { - "Statut personne": { - "RequiredRight": "mgwikimanagelevel1", - "EditOwnUserpage": true, - "Groups": ["interne", "médecin", "scientifique"], - "MapFromProperty": { - "Visiteur": "visiteur", - "Interne": "interne", - "Médecin": "médecin", - "Scientifique": "scientifique" - }, - "MapFromTitle": { - "/^GEP/": "interne", - "/^GAPP/": "médecin" - } - }, - "Statut additionnel personne": { - "RequiredRight": "mgwikimanagelevel2", - "Groups": ["", "U2"], - "MapFromProperty": { - "": "", - "Tuteur": "U2", - "Modérateur": "U2", - "Formateur": "U2", - "MSU": "U2" - }, - "MapFromTitle": { - "/^Tuteurs/": "U2", - "/^Modérateurs/": "U2" - } - }, - "Groupe U2": { - "RequiredRight": "mgwikimanagelevel2", - "Groups": ["", "U2"], - "MapFromProperty": { - "": "", - "No": "", - "Non": "", - "1": "U2", - "Yes": "U2", - "Oui": "U2" - } - } - }, - "MGWikiUserProperties": { - "email": "Courriel", - "firstname": "Prénom", - "lastname": "Nom", - "statutPersonne": "Statut personne", - "statutAdditionnelPersonne": "Statut additionnel personne", - "timestamp": "Dernière modification", - "institution": "Institution de rattachement", - "referrer": "Responsable référent", - "moderator": "Tuteur ou modérateur", - "requiredUserUpdate": "Mise à jour requise", - "participantTemplate": "Participant Groupe", - "membersList": "Membres", - "codeAdepul": "Code ADEPUL", - "codeActionAdepul": "Code action" - } - }, - "SpecialPages": { - "Invitation": "Invitation" - }, - "GroupPermissions": { - "interne": {}, - "médecin": {}, - "scientifique": {}, - "U2": { - "mgwikimanagelevel1": true - }, - "sysop": { - "mgwikimanagelevel1": true, - "mgwikimanagelevel2": true - } - }, "MessagesDirs": { - "MGWiki": [ + "MGWikiDev": [ "i18n" ] }, - "ExtensionMessagesFiles": { - "MGWikiAlias": "MGWiki.alias.php", - "MGWikiMagic": "MGWiki.magic.php" - }, "AutoloadClasses": { - "MGWiki": "MGWiki.php", - "MGWikiParserFunctions": "MGWikiParserFunctions.php", - "MGWikiMarguerite": "MGWikiMarguerite.php", - "Invitation": "SpecialInvitation.php", - "ApiNewADEPULGroup": "ApiNewADEPULGroup.php", - "ApiADEPULGroupMember": "ApiADEPULGroupMember.php", - "MediaWiki\\Auth\\EmailTokenAuthenticationRequest": "EmailTokenAuthenticationRequest.php", - "MediaWiki\\Auth\\EmailTokenPrimaryAuthenticationProvider": "EmailTokenPrimaryAuthenticationProvider.php" - }, - "Hooks": { - "userCan": [ - "MGWiki::onuserCan" - ], - "sfHTMLBeforeForm": [ - "MGWiki::onsfHTMLBeforeForm" - ], - "PrefsEmailAudit": [ - "MGWiki::onPrefsEmailAudit" - ], - "SMW::SQLStore::AfterDataUpdateComplete": [ - "MGWiki::onSMW_SQLStore_AfterDataUpdateComplete" - ], - "PageContentSave": [ - "MGWiki::onPageContentSave" - ], - "PostLoginRedirect": [ - "MGWiki::onPostLoginRedirect" - ], - "SpecialPageAfterExecute": [ - "MGWiki::onSpecialPageAfterExecute" - ], - "ParserFirstCallInit": [ - "MGWikiParserFunctions::onParserFirstCallInit" - ], - "BeforePageDisplay": [ - "MGWikiMarguerite::onBeforePageDisplay" - ] + "MGWikiDev": "includes/MGWikiDev.php", + "SpecialAccountRequest": "includes/SpecialAccountRequest.php" }, - "AuthManagerAutoConfig": { - "primaryauth": { - "MediaWiki\\Auth\\EmailTokenPrimaryAuthenticationProvider": { - "class": "MediaWiki\\Auth\\EmailTokenPrimaryAuthenticationProvider", - "args": [ - { - "authoritative": false - } - ], - "sort": 0 - } - } + "SpecialPages": { + "SpecialAccountRequest": "SpecialAccountRequest" }, "ResourceFileModulePaths": { "localBasePath": "resources", - "remoteExtPath": "MGWiki/resources" + "remoteExtPath": "MGWikiDev/resources" }, "ResourceModules": { - "ext.mgwiki": { - "scripts": [ - "ext.mgwiki.js" - ], - "dependencies": [ - "ext.mgwiki.edit" - ] + "ext.mgwiki-dev": { + "packageFiles": [ "mgwiki-dev.js" ], + "styles": [ "mgwiki-dev.css" ] }, - "ext.mgwiki.edit": { - "scripts": [ - "ext.mgwiki.edit.js" - ], - "dependencies": [ - "mediawiki.api", - "mediawiki.Title" - ], - "targets": [ - "desktop", - "mobile" - ] + "ext.mgwiki-dev-specialaccountrequest": { + "packageFiles": [ "specialaccountrequest.js" ], + "styles": [ "specialaccountrequest.css" ] } }, - "APIModules": { - "nouveau-groupe-adepul": "ApiNewADEPULGroup", - "membre-groupe-adepul": "ApiADEPULGroupMember" + "Hooks": { + "BeforePageDisplay": "MGWikiDev::onBeforePageDisplay" + }, + "ExtensionMessagesFiles": { + "MGWikiDevAlias": "includes/MGWikiDev.alias.php" }, "manifest_version": 1 } diff --git a/i18n/en.json b/i18n/en.json index d60a8a4..7fa1a0f 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1,41 +1,14 @@ { "@metadata": { "authors": [ - "Sébastien Beyou" + "Sébastien Beyou", + "Alexandre Brulet" ] }, - "mgwiki-desc": "Specific hooks for MGWiki", - "invitation": "Invitation", - "right-mgwikimanagelevel1": "Manage MGWiki level-1 users (interns, physicians, scientists)", - "right-mgwikimanagelevel2": "Manage MGWiki level-2 users (moderators)", - "group-interne": "Interns", - "group-médecin": "Physicians", - "group-scientifique": "Scientists", - "group-U2": "U2", - "group-interne-member": "{{GENDER:$1|intern}}", - "group-médecin-member": "{{GENDER:$1|physician}}", - "group-scientifique-member": "{{GENDER:$1|scientist}}", - "group-U2-member": "{{GENDER:$1|U2|U2}}", - "grouppage-interne": "{{ns:project}}:Internes", - "grouppage-médecin": "{{ns:project}}:Médecins", - "grouppage-scientifique": "{{ns:project}}:Scientifiques", - "grouppage-U2": "{{ns:project}}:U2", - "authmanager-emailtoken-label": "Email token", - "authmanager-emailtoken-help": "Email token of the user", - "confirmemail_body_created_by_mgwiki": "MGWiki <{{SERVER}}> is a website for medecine students\nand physicians aiming at exchanging their practices and knownledges. A moderator\nfrom MGWiki created this account \"$2\" for you with this email address.\n\nTo log in and confirm this account, open this link in your browser:\n\n{{SERVER}}/Special:Invitation/{{#titleparts:$3||-1}}\n\nThis confirmation code will expire at $4.", - "mgwiki-userpage-without-useraccount": "The userpage exists but not the MediaWiki user account.", - "mgwiki-useraccount-without-userpage": "The user account exists but not the userpage.", - "mgwiki-create-userpage": "Userpage initialization", - "mgwiki-create-grouppage": "Group page initialization", - "mgwiki-template-new-userpage": "{{Personne\n|Titre=\n|Prénom=$2\n|Nom=$3\n|E-mail=$4\n|Date de dernière modification=\n|Statut personne=$5\n|Statut additionnel personne=$6\n|Spécialité ou profession=\n|Institution de rattachement=$7\n|Responsable référent=$8\n|Année de promotion=\n|Année de thèse=\n|Présentation=\n}}\n{{Personne2\n|Rapports et conflits d'intérêts=\n}}", - "mgwiki-bad-email-token": "The initial connection link seems to be broken (bad email token). Please check the link was not truncated by copying and pasting in your browser the whole link from your email.", - "mgwiki-changed-email": "Change email address", - "mgwiki-summary-rewrite-grouppage": "Convert new users to group members", - "mgwiki-ephemeral-page-deleted": "Ephemeral page", - "mgwiki-isusersysop-nousername": "No username given", - "mgwiki-isusersysop-badusername": "Bad username given", - "mgwiki-parserfunction-internalerror": "PHP code internal error", - "mgwiki-wrong-secret-key": "Wrong secret key", - "mgwiki-bad-adepul-group": "Badly recognised ADEPUL group", - "mgwiki-add-member-grouppage": "Add a new member in the group" + "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", + "specialaccountrequest": "Account request", + "specialaccountrequest-title": "Account request", + "specialaccountrequest-intro-lyon": "{{specialaccountrequest-intro-lyon}}", + "specialaccountrequest-intro-adepul": "{{specialaccountrequest-intro-adepul}}", + "specialaccountrequest-intro-autre": "{{specialaccountrequest-intro-autre}}" } diff --git a/i18n/fr.json b/i18n/fr.json index 548e378..220a6ed 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -1,40 +1,14 @@ { "@metadata": { "authors": [ - "Sébastien Beyou" + "Sébastien Beyou", + "Alexandre Brulet" ] }, - "mgwiki-desc": "Accroches spécifiques à MGWiki", - "invitation": "Invitation", - "right-mgwikimanagelevel1": "Gérer les utilisateurs MGWiki de niveau 1 (internes, médecins, scientifiques)", - "right-mgwikimanagelevel2": "Gérer les utilisateurs MGWiki de niveau 2 (tuteurs, modérateurs)", - "group-interne": "Internes", - "group-médecin": "Médecins", - "group-scientifique": "Scientifiques", - "group-U2": "U2", - "group-interne-member": "{{GENDER:$1|interne}}", - "group-médecin-member": "{{GENDER:$1|médecin}}", - "group-scientifique-member": "{{GENDER:$1|scientifique}}", - "group-U2-member": "{{GENDER:$1|U2|U2}}", - "grouppage-interne": "{{ns:project}}:Internes", - "grouppage-médecin": "{{ns:project}}:Médecins", - "grouppage-scientifique": "{{ns:project}}:Scientifiques", - "grouppage-U2": "{{ns:project}}:U2", - "authmanager-emailtoken-label": "Jeton de courriel", - "authmanager-emailtoken-help": "Jeton de courriel de l’utilisateur", - "confirmemail_body_created_by_mgwiki": "MGWiki <{{SERVER}}> est un site permettant aux internes\nen médecine et médecins d’échanger sur leurs pratiques. Un tuteur de MGWiki\nvous a créé un compte « $2 » avec cette adresse de courriel.\n\nPour vous connecter et confirmer ce compte, veuillez suivre ce lien dans votre navigateur :\n\n{{SERVER}}/Special:Invitation/{{#titleparts:$3||-1}}\n\nSi ce vous n’avez pas sollicité de tel compte, n’ouvrez pas ce lien.\n\nCe code de confirmation expirera le $4.", - "mgwiki-userpage-without-useraccount": "La page utilisateur existe, mais pas le compte utilisateur MediaWiki.", - "mgwiki-useraccount-without-userpage": "Le compte utilisateur existe, mais pas la page utilisateur.", - "mgwiki-create-userpage": "Initialisation de la page utilisateur", - "mgwiki-create-grouppage": "Initialisation de la page du groupe", - "mgwiki-template-new-userpage": "{{Personne\n|Titre=\n|Prénom=$2\n|Nom=$3\n|E-mail=$4\n|Date de dernière modification=\n|Statut personne=$5\n|Statut additionnel personne=$6\n|Spécialité ou profession=\n|Institution de rattachement=$7\n|Responsable référent=$8\n|Année de promotion=\n|Année de thèse=\n|Présentation=\n}}\n{{Personne2\n|Rapports et conflits d'intérêts=\n}}", - "mgwiki-bad-email-token": "Le lien de connexion initiale que vous avez suivi semble ne pas être valide (mauvais jeton de courriel).\n\nVeuillez vérifier que le lien n’a pas été tronqué en copiant et collant dans votre navigateur l’ensemble du lien du courriel.", - "mgwiki-changed-email": "Changement d’adresse de courriel", - "mgwiki-summary-rewrite-grouppage": "Conversion des nouveaux utilisateurs en membres du groupe", - "mgwiki-ephemeral-page-deleted": "Page éphémère", - "mgwiki-isusersysop-nousername": "Pas de nom d’utilisateur indiqué", - "mgwiki-isusersysop-badusername": "Mauvais nom d’utilisateur", - "mgwiki-parserfunction-internalerror": "Erreur interne au code PHP", - "mgwiki-bad-adepul-group": "Groupe ADEPUL mal reconnu", - "mgwiki-add-member-grouppage": "Ajout d’un nouveau membre au groupe" + "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", + "specialaccountrequest": "Demande de création de compte", + "specialaccountrequest-title": "Demande de création de compte", + "specialaccountrequest-intro-lyon": "{{specialaccountrequest-intro-lyon}}", + "specialaccountrequest-intro-adepul": "{{specialaccountrequest-intro-adepul}}", + "specialaccountrequest-intro-autre": "{{specialaccountrequest-intro-autre}}" } diff --git a/i18n/qqq.json b/i18n/qqq.json index 9539105..7fa1a0f 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -1,39 +1,14 @@ { "@metadata": { "authors": [ - "Sébastien Beyou" + "Sébastien Beyou", + "Alexandre Brulet" ] }, - "mgwiki-desc": "{{desc|name=MGWiki|url=https://mgwiki.univ-lyon1.fr}}", - "invitation": "Title of the [[Special:Invitation]] special page", - "right-mgwikimanagelevel1": "{{doc-right|right-mgwikimanagelevel1}}Right to manage basic users", - "right-mgwikimanagelevel2": "{{doc-right|right-mgwikimanagelevel2}}Right to manage advanced users (moderators)", - "group-interne": "{{doc-group|interne}}", - "group-médecin": "{{doc-group|médecin}}", - "group-scientifique": "{{doc-group|scientifique}}", - "group-U2": "{{doc-group|U2}}", - "group-interne-member": "{{doc-group|interne|member}}", - "group-médecin-member": "{{doc-group|médecin|member}}", - "group-scientifique-member": "{{doc-group|scientifique|member}}", - "group-U2-member": "{{doc-group|U2|member}}", - "grouppage-interne": "{{doc-group|interne|page}}", - "grouppage-médecin": "{{doc-group|médecin|page}}", - "grouppage-scientifique": "{{doc-group|scientifique|page}}", - "grouppage-U2": "{{doc-group|U2|page}}", - "authmanager-emailtoken-label": "Jeton de courriel", - "authmanager-emailtoken-help": "Jeton de courriel de l’utilisateur", - "confirmemail_body_created_by_mgwiki": "Body of the email sent to user when their account is created by a MGWiki moderator.\n\nParameters:\n* $1 - the IP address of the user that created or confirmed the email address\n* $2 - the name of the user\n* $3 - a URL to [[Special:ConfirmEmail]]\n* $4 - a time and date (duplicated by $6 and $7)\n* $5 - a URL to [[Special:InvalidateEmail]]\n* $6 - (Optional) a date\n* $7 - (Optional) a time\n{{Related|Confirmemail body}}", - "mgwiki-userpage-without-useraccount": "Warning message on top of a form", - "mgwiki-useraccount-without-userpage": "Warning message on top of a form", - "mgwiki-create-userpage": "Summary when a userpage is created", - "mgwiki-template-new-userpage": "Wikitext template when a userpage is initialised.\nParameters:\n* $1 - username\n* $2 - first name\n* $3 - family name\n* $4 - email\n* $5 - status (interne/médecin/scientifique)\n* $6 additional status (tuteur/modérateur)\n* $7 Institution\n* $8 Referrer", - "mgwiki-bad-email-token": "Warning message when a user has a bad token when trying to connect with", - "mgwiki-summary-rewrite-grouppage": "Summary when group page is edited to convert new users to group members", - "mgwiki-ephemeral-page-deleted": "Log message when an ephemeral page is deleted", - "mgwiki-isusersysop-nousername": "Error message for #isusersysop", - "mgwiki-isusersysop-badusername": "Error message for #isusersysop", - "mgwiki-parserfunction-internalerror": "Error message for parser functions", - "mgwiki-wrong-secret-key": "Error message in API", - "mgwiki-bad-adepul-group": "Error message in API", - "mgwiki-add-member-grouppage": "Summary" + "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", + "specialaccountrequest": "Account request", + "specialaccountrequest-title": "Account request", + "specialaccountrequest-intro-lyon": "{{specialaccountrequest-intro-lyon}}", + "specialaccountrequest-intro-adepul": "{{specialaccountrequest-intro-adepul}}", + "specialaccountrequest-intro-autre": "{{specialaccountrequest-intro-autre}}" } diff --git a/MGWiki.alias.php b/includes/MGWikiDev.alias.php similarity index 62% rename from MGWiki.alias.php rename to includes/MGWikiDev.alias.php index 8aa16c0..31a6b9f 100644 --- a/MGWiki.alias.php +++ b/includes/MGWikiDev.alias.php @@ -11,10 +11,10 @@ /** English (English) */ $specialPageAliases['en'] = array( - 'Invitation' => array( 'Invitation' ), + 'SpecialAccountRequest' => array( 'Account Request' ), ); /** French (français) */ -$specialPageAliases['ff'] = array( - 'Invitation' => array( 'Invitation' ), +$specialPageAliases['fr'] = array( + 'SpecialAccountRequest' => array( 'Demande de création de compte' ), ); diff --git a/includes/MGWikiDev.php b/includes/MGWikiDev.php new file mode 100644 index 0000000..b0a3e40 --- /dev/null +++ b/includes/MGWikiDev.php @@ -0,0 +1,21 @@ + + * @author Alexandre Brulet + * @license GPL-3.0+ + * @package MediaWiki-extension-MGWiki + */ + +class MGWikiDev { + + /** + * Divers + */ + public static function onBeforePageDisplay( OutputPage $out, Skin $skin ) { + //Modules pour toutes les pages + $out->addModules('ext.mgwiki-dev'); + } +} diff --git a/includes/SpecialAccountRequest.php b/includes/SpecialAccountRequest.php new file mode 100644 index 0000000..bd75655 --- /dev/null +++ b/includes/SpecialAccountRequest.php @@ -0,0 +1,99 @@ +getOutput(); + $out->addModules('ext.mgwiki-dev-specialaccountrequest'); + $out->setPageTitle( $this->msg( 'specialaccountrequest-title' ) ); + + $form1 = ' +
+ + + + +
Institution de rattachement +
+
+ +
'; + $form2 = ' + + + + + + + +
+ +
+

Commentaires
+ +

+
+ + +
'; + + + $mess = '

Votre demande a bien été envoyée à l\'administrateur du site.

+ '; + + if (isset($_POST['accountRequest'])){ + $mailer = new \UserMailer(); + $mail_to = new \MailAddress($wgEmergencyContact); + $mail_from = new \MailAddress($_POST['email']); + $year = isset($_POST['year']) ? $_POST['year'] : "null"; + $comment = isset($_POST['comment']) ? $_POST['comment'] : "null"; + $body =' +Demande de création de compte MGWiki + +Date: ' . date('Y-m-d H:i:s') . ' +Institution: ' . $_POST['institution'] . ' +Nom: ' . ucfirst(strtolower($_POST['prenom'])) . ' ' . strtoupper($_POST['nom']) . ' +Formateur/Formation: ' . $_POST['formateur'] . ' +Année: ' . $year . ' +Commentaires: ' . $comment . ' +Email: ' . $_POST['email'] ; + + $mailer->send( array($mail_to), $mail_from, 'Demande de création de compte', $body ); + $out->addHTML($mess); + } + else { + $out->addHTML($form1); + $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-lyon' )); + $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-adepul' )); + $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-autre' )); + $out->addHTML($form2); + } + } + + /** + * groupe auquel se rapporte cette page spéciale + */ + protected function getGroupName() { + return 'mgwiki'; + } +} diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index d81a292..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - . - - - vendor - diff --git a/resources/ext.mgwiki.edit.js b/resources/ext.mgwiki.edit.js deleted file mode 100644 index 0b81c2c..0000000 --- a/resources/ext.mgwiki.edit.js +++ /dev/null @@ -1,196 +0,0 @@ -/** - * @class mw.Api.plugin.edit - * - * This is directly backported from MediaWiki 1.28, only the names were changed - * to avoid future naming conflicts. - */ -( function ( mw, $ ) { - - $.extend( mw.Api.prototype, { - - /** - * Post to API with csrf token. If we have no token, get one and try to post. - * If we have a cached token try using that, and if it fails, blank out the - * cached token and start over. - * - * @param {Object} params API parameters - * @param {Object} [ajaxOptions] - * @return {jQuery.Promise} See #post - */ - mgwikiPostWithEditToken: function ( params, ajaxOptions ) { - return this.postWithToken( 'csrf', params, ajaxOptions ); - }, - - /** - * API helper to grab a csrf token. - * - * @return {jQuery.Promise} Received token. - */ - mgwikiGetEditToken: function () { - return this.getToken( 'csrf' ); - }, - - /** - * Create a new page. - * - * Example: - * - * new mw.Api().create( 'Sandbox', - * { summary: 'Load sand particles.' }, - * 'Sand.' - * ); - * - * @since 1.28 - * @param {mw.Title|string} title Page title - * @param {Object} params Edit API parameters - * @param {string} params.summary Edit summary - * @param {string} content - * @return {jQuery.Promise} API response - */ - mgwikiCreate: function ( title, params, content ) { - return this.mgwikiPostWithEditToken( $.extend( { - action: 'edit', - title: String( title ), - text: content, - formatversion: '2', - - // Protect against errors and conflicts - assert: mw.user.isAnon() ? undefined : 'user', - createonly: true - }, params ) ).then( function ( data ) { - return data.edit; - } ); - }, - - /** - * Edit an existing page. - * - * To create a new page, use #create() instead. - * - * Simple transformation: - * - * new mw.Api() - * .edit( 'Sandbox', function ( revision ) { - * return revision.content.replace( 'foo', 'bar' ); - * } ) - * .then( function () { - * console.log( 'Saved! '); - * } ); - * - * Set save parameters by returning an object instead of a string: - * - * new mw.Api().edit( - * 'Sandbox', - * function ( revision ) { - * return { - * text: revision.content.replace( 'foo', 'bar' ), - * summary: 'Replace "foo" with "bar".', - * assert: 'bot', - * minor: true - * }; - * } - * ) - * .then( function () { - * console.log( 'Saved! '); - * } ); - * - * Transform asynchronously by returning a promise. - * - * new mw.Api() - * .edit( 'Sandbox', function ( revision ) { - * return Spelling - * .corrections( revision.content ) - * .then( function ( report ) { - * return { - * text: report.output, - * summary: report.changelog - * }; - * } ); - * } ) - * .then( function () { - * console.log( 'Saved! '); - * } ); - * - * @since 1.28 - * @param {mw.Title|string} title Page title - * @param {Function} transform Callback that prepares the edit - * @param {Object} transform.revision Current revision - * @param {string} transform.revision.content Current revision content - * @param {string|Object|jQuery.Promise} transform.return New content, object with edit - * API parameters, or promise providing one of those. - * @return {jQuery.Promise} Edit API response - */ - mgwikiEdit: function ( title, transform ) { - var basetimestamp, curtimestamp, - api = this; - return api.get( { - action: 'query', - prop: 'revisions', - rvprop: [ 'content', 'timestamp' ], - titles: String( title ), - formatversion: '2', - curtimestamp: true - } ) - .then( function ( data ) { - var page, revision; - if ( !data.query || !data.query.pages ) { - return $.Deferred().reject( 'unknown' ); - } - page = data.query.pages[ 0 ]; - if ( !page || page.missing ) { - return $.Deferred().reject( 'nocreate-missing' ); - } - revision = page.revisions[ 0 ]; - basetimestamp = revision.timestamp; - curtimestamp = data.curtimestamp; - return transform( { - timestamp: revision.timestamp, - content: revision.content - } ); - } ) - .then( function ( params ) { - var editParams = typeof params === 'object' ? params : { text: String( params ) }; - return api.mgwikiPostWithEditToken( $.extend( { - action: 'edit', - title: title, - formatversion: '2', - - // Protect against errors and conflicts - assert: mw.user.isAnon() ? undefined : 'user', - basetimestamp: basetimestamp, - starttimestamp: curtimestamp, - nocreate: true - }, editParams ) ); - } ) - .then( function ( data ) { - return data.edit; - } ); - }, - - /** - * Post a new section to the page. - * - * @see #postWithEditToken - * @param {mw.Title|string} title Target page - * @param {string} header - * @param {string} message wikitext message - * @param {Object} [additionalParams] Additional API parameters, e.g. `{ redirect: true }` - * @return {jQuery.Promise} - */ - mgwikiNewSection: function ( title, header, message, additionalParams ) { - return this.mgwikiPostWithEditToken( $.extend( { - action: 'edit', - section: 'new', - title: String( title ), - summary: header, - text: message - }, additionalParams ) ); - } - } ); - - /** - * @class mw.Api - * @mixins mw.Api.plugin.edit - */ - -}( mediaWiki, jQuery ) ); diff --git a/resources/ext.mgwiki.js b/resources/ext.mgwiki.js deleted file mode 100644 index fe33790..0000000 --- a/resources/ext.mgwiki.js +++ /dev/null @@ -1,97 +0,0 @@ -( function ( mw, $ ) { - - /** - * Modify in background the competency on the current page. - * - * The edition is done by the current user without edit summary. - * The competence can be any string followed by '=Oui' or '=Non' - * (boolean values in a template). If no specific value is given - * in the second argument, the boolean value is toggled. - * - * @param {string} Competence name - * @param {string|boolean} [Value] - * @return void - */ - mw.mgwikiModifyCompetence = function ( name, value ) { - - new mw.Api() - .mgwikiEdit( mw.config.get( 'wgPageName' ), function ( revision ) { - if ( !revision.content.match( new RegExp( '\\| *' + name + ' *= *([Oo]ui|[Nn]on)' ) ) ) { - return revision.content; - } - if ( value === undefined ) { - value = 'Oui'; - if ( revision.content.match( new RegExp( '\\| *' + name + ' *= *[Oo]ui' ) ) ) { - value = 'Non'; - } - } else if ( value == 'Non' || value == 'non' || !value ) { - value = 'Non'; - } else { - value = 'Oui'; - } - return revision.content.replace( - new RegExp( '\\| *' + name + ' *= *([Oo]ui|[Nn]on)' ), - '|' + name + '=' + value ); - } ); - } - - /** - * On some images, add clickable areas editing the page. - * - * More specifically, the image must be included in a

, - * and this div must also contain a description of the clickable areas redacted in - * HTML with (only and are whitelisted - * HTML tags); the must have a dedicated attribute data-mgwiki-competence with - * "Compétence" or "Compétence=Oui" or "Compétence=Non" inside. When the use clicks on - * some area, s/he edits the page in background, replacing the competence on the page - * (specifically, "|" + name + "=Oui" (or "=Non") according to the data-mgwiki-competence - * attribute ("Compétence" alone toggles the boolean value). - */ - $( function () { - - if( !mw.config.get( 'wgIsArticle' ) || mw.config.get( 'wgAction' ) != 'view' || $( '#mw-content-text table.diff' ).length > 0 ) { - return; - } - - $( '.mgwiki-marguerite' ).each( function () { - - var map = $( '.map', $(this) ), - maphtml = map.text() ? - map.text() : - map.html() - .replace( new RegExp( '' ), '' ) - .replace( new RegExp( '<' ), '<' ) - .replace( new RegExp( '>' ), '>' ), - mapname = maphtml.match( new RegExp( '' ) ) ? - maphtml.match( new RegExp( '' ) )[1] : - null; - - // Verify all is in order and protect against unauthorised HTML, only and are whitelisted - if ( !maphtml || !mapname || maphtml.replace( new RegExp( ']', 'g' ), '' ).match( new RegExp( ']' ) ) ) { - return; - } - - // Add the definition and activate it on the first image - $(this).append( maphtml ); - $( 'img', $(this) ).first().attr( 'usemap', '#' + mapname ); - - // Add click events on - $( 'area', $(this) ).css( 'cursor', 'pointer' ).click( function() { - var competencedata = $(this).attr( 'data-mgwiki-competence' ), - competence = competencedata ? - competencedata.match( new RegExp( '^([a-zA-ZéèêëáöôíîïÉÈÊËÁÖÔÍÎÏ0-9]+)(=([Oo]ui|[Nn]on))?$' ) ) : - null; - if( !competence ) { - return false; - } - if( competence[3] ) { - mw.mgwikiModifyCompetence( competence[1], competence[3] ); - } else { - mw.mgwikiModifyCompetence( competence[1] ); - } - return false; - } ); - } ); - } ); - -}( mediaWiki, jQuery ) ); diff --git a/resources/mgwiki-dev.css b/resources/mgwiki-dev.css new file mode 100644 index 0000000..fa7e7e7 --- /dev/null +++ b/resources/mgwiki-dev.css @@ -0,0 +1,30 @@ + +/***** MGW CUSTOM STYLES *****/ +.mgw-toggle-show { + color:white; + cursor:pointer; + float: right; + background: #447ff5; + border-radius: 3px; + padding-left:5px; + padding-right:5px; +} + +.mgw-toggle-hide { + color:white; + cursor:pointer; + float: right; + background: #447ff5; + border-radius: 3px 3px 0 0; + padding-left:5px; + padding-right:5px; +} + +.mgw-toggle-content { + color:black; + border: 1px solid #447ff5; + background: white; + padding:8px; + float: right; + border-radius: 5px 0 5px 5px; +} diff --git a/resources/mgwiki-dev.js b/resources/mgwiki-dev.js new file mode 100644 index 0000000..58fbde3 --- /dev/null +++ b/resources/mgwiki-dev.js @@ -0,0 +1,42 @@ + +/** + * MGW customization + */ + + mw.mgwImgTooltip(); + $("#mgw-toggle-createUserSubPage-icon").html(' ▼ '); + $("#mgw-toggle-createUserSubPage").attr("onclick","mw.mgwToggleCreateUserSubPage()"); + +( function ( mw, $ ) { + //logo aide: + //override image link tooltip + mw.mgwImgTooltip = function () { + $(".mgw-help-img").each(function () { + title = $( this ).attr('title'); + $( this ).children("a").attr('title',title); + }); + } + + mw.mgwToggleCreateUserSubPage = function () { + $icon = $("#mgw-toggle-createUserSubPage-icon"); + $div = $("#mgw-toggle-createUserSubPage"); + if ($icon.attr("class") == "mgw-show"){ + $div.after('\ +
\ +
\ + \ + \ +
\ +
'); + $icon.html(' ▲ '); + $icon.attr('class','mgw-hide'); + $div.attr('class','mgw-toggle-hide'); + } + else { + $icon.html(' ▼ '); + $('.mgw-toggle-content').remove(); + $icon.attr('class','mgw-show'); + $div.attr('class','mgw-toggle-show'); + } + } +}( mediaWiki, jQuery ) ); diff --git a/resources/specialaccountrequest.css b/resources/specialaccountrequest.css new file mode 100644 index 0000000..ed35e0c --- /dev/null +++ b/resources/specialaccountrequest.css @@ -0,0 +1,20 @@ +/* special account request styles */ +#institution-label{ + vertical-align: top; +} +.mgw-accountrequest-intro{ + display:none; +} +.mgw-hidden{ + display:none; +} + +/* +.mgw-tr-formateur{ + display: none; +} + +.mgw-tr-year{ + display: none; +} +*/ diff --git a/resources/specialaccountrequest.js b/resources/specialaccountrequest.js new file mode 100644 index 0000000..c0b7e07 --- /dev/null +++ b/resources/specialaccountrequest.js @@ -0,0 +1,52 @@ + + +( function ( mw, $ ) { + mw.accountRequest = function(){ + if ($('input[type=radio][name=institution]:checked').attr('value') == 'lyon1') { + $('.mgw-hidden').show(); + $('span[id=intro-lyon]').show(); + $('span[id=intro-adepul]').hide(); + $('span[id=intro-autre]').hide(); + $('td[class=mgw-tr-formateur]').text('Nom de votre tuteur'); + $('td[class=mgw-tr-year]').text('Année de promotion'); + $('.mgw-tr-formateur').show(); + $('.mgw-tr-formateur').attr("required","true") + $('.mgw-tr-year').show(); + $('.mgw-tr-year').attr("required","true") + $('#mgw-textarea-comment').removeAttr("required"); + } + if ($('input[type=radio][name=institution]:checked').attr('value') == 'adepul') { + $('.mgw-hidden').show(); + $('span[id=intro-lyon]').hide(); + $('span[id=intro-adepul]').show(); + $('span[id=intro-autre]').hide(); + $('td[class=mgw-tr-formateur]').text('Nom de votre formation'); + $('.mgw-tr-formateur').show(); + $('.mgw-tr-formateur').attr("required","true"); + $('.mgw-tr-year').hide(); + $('.mgw-tr-year').removeAttr("required"); + $('#mgw-p-comment-intro a').hide(); + $('#mgw-textarea-comment').attr("required"); + } + if ($('input[type=radio][name=institution]:checked').attr('value') == 'autre') { + $('.mgw-hidden').show(); + $('span[id=intro-lyon]').hide(); + $('span[id=intro-adepul]').hide(); + $('span[id=intro-autre]').show(); + $('.mgw-tr-formateur').hide(); + $('.mgw-tr-formateur').removeAttr("required"); + $('.mgw-tr-year').hide(); + $('.mgw-tr-year').removeAttr("required"); + $('#mgw-p-comment-intro a').show(); + $('#mgw-textarea-comment').attr("required", true); + } + } + + mw.mgwHome = function(){ + $(location).attr("href", "https://mgwiki.univ-lyon1.fr"); + } + + $( function () { + // code à faire d'emblée (...) + }); +}( mediaWiki, jQuery ) ); From 5d7f7ad1f1a3fcb73804464fa647e4ac5722e209 Mon Sep 17 00:00:00 2001 From: Looxloox Date: Fri, 23 Oct 2020 13:12:06 +0200 Subject: [PATCH 05/15] README update --- README.md | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 872d68e..fdc6d39 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,7 @@ -[English below] - -Personnalisations pour MGWiki -============================= +Extension MGWiki - module de développement +========================================== Cette extension permet de personnaliser le site [MGWiki](https://mgwiki.univ-lyon1.fr), wiki privé dédié au partage d’expérience entre internes en médecine et médecins. -Les personnalisations concernent : - -* la gestion des droits entre différents niveaux d’utilisateurs ; -* la création d’utilisateurs au moyen de formulaires Semantic MediaWiki ; -* l’envoi d’une invitation aux nouveaux inscrits, invitation qui les connecte directement et les redirigent vers le formulaire de leur profil utilisateur ; -* la synchronisation entre les formulaires des profils utilisateur et MediaWiki (groupes utilisateur et adresse de courriel). - -Cette extension (et son versant Semantic MediaWiki) a été réalisée par l’entreprise [Wiki Valley](http://wiki-valley.com) pour MGWiki, et est publiée sous licence GPL-3.0+. Il est peu probable qu’elle serve directement à d’autres réutilisateurs, étant très spécialisée, mais elle est publiée dans l’espoir que certaines parties puissent aider d’autres développeurs ayant des besoins similaires. - -MGWiki customisations -===================== - -This extension customise the website [MGWiki](https://mgwiki.univ-lyon1.fr), private wiki dedicated to experience sharing between medecine interns and physicians. - -These customisations are: - -* the rights management between different user levels; -* the creation of users by means of Semantic MediaWiki forms; -* the sending of an invitation to new users, which logs them directly in and redirects them to the user profile form; -* the synchronisation between the user profile forms and MediaWiki (user groups and email address). - -This extension (and its counterpart Semantic MediaWiki) was realised by the company [Wiki Valley](http://wiki-valley.com) for MGWiki, and is published under the GPL-3.0+ licence. It is unlikely it will be directely reused by other people, being very specialised, but it is published in the hope some parts can help other developers with similar needs. +Modules inclus: +* SpecialAccountRequest (MàJ 23/10/2020) From df2d8655dda1d5be659df8077d91d24ec14d6100 Mon Sep 17 00:00:00 2001 From: Looxloox Date: Fri, 23 Oct 2020 23:00:10 +0200 Subject: [PATCH 06/15] =?UTF-8?q?accountRequest=20version=20s=C3=A9curis?= =?UTF-8?q?=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/SpecialAccountRequest.php | 197 ++++++++++++++++++++-------- includes/captcha.php | 34 +++++ resources/specialaccountrequest.css | 16 +-- resources/specialaccountrequest.js | 6 +- 4 files changed, 183 insertions(+), 70 deletions(-) create mode 100644 includes/captcha.php diff --git a/includes/SpecialAccountRequest.php b/includes/SpecialAccountRequest.php index bd75655..6cdf550 100644 --- a/includes/SpecialAccountRequest.php +++ b/includes/SpecialAccountRequest.php @@ -6,6 +6,10 @@ class SpecialAccountRequest extends \SpecialPage { + private $form; + private $mess = ""; + private $done = false; + /** * Initialize the special page. */ @@ -14,80 +18,161 @@ public function __construct() { } /** - * Shows the page to the user. - * @param string $sub The subpage string argument (if any). - * [[Special:HelloWorld/subpage]]. + * Form, captcha and $_POST checks. + * HTML content definition. */ - public function execute( $sub ) { + protected function beforeExecute( $subPage ) { global $_SERVER; global $_POST; + global $_COOKIE; global $wgEmergencyContact; + include ('captcha.php'); + $post1 = (isset($_POST['accountRequest'])) ? true :false; + $post2 = (isset($_POST['captchaResponse'])) ? true :false; - $out = $this->getOutput(); - $out->addModules('ext.mgwiki-dev-specialaccountrequest'); - $out->setPageTitle( $this->msg( 'specialaccountrequest-title' ) ); + //récupération des variables si elles existent + if ($post1){ + $post_lyon1 = ($_POST['institution'] == 'lyon1')? "checked":""; + $post_adepul = ($_POST['institution'] == 'adepul')? "checked":""; + $post_autre = ($_POST['institution'] == 'autre')? "checked":""; + $post_nom = htmlspecialchars($_POST['nom']); + $post_prenom = htmlspecialchars($_POST['prenom']); + $post_email = htmlspecialchars($_POST['email']); + $post_formateur = htmlspecialchars($_POST['formateur']); + $post_year = htmlspecialchars($_POST['year']); + $post_comment = htmlspecialchars($_POST['comment']); + } + else { + $post_lyon1 = ""; + $post_adepul = ""; + $post_autre = ""; + $post_nom = ""; + $post_prenom = ""; + $post_email = ""; + $post_formateur = ""; + $post_year = ""; + $post_comment = ""; + } - $form1 = ' -
- - - - -
Institution de rattachement -
-
- -
'; - $form2 = ' - - - - + $cookieLabel = str_replace(array('=',',',';','\t','\r','\n','\013','\014'),'','mgw_'.$post_nom.$post_prenom); + //vérification du captcha, envoi du mail si ok + if ($post2){ + if (isset($_COOKIE[$cookieLabel])){ + $this->mess = ' +

Votre demande déjà envoyée.

+ '; + $this->done = true; + } + elseif (strtolower(str_replace('.',',',$_POST['captchaResponse'])) == $captcha[$_POST['captchaKey']]) { + $mailer = new \UserMailer(); + $mail_to = new \MailAddress($wgEmergencyContact); + $mail_from = new \MailAddress($post_email); + $body =' +Demande de création de compte MGWiki -

- -
+Date: ' . date('Y-m-d H:i:s') . ' +Institution: ' . $_POST['institution'] . ' +Nom: ' . ucfirst(strtolower($post_prenom)) . ' ' . strtoupper($post_nom) . ' +Formateur/Formation: ' . $post_formateur . ' +Année: ' . $post_year . ' +Commentaires: ' . $post_comment . ' +Email: ' . $post_email ; -
-

Commentaires
- -

-
+ $mailer->send( array($mail_to), $mail_from, 'Demande de création de compte', $body ); + setcookie($cookieLabel,"sent", time()+3600); + $this->mess = ' +

Votre demande a bien été envoyée à l\'administrateur du site.

+ '; + $this->done = true; + } + else { + $count = 0; + if (isset($_COOKIE['mgw_accountRequestBlocked'])){ + $count = $_COOKIE['mgw_accountRequestBlocked']; + } + if ($count > 4){ + $this->mess = ' +

Vous avez dépassé le nombre d\'essais autorisés.

+ '; + $this->done = true; + } + else { + $count += 1; + $this->mess = ' +
+ + Il y a une erreur... si vous n\'êtes pas un robot, merci de recommencer ! + '; + setcookie('mgw_accountRequestBlocked',$count, time()+60*60); + } + } + } - -

'; + // affichage du formulaire + if (!$this->done) { + $this->form[1] = ' +
+ + + + +
Institution de rattachement:    +
+
+ +
'; + $this->form[2] = ' + '; + if ($post1){ + $this->form[2] .= ' +
'.$key.'
+ Je ne suis pas un robot: + + +
'; + } + $this->form[2] .= ' + + + + + + - $mess = '

Votre demande a bien été envoyée à l\'administrateur du site.

- '; +

+ +
'; + } + return true; + } - if (isset($_POST['accountRequest'])){ - $mailer = new \UserMailer(); - $mail_to = new \MailAddress($wgEmergencyContact); - $mail_from = new \MailAddress($_POST['email']); - $year = isset($_POST['year']) ? $_POST['year'] : "null"; - $comment = isset($_POST['comment']) ? $_POST['comment'] : "null"; - $body =' -Demande de création de compte MGWiki + /** + * Shows the page to the user. + * @param string $sub The subpage string argument (if any). + * [[Special:HelloWorld/subpage]]. + */ + public function execute( $sub ) { -Date: ' . date('Y-m-d H:i:s') . ' -Institution: ' . $_POST['institution'] . ' -Nom: ' . ucfirst(strtolower($_POST['prenom'])) . ' ' . strtoupper($_POST['nom']) . ' -Formateur/Formation: ' . $_POST['formateur'] . ' -Année: ' . $year . ' -Commentaires: ' . $comment . ' -Email: ' . $_POST['email'] ; + $out = $this->getOutput(); + $out->addModules('ext.mgwiki-dev-specialaccountrequest'); + $out->setPageTitle( $this->msg( 'specialaccountrequest-title' ) ); - $mailer->send( array($mail_to), $mail_from, 'Demande de création de compte', $body ); - $out->addHTML($mess); - } - else { - $out->addHTML($form1); + if (!$this->done){ + $out->addHTML($this->form[1]); $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-lyon' )); $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-adepul' )); $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-autre' )); - $out->addHTML($form2); + $out->addHTML($this->mess); + $out->addHTML($this->form[2]); } + else { + $out->addHTML($this->mess); + } } /** diff --git a/includes/captcha.php b/includes/captcha.php new file mode 100644 index 0000000..2df750d --- /dev/null +++ b/includes/captcha.php @@ -0,0 +1,34 @@ +"6", + "Combien de pis a une vache ?"=> "4", + "Quelle est l'année de la prise de la Bastille ?"=> "1789", + "Combien de lettres comporte 'toutes' au singulier ?"=> "5", + "Combien de mots au pluriel comporte cette phrase ?"=> "1", + "Combien de pattes a une fourmi ?" => "6", + "Combien font sept fois six moins deux"=> "40", + "Combien de mots au singulier comporte cette phrase ?"=> "7", + "Combien de mots finissant par ' e ' comporte cette phrase ?"=> "4", + "Combien de mots finissant par ' t ' comporte cette phrase ?"=> "1", + "Combien de mots commencant par ' c ' comporte cette phrase ?"=> "4", + "Combien vaut pi deux chiffres après la virgule ?"=> "3,14", + "Ecrivez 'forme' en remplaçant le ' m ' par un ' t '"=> "forte", + "Ecrivez 'porte' en remplaçant ' p ' par ' f '"=> "forte", + "Ecrivez 'morne' en mettant ' b ' à la place de ' m '"=> "borne", + "Ecrivez 'sortez' à l'infinitif"=> "sortir", + "Conjuguez 'former' à la troisième personne du singulier pluriel"=> "forment", + "Mangez combien de fruits et légumes par jour ? "=> "5", + "Huit fois deux moins seize ?"=> "0", + "Neuf divisé par trois moins quatre ?"=> "-1", + "Quelle est la couleur du couvercle noir du bidon bleu ?"=>"noir", + "Combien de lettres comporte médecine ?" => "8", + "Combien font deux et deux et deux ?" => "6", + "Combien fait trois puissance deux ?" => "9", + "Quelle lettre est en double dans le deuxième mot de cette phrase ?"=>"t", + "Quel est le dernier mot de cette phrase ?"=>"phrase", + "Quelle couleur fait jaune plus rouge ?"=>"orange", + "Quelle couleur fait bleu plus jaune ?"=>"vert", + "Quelle couleur faut-il mélanger à rouge pour faire du violet ?"=>"bleu", + "Ecrivez ' rien ' ."=>"rien" +); +$key = array_rand($captcha); diff --git a/resources/specialaccountrequest.css b/resources/specialaccountrequest.css index ed35e0c..68c715f 100644 --- a/resources/specialaccountrequest.css +++ b/resources/specialaccountrequest.css @@ -1,20 +1,16 @@ /* special account request styles */ +/* #institution-label{ vertical-align: top; -} +}*/ .mgw-accountrequest-intro{ display:none; } .mgw-hidden{ display:none; } - -/* -.mgw-tr-formateur{ - display: none; -} - -.mgw-tr-year{ - display: none; +#captcha{ + border: 1px solid red; + border-radius: 10px; + background:#f1e8e8; } -*/ diff --git a/resources/specialaccountrequest.js b/resources/specialaccountrequest.js index c0b7e07..4f79348 100644 --- a/resources/specialaccountrequest.js +++ b/resources/specialaccountrequest.js @@ -25,8 +25,7 @@ $('.mgw-tr-formateur').attr("required","true"); $('.mgw-tr-year').hide(); $('.mgw-tr-year').removeAttr("required"); - $('#mgw-p-comment-intro a').hide(); - $('#mgw-textarea-comment').attr("required"); + $('#mgw-textarea-comment').attr("required",true); } if ($('input[type=radio][name=institution]:checked').attr('value') == 'autre') { $('.mgw-hidden').show(); @@ -37,7 +36,6 @@ $('.mgw-tr-formateur').removeAttr("required"); $('.mgw-tr-year').hide(); $('.mgw-tr-year').removeAttr("required"); - $('#mgw-p-comment-intro a').show(); $('#mgw-textarea-comment').attr("required", true); } } @@ -47,6 +45,6 @@ } $( function () { - // code à faire d'emblée (...) + if ($('input[type=radio]:checked')){mw.accountRequest();} }); }( mediaWiki, jQuery ) ); From 168ee8a059f954b15afce9a69a2ba94979f1ef0b Mon Sep 17 00:00:00 2001 From: Looxloox Date: Sat, 24 Oct 2020 00:15:02 +0200 Subject: [PATCH 07/15] accountRequest securisation V2 --- extension-dev.json | 46 ++++++++++++++++++++++++++++++ extension.json | 8 ------ includes/SpecialAccountRequest.php | 26 +++++++++++------ includes/captcha.php | 3 +- 4 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 extension-dev.json diff --git a/extension-dev.json b/extension-dev.json new file mode 100644 index 0000000..e090612 --- /dev/null +++ b/extension-dev.json @@ -0,0 +1,46 @@ +{ + "@note": "When updating this file please also update MGWiki.php with the same changes.", + "name": "MGWikiDev", + "version": "0.1", + "author": [ + "Sébastien Beyou", + "Alexandre BRULET" + ], + "url": "https://mgwiki.univ-lyon1.fr", + "descriptionmsg": "mgwiki-dev-desc", + "license-name": "GPL-3.0+", + "type": "other", + "MessagesDirs": { + "MGWikiDev": [ + "i18n" + ] + }, + "AutoloadClasses": { + "MGWikiDev": "includes/MGWikiDev.php", + "SpecialAccountRequest": "includes/SpecialAccountRequest.php" + }, + "SpecialPages": { + "SpecialAccountRequest": "SpecialAccountRequest" + }, + "ResourceFileModulePaths": { + "localBasePath": "resources", + "remoteExtPath": "MGWikiDev/resources" + }, + "ResourceModules": { + "ext.mgwiki-dev": { + "packageFiles": [ "mgwiki-dev.js" ], + "styles": [ "mgwiki-dev.css" ] + }, + "ext.mgwiki-dev-specialaccountrequest": { + "packageFiles": [ "specialaccountrequest.js" ], + "styles": [ "specialaccountrequest.css" ] + } + }, + "Hooks": { + "BeforePageDisplay": "MGWikiDev::onBeforePageDisplay" + }, + "ExtensionMessagesFiles": { + "MGWikiDevAlias": "includes/MGWikiDev.alias.php" + }, + "manifest_version": 1 +} diff --git a/extension.json b/extension.json index e090612..80bdd8e 100644 --- a/extension.json +++ b/extension.json @@ -16,7 +16,6 @@ ] }, "AutoloadClasses": { - "MGWikiDev": "includes/MGWikiDev.php", "SpecialAccountRequest": "includes/SpecialAccountRequest.php" }, "SpecialPages": { @@ -27,18 +26,11 @@ "remoteExtPath": "MGWikiDev/resources" }, "ResourceModules": { - "ext.mgwiki-dev": { - "packageFiles": [ "mgwiki-dev.js" ], - "styles": [ "mgwiki-dev.css" ] - }, "ext.mgwiki-dev-specialaccountrequest": { "packageFiles": [ "specialaccountrequest.js" ], "styles": [ "specialaccountrequest.css" ] } }, - "Hooks": { - "BeforePageDisplay": "MGWikiDev::onBeforePageDisplay" - }, "ExtensionMessagesFiles": { "MGWikiDevAlias": "includes/MGWikiDev.alias.php" }, diff --git a/includes/SpecialAccountRequest.php b/includes/SpecialAccountRequest.php index 6cdf550..21d9e8e 100644 --- a/includes/SpecialAccountRequest.php +++ b/includes/SpecialAccountRequest.php @@ -67,21 +67,29 @@ protected function beforeExecute( $subPage ) { $mailer = new \UserMailer(); $mail_to = new \MailAddress($wgEmergencyContact); $mail_from = new \MailAddress($post_email); - $body =' -Demande de création de compte MGWiki + $body ='Merci beaucoup de votre intérêt pour MGWiki. +------ Votre message : ------- Date: ' . date('Y-m-d H:i:s') . ' Institution: ' . $_POST['institution'] . ' -Nom: ' . ucfirst(strtolower($post_prenom)) . ' ' . strtoupper($post_nom) . ' -Formateur/Formation: ' . $post_formateur . ' -Année: ' . $post_year . ' -Commentaires: ' . $post_comment . ' -Email: ' . $post_email ; +Nom: ' . htmlspecialchars_decode(ucfirst(strtolower($post_prenom))) . ' ' . htmlspecialchars_decode(strtoupper($post_nom)) .' +Email: ' . htmlspecialchars_decode($post_email); + if ($_POST['institution']=='lyon1'){$body .= ' +Tuteur: ' . htmlspecialchars_decode($post_formateur) . ' +Année de promotion: ' . htmlspecialchars_decode($post_year);} + if ($_POST['institution']=='adepul'){$body .= ' +Formation: ' . htmlspecialchars_decode($post_formateur);} + $body .= ' + Commentaires: +' . htmlspecialchars_decode($post_comment) . ' +------------------------------ - $mailer->send( array($mail_to), $mail_from, 'Demande de création de compte', $body ); +Nous essayons de donner suite à votre demande dans les meilleurs délais.'; + + $mailer->send( array($mail_to,$mail_from), $mail_to, 'MGWiki: demande de création de compte', $body, array('replyTo'=>$mail_from) ); setcookie($cookieLabel,"sent", time()+3600); $this->mess = ' -

Votre demande a bien été envoyée à l\'administrateur du site.

+

Votre demande a bien été envoyée.
Une copie a été adressée à l\'adresse mail que vous avez renseigné.

'; $this->done = true; } diff --git a/includes/captcha.php b/includes/captcha.php index 2df750d..50dea81 100644 --- a/includes/captcha.php +++ b/includes/captcha.php @@ -29,6 +29,7 @@ "Quelle couleur fait jaune plus rouge ?"=>"orange", "Quelle couleur fait bleu plus jaune ?"=>"vert", "Quelle couleur faut-il mélanger à rouge pour faire du violet ?"=>"bleu", - "Ecrivez ' rien ' ."=>"rien" + "Ecrivez ' rien ' ."=>"rien", + "Quel est le premier mot de cette phrase ?"=>"quel" ); $key = array_rand($captcha); From 49115c22263e8498f8e87b78e501287093bcc853 Mon Sep 17 00:00:00 2001 From: Looxloox Date: Tue, 17 Nov 2020 14:28:37 +0100 Subject: [PATCH 08/15] JsonToForm v1, getJsonPage v1, onclick parser v1, specialaccountrequest v2, maintenance v1 --- .gitignore | 13 +- Hooks.php | 44 ++ ...MGWikiDev.alias.php => MGWikiDev.alias.php | 1 + MGWikiDev.i18n.php | 20 + README.md | 12 + composer.json | 28 +- extension-dev.json | 46 -- extension.json | 34 +- i18n/en.json | 7 +- i18n/fr.json | 7 +- i18n/qqq.json | 7 +- includes/Api/ApiGetJson.php | 74 +++ includes/MGWikiDev.php | 21 - includes/Parsers.php | 27 ++ includes/SpecialAccountRequest.php | 268 +++++------ includes/Utilities/Captcha.php | 34 ++ includes/Utilities/GetJsonPage.php | 101 +++++ includes/Utilities/JsonToForm.php | 422 ++++++++++++++++++ includes/Utilities/PhpFunctions.php | 73 +++ includes/captcha.php | 35 -- maintenance/CheckHooks.php | 97 ++++ .../{mgwiki-dev.css => ext.mgwiki-dev.css} | 0 .../{mgwiki-dev.js => ext.mgwiki-dev.js} | 11 +- resources/jsonform.css | 8 + resources/jsonform.js | 82 ++++ resources/specialaccountrequest.css | 16 - resources/specialaccountrequest.js | 46 +- 27 files changed, 1172 insertions(+), 362 deletions(-) create mode 100644 Hooks.php rename includes/MGWikiDev.alias.php => MGWikiDev.alias.php (99%) create mode 100644 MGWikiDev.i18n.php delete mode 100644 extension-dev.json create mode 100644 includes/Api/ApiGetJson.php delete mode 100644 includes/MGWikiDev.php create mode 100644 includes/Parsers.php create mode 100644 includes/Utilities/Captcha.php create mode 100644 includes/Utilities/GetJsonPage.php create mode 100644 includes/Utilities/JsonToForm.php create mode 100644 includes/Utilities/PhpFunctions.php delete mode 100644 includes/captcha.php create mode 100644 maintenance/CheckHooks.php rename resources/{mgwiki-dev.css => ext.mgwiki-dev.css} (100%) rename resources/{mgwiki-dev.js => ext.mgwiki-dev.js} (85%) create mode 100644 resources/jsonform.css create mode 100644 resources/jsonform.js delete mode 100644 resources/specialaccountrequest.css diff --git a/.gitignore b/.gitignore index db9f334..e6eead8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ -# Backup files -*.swp -*~ +# syntax: https://www.atlassian.com/git/tutorials/saving-changes/gitignore -# Composer & npm -/node_modules -/vendor -/composer.lock +# Private files +**/Private + +# temporary log files +*-report.txt diff --git a/Hooks.php b/Hooks.php new file mode 100644 index 0000000..ec17fe0 --- /dev/null +++ b/Hooks.php @@ -0,0 +1,44 @@ + + * @author Alexandre Brulet + * @license GPL-3.0+ + * @package MediaWiki-extension-MGWikiDev + */ +class MGWikiHooks { + + /** + * Chargement du module MGWikiDev + */ + public static function onBeforePageDisplay( OutputPage $out, Skin $skin ) { + //Modules pour toutes les pages + $out->addModules('ext.mgwiki-dev'); + } + + /** + * ! CUSTOM HOOK ! (cf readme -> ApiMain.php changes) + * Autoriser l'API getjson quelque soit l'utilisateur + */ + public static function onApiAllow( $module, $user ) + { + global $wgRequest; + if ( $wgRequest->getText( 'action' ) == 'getjson' ) + { + return true; + } + return false; + } + + // Register any render callbacks with the parser + public static function onParserFirstCallInit( Parser $parser ) { + + // Create a function hook associating the "example" magic word with renderExample() + $parser->setFunctionHook( 'mgw-onclick', [ Parsers::class, 'onclickSpan' ] ); + } +} diff --git a/includes/MGWikiDev.alias.php b/MGWikiDev.alias.php similarity index 99% rename from includes/MGWikiDev.alias.php rename to MGWikiDev.alias.php index 31a6b9f..d04bd06 100644 --- a/includes/MGWikiDev.alias.php +++ b/MGWikiDev.alias.php @@ -9,6 +9,7 @@ $specialPageAliases = array(); + /** English (English) */ $specialPageAliases['en'] = array( 'SpecialAccountRequest' => array( 'Account Request' ), diff --git a/MGWikiDev.i18n.php b/MGWikiDev.i18n.php new file mode 100644 index 0000000..d430776 --- /dev/null +++ b/MGWikiDev.i18n.php @@ -0,0 +1,20 @@ + [ 0, 'mgw-onclick' ], +]; + +/** French (français) */ +$magicWords['fr'] = [ + 'mgw-onclick' => [ 0, 'mgw-onclick' ], +]; diff --git a/README.md b/README.md index fdc6d39..16441a5 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,15 @@ Cette extension permet de personnaliser le site [MGWiki](https://mgwiki.univ-lyo Modules inclus: * SpecialAccountRequest (MàJ 23/10/2020) + +Todo: +* includes/SpecialAccount.php: use WebResponse::setCookie() instead of setcookie() when upgrading Mediawiki >= 1.35 + +Core changes: +* /includes/api/ApiMain.php l.1420 : + .protected function checkExecutePermissions( $module ) { + . $user = $this->getUser(); + . + . if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) && + . !$user->isAllowed( 'read' ) + ++ && !Hooks::run('ApiAllow', [ $module, $user ] ) diff --git a/composer.json b/composer.json index cbecdaf..d2b4d16 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,11 @@ { - "description": "Customisations for MGWiki", + "name": "MGWikiDev", + "type": "mediawiki-extension", + "description": "Customisations for MGWiki - development extension", "keywords": [ "Semantic MediaWiki", - "MediaWiki" + "MediaWiki", + "MGWiki" ], "homepage": "https://mgwiki.univ-lyon1.fr", "license": "GPL-3.0+", @@ -11,29 +14,20 @@ "name": "Sébastien Beyou", "homepage": "https://www.seb35.fr", "role": "Original author" + }, + { + "name": "Alexandre Bulet", + "role": "Co-author" } ], "support": { - "issues": "https://github.com/WikiValley/MGWiki/issues", - "source": "https://github.com/WikiValley/MGWiki" + "issues": "https://github.com/Looxloox/MGWiki-dev/issues", + "source": "https://github.com/Looxloox/MGWiki-dev" }, "require": { "php": ">=7.0.0" }, "require-dev": { - "jakub-onderka/php-parallel-lint": "0.9.2", "mediawiki/mediawiki-codesniffer": "0.7.2" - }, - "suggest": { - "mediawiki/semantic-forms": "Easy creation of forms to add semantic data" - }, - "scripts": { - "test": [ - "parallel-lint . --exclude vendor", - "phpcs -p -s" - ], - "fix": [ - "phpcbf" - ] } } diff --git a/extension-dev.json b/extension-dev.json deleted file mode 100644 index e090612..0000000 --- a/extension-dev.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "@note": "When updating this file please also update MGWiki.php with the same changes.", - "name": "MGWikiDev", - "version": "0.1", - "author": [ - "Sébastien Beyou", - "Alexandre BRULET" - ], - "url": "https://mgwiki.univ-lyon1.fr", - "descriptionmsg": "mgwiki-dev-desc", - "license-name": "GPL-3.0+", - "type": "other", - "MessagesDirs": { - "MGWikiDev": [ - "i18n" - ] - }, - "AutoloadClasses": { - "MGWikiDev": "includes/MGWikiDev.php", - "SpecialAccountRequest": "includes/SpecialAccountRequest.php" - }, - "SpecialPages": { - "SpecialAccountRequest": "SpecialAccountRequest" - }, - "ResourceFileModulePaths": { - "localBasePath": "resources", - "remoteExtPath": "MGWikiDev/resources" - }, - "ResourceModules": { - "ext.mgwiki-dev": { - "packageFiles": [ "mgwiki-dev.js" ], - "styles": [ "mgwiki-dev.css" ] - }, - "ext.mgwiki-dev-specialaccountrequest": { - "packageFiles": [ "specialaccountrequest.js" ], - "styles": [ "specialaccountrequest.css" ] - } - }, - "Hooks": { - "BeforePageDisplay": "MGWikiDev::onBeforePageDisplay" - }, - "ExtensionMessagesFiles": { - "MGWikiDevAlias": "includes/MGWikiDev.alias.php" - }, - "manifest_version": 1 -} diff --git a/extension.json b/extension.json index 80bdd8e..c2f7ced 100644 --- a/extension.json +++ b/extension.json @@ -3,7 +3,6 @@ "name": "MGWikiDev", "version": "0.1", "author": [ - "Sébastien Beyou", "Alexandre BRULET" ], "url": "https://mgwiki.univ-lyon1.fr", @@ -15,24 +14,45 @@ "i18n" ] }, + "AutoloadNamespaces": { + "MediaWiki\\Extension\\MGWikiDev\\": "includes/", + "MediaWiki\\Extension\\MGWikiDev\\Utilities\\": "includes/Utilities/", + "MediaWiki\\Extension\\MGWikiDev\\Api\\": "includes/Api/" + }, "AutoloadClasses": { - "SpecialAccountRequest": "includes/SpecialAccountRequest.php" + "MGWikiHooks": "Hooks.php" }, "SpecialPages": { - "SpecialAccountRequest": "SpecialAccountRequest" + "SpecialAccountRequest": "MediaWiki\\Extension\\MGWikiDev\\SpecialAccountRequest" + }, + "Hooks": { + "BeforePageDisplay": "MGWikiHooks::onBeforePageDisplay", + "ApiAllow": "MGWikiHooks::onApiAllow", + "ParserFirstCallInit": "MGWikiHooks::onParserFirstCallInit" + }, + "APIModules": { + "getjson" : "MediaWiki\\Extension\\MGWikiDev\\Api\\ApiGetJson" }, "ResourceFileModulePaths": { "localBasePath": "resources", "remoteExtPath": "MGWikiDev/resources" }, "ResourceModules": { - "ext.mgwiki-dev-specialaccountrequest": { - "packageFiles": [ "specialaccountrequest.js" ], - "styles": [ "specialaccountrequest.css" ] + "ext.mgwiki-dev": { + "packageFiles": [ "ext.mgwiki-dev.js" ], + "styles": [ "ext.mgwiki-dev.css" ] + }, + "ext.mgwiki-jsonform": { + "packageFiles": [ "jsonform.js" ], + "styles": [ "jsonform.css" ] + }, + "ext.mgwiki-specialaccountrequest": { + "packageFiles": [ "specialaccountrequest.js" ] } }, "ExtensionMessagesFiles": { - "MGWikiDevAlias": "includes/MGWikiDev.alias.php" + "MGWikiDevAlias": "MGWikiDev.alias.php", + "MGWikiDevMagic": "MGWikiDev.i18n.php" }, "manifest_version": 1 } diff --git a/i18n/en.json b/i18n/en.json index 7fa1a0f..db67817 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6,9 +6,6 @@ ] }, "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", - "specialaccountrequest": "Account request", - "specialaccountrequest-title": "Account request", - "specialaccountrequest-intro-lyon": "{{specialaccountrequest-intro-lyon}}", - "specialaccountrequest-intro-adepul": "{{specialaccountrequest-intro-adepul}}", - "specialaccountrequest-intro-autre": "{{specialaccountrequest-intro-autre}}" + "specialpages-group-mgwiki": "MGWiki", + "specialaccountrequest": "Demande de création de compte" } diff --git a/i18n/fr.json b/i18n/fr.json index 220a6ed..08e15f3 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -6,9 +6,8 @@ ] }, "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", + "specialpages-group-mgwiki": "MGWiki", "specialaccountrequest": "Demande de création de compte", - "specialaccountrequest-title": "Demande de création de compte", - "specialaccountrequest-intro-lyon": "{{specialaccountrequest-intro-lyon}}", - "specialaccountrequest-intro-adepul": "{{specialaccountrequest-intro-adepul}}", - "specialaccountrequest-intro-autre": "{{specialaccountrequest-intro-autre}}" + "apihelp-getjson-summary": "Permet de récupérer des données json", + "apihelp-getjson-param-service": "'messages' -> messages d'interface pour MGWiki
'specialAccountRequest' -> choix d'institutions pour la page Special:Demande_de_création_de_compte" } diff --git a/i18n/qqq.json b/i18n/qqq.json index 7fa1a0f..db67817 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -6,9 +6,6 @@ ] }, "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", - "specialaccountrequest": "Account request", - "specialaccountrequest-title": "Account request", - "specialaccountrequest-intro-lyon": "{{specialaccountrequest-intro-lyon}}", - "specialaccountrequest-intro-adepul": "{{specialaccountrequest-intro-adepul}}", - "specialaccountrequest-intro-autre": "{{specialaccountrequest-intro-autre}}" + "specialpages-group-mgwiki": "MGWiki", + "specialaccountrequest": "Demande de création de compte" } diff --git a/includes/Api/ApiGetJson.php b/includes/Api/ApiGetJson.php new file mode 100644 index 0000000..8609fc9 --- /dev/null +++ b/includes/Api/ApiGetJson.php @@ -0,0 +1,74 @@ +extractRequestParams(); + $r = $this->getResult(); + + try { + if ( !isset( $params['service'] ) ) { + throw new \Exception("Erreur : service non défini."); + } + + $Json = new GetJsonPage($params['service']); + + # arguments 'key' et 'subkeys': retourne la 1ère valeur trouvée + if ( isset( $params['key'] ) && isset( $params['parents'] ) ) { + $data = $Json->getSubData( $params['key'], $params['parents'] ); + } + elseif ( isset( $params['key'] ) ) { + $data = $Json->getSubData( $params['key'] ); + } + + # arguments 'merge' et 'parents': aggrégation de toutes les valeurs trouvées + elseif ( isset( $params['merge'] ) && isset( $params['parents'] ) ) { + $data = $Json->mergeSubData( $params['merge'], $params['parents'] ); + } + elseif ( isset( $params['merge'] ) ) { + $data = $Json->mergeSubData( $params['merge'] ); + } + + # absence d'argument: retourne la totalité du contenu + else { + $data = $Json->getFullData(); + } + + # retour des valeurs + if ( !is_null($data) ) { + foreach ( $data as $key => $value ) { + $r->addValue( null, $key, $value ); + } + } + } catch (\Exception $e) { + $r->addValue( null, "erreur", $e ); + } + } + + protected function getAllowedParams() { + return [ + 'service' => [ + ApiBase::PARAM_TYPE => 'string' + ], + 'key' => [ + ApiBase::PARAM_TYPE => 'string' + ], + 'merge' => [ + ApiBase::PARAM_TYPE => 'string' + ], + 'parents' => [ + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_ISMULTI => true, + ] + ]; + } +} diff --git a/includes/MGWikiDev.php b/includes/MGWikiDev.php deleted file mode 100644 index b0a3e40..0000000 --- a/includes/MGWikiDev.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @author Alexandre Brulet - * @license GPL-3.0+ - * @package MediaWiki-extension-MGWiki - */ - -class MGWikiDev { - - /** - * Divers - */ - public static function onBeforePageDisplay( OutputPage $out, Skin $skin ) { - //Modules pour toutes les pages - $out->addModules('ext.mgwiki-dev'); - } -} diff --git a/includes/Parsers.php b/includes/Parsers.php new file mode 100644 index 0000000..9dbb49a --- /dev/null +++ b/includes/Parsers.php @@ -0,0 +1,27 @@ +' . $include . ''; + + if ($target == ''){ + $output = $include; + } + + return [ $output, 'isHTML' => true ];//, 'noparse' => true, + } +} diff --git a/includes/SpecialAccountRequest.php b/includes/SpecialAccountRequest.php index 21d9e8e..ecba552 100644 --- a/includes/SpecialAccountRequest.php +++ b/includes/SpecialAccountRequest.php @@ -1,187 +1,149 @@ messages = GetJsonPage::getData('messages'); + + # gestion du formulaire + $postData = $this->getRequest()->getPostValues(); // récupération des variables POST si elles existent + $this->isPosted = ( sizeof($postData) > 0 ); + $this->JsonForm = new JsonToForm( + 'specialaccountrequest', + $postData, + $this->messages + ); + + # récupération des cookies + self::getCookies(); } /** - * Form, captcha and $_POST checks. - * HTML content definition. + * Shows the page to the user. + * @param string $sub The subpage string argument (if any). */ - protected function beforeExecute( $subPage ) { - global $_SERVER; - global $_POST; - global $_COOKIE; - global $wgEmergencyContact; - include ('captcha.php'); - $post1 = (isset($_POST['accountRequest'])) ? true :false; - $post2 = (isset($_POST['captchaResponse'])) ? true :false; - - //récupération des variables si elles existent - if ($post1){ - $post_lyon1 = ($_POST['institution'] == 'lyon1')? "checked":""; - $post_adepul = ($_POST['institution'] == 'adepul')? "checked":""; - $post_autre = ($_POST['institution'] == 'autre')? "checked":""; - $post_nom = htmlspecialchars($_POST['nom']); - $post_prenom = htmlspecialchars($_POST['prenom']); - $post_email = htmlspecialchars($_POST['email']); - $post_formateur = htmlspecialchars($_POST['formateur']); - $post_year = htmlspecialchars($_POST['year']); - $post_comment = htmlspecialchars($_POST['comment']); - } - else { - $post_lyon1 = ""; - $post_adepul = ""; - $post_autre = ""; - $post_nom = ""; - $post_prenom = ""; - $post_email = ""; - $post_formateur = ""; - $post_year = ""; - $post_comment = ""; - } + public function execute( $sub ) { - $cookieLabel = str_replace(array('=',',',';','\t','\r','\n','\013','\014'),'','mgw_'.$post_nom.$post_prenom); - //vérification du captcha, envoi du mail si ok - if ($post2){ - if (isset($_COOKIE[$cookieLabel])){ - $this->mess = ' -

Votre demande déjà envoyée.

- '; - $this->done = true; + $messHTML = ""; + $done = false; + + /* Traitement de la réponse au captcha */ + if ( $this->JsonForm->isCaptchaPosted() ) + { + # demande déjà finalisée + if ( $this->cookieData['done'] ) + { + $messHTML = $this->messageHTML( 'done', $this->getMsg('specialaccountrequest-mess-alreadydone') ); + $done = true; } - elseif (strtolower(str_replace('.',',',$_POST['captchaResponse'])) == $captcha[$_POST['captchaKey']]) { - $mailer = new \UserMailer(); - $mail_to = new \MailAddress($wgEmergencyContact); - $mail_from = new \MailAddress($post_email); - $body ='Merci beaucoup de votre intérêt pour MGWiki. - ------- Votre message : ------- -Date: ' . date('Y-m-d H:i:s') . ' -Institution: ' . $_POST['institution'] . ' -Nom: ' . htmlspecialchars_decode(ucfirst(strtolower($post_prenom))) . ' ' . htmlspecialchars_decode(strtoupper($post_nom)) .' -Email: ' . htmlspecialchars_decode($post_email); - if ($_POST['institution']=='lyon1'){$body .= ' -Tuteur: ' . htmlspecialchars_decode($post_formateur) . ' -Année de promotion: ' . htmlspecialchars_decode($post_year);} - if ($_POST['institution']=='adepul'){$body .= ' -Formation: ' . htmlspecialchars_decode($post_formateur);} - $body .= ' - Commentaires: -' . htmlspecialchars_decode($post_comment) . ' ------------------------------- - -Nous essayons de donner suite à votre demande dans les meilleurs délais.'; - - $mailer->send( array($mail_to,$mail_from), $mail_to, 'MGWiki: demande de création de compte', $body, array('replyTo'=>$mail_from) ); - setcookie($cookieLabel,"sent", time()+3600); - $this->mess = ' -

Votre demande a bien été envoyée.
Une copie a été adressée à l\'adresse mail que vous avez renseigné.

- '; - $this->done = true; + # envoi du mail si réponse valide + elseif ( $this->JsonForm->isCaptchaValid() ) + { + $this->JsonForm->sendEmail(); + //Todo: use WebResponse::setCookie() when upgrade Mediawiki > 1.35 + setcookie( $this->cookieData['label'], "Account request sent.", time()+3600 ); + $messHTML = $this->messageHTML( 'done', $this->getMsg('specialaccountrequest-mess-confirm') ); + $done = true; } - else { - $count = 0; - if (isset($_COOKIE['mgw_accountRequestBlocked'])){ - $count = $_COOKIE['mgw_accountRequestBlocked']; - } - if ($count > 4){ - $this->mess = ' -

Vous avez dépassé le nombre d\'essais autorisés.

- '; - $this->done = true; - } - else { - $count += 1; - $this->mess = ' -
- - Il y a une erreur... si vous n\'êtes pas un robot, merci de recommencer ! - '; - setcookie('mgw_accountRequestBlocked',$count, time()+60*60); - } + + # captcha invalide, nombre d'essais dépassés + elseif ( $this->cookieData['attempt'] > 4 ){ + $messHTML = $this->messageHTML( 'done', $this->getMsg('specialaccountrequest-mess-attemptslimit') ); + $done = true; + } + + # captcha invalide, nouvel essai + else + { + $this->cookieData['attempt'] += 1; + $messHTML = $this->messageHTML( 'captchaError', $this->getMsg('specialaccountrequest-mess-captchaerror') ); + setcookie('mgw_accountRequestAttempt', $this->cookieData['attempt'], time()+60*60); } } - // affichage du formulaire - if (!$this->done) { - $this->form[1] = ' -

- - - - -
Institution de rattachement:    -
-
- -
'; - $this->form[2] = ' - '; - if ($post1){ - $this->form[2] .= ' -
'.$key.'
- Je ne suis pas un robot: - - -
'; + /* Construction de la page */ + $out = $this->getOutput(); + $out->addModules('ext.mgwiki-jsonform'); + $out->addModules('ext.mgwiki-specialaccountrequest'); + $out->setPageTitle( $this->getMsg('specialaccountrequest-title') ); + + if (!$done){ + $form = $this->JsonForm->makeForm( $messHTML ); + foreach ( $form as $outKey => $outValue ) { + switch ( $outValue['type'] ) { + case 'html': + $out->addHTML( $outValue['content'] ); + break; + case 'wiki': + $out->addWikiTextAsContent( $outValue['content'] ); + break; + } } - $this->form[2] .= ' - - - - - - - - - - -
'; + } + else { + $out->addHTML($messHTML); + } + } + + protected function messageHTML($option, $mess) + { + switch ($option) { + + case 'done': + return '

' . $mess . '

+ '; + break; + + case 'captchaError': + return '
' . $mess . ''; + break; + + default: + throw new \Exception("Error: use of SpecialAccountRequest::messageHTML with invalid option argument", 1); } - return true; } /** - * Shows the page to the user. - * @param string $sub The subpage string argument (if any). - * [[Special:HelloWorld/subpage]]. + * récupération des cookies s'ils existent */ - public function execute( $sub ) { + private function getCookies () + { + global $_COOKIE; // NB: l'appel de $this->getRequest()->getCookie() provoque une erreur: Warning: require(): Could not call the sapi_header_callback in /var/www/html/wiki/extensions/MGWikiDev/includes/SpecialAccountRequest.php + if ( $this->isPosted ){ + $this->cookieData['label'] = str_replace(array('=', ',' ,';' ,'\t' ,'\r' ,'\n' ,'\013' ,'\014'), '', "mgw_" . $this->JsonForm->getHash() ); + $this->cookieData['done'] = isset($_COOKIE[$this->cookieData['label']]); + $this->cookieData['attempt'] = isset($_COOKIE['mgw_accountRequestAttempt']) ? $_COOKIE['mgw_accountRequestAttempt'] : 0; + if ( isset( $this->cookieData[ 'attempt'] ) && !is_int( $this->cookieData[ 'attempt' ] ) ) { $this->cookieData[ 'attempt' ] = 5; } + } + } - $out = $this->getOutput(); - $out->addModules('ext.mgwiki-dev-specialaccountrequest'); - $out->setPageTitle( $this->msg( 'specialaccountrequest-title' ) ); - - if (!$this->done){ - $out->addHTML($this->form[1]); - $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-lyon' )); - $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-adepul' )); - $out->addWikiTextAsContent($this->msg( 'specialaccountrequest-intro-autre' )); - $out->addHTML($this->mess); - $out->addHTML($this->form[2]); - } + private function getMsg ( $mess ) { + if ( isset( $this->messages[ $mess ] ) ) { + return $this->messages[ $mess ]; + } else { - $out->addHTML($this->mess); + return '<' . $mess . '>'; } - } + } /** * groupe auquel se rapporte cette page spéciale diff --git a/includes/Utilities/Captcha.php b/includes/Utilities/Captcha.php new file mode 100644 index 0000000..c9108ac --- /dev/null +++ b/includes/Utilities/Captcha.php @@ -0,0 +1,34 @@ +answers = json_decode(file_get_contents($IP . "/extensions/MGWikiDev/data/Private/Captcha.json"), true); + } + + public function getRandomKey() + { + return array_rand($this->answers); + } + + public function isValid($key, $response) + { + return $this->getAnswer($key) === $this->sanitize($response); + } + + private function getAnswer($key) + { + return isset($this->answers[$key]) ? htmlspecialchars($this->answers[$key]) : false; + } + + private function sanitize($response) + { + return strtolower(str_replace('.',',',$response)); + } +} diff --git a/includes/Utilities/GetJsonPage.php b/includes/Utilities/GetJsonPage.php new file mode 100644 index 0000000..f94ad63 --- /dev/null +++ b/includes/Utilities/GetJsonPage.php @@ -0,0 +1,101 @@ +jsonData = self::retrieveData( $service ); + } + + public function getFullData() + { + return $this->jsonData; + } + + /** + * Retrieve the first mached Json data from a key (recursive search) + * @param string $key + * @param array $parents (optionnal): a list of parent keys to be searched (in tree order) + * @return mixed value or array + */ + public function getSubData( $key, $parents = [] ) + { + $ret = $this->jsonData; + if ( !is_array( $parents ) ) { + throw new \Exception("Erreur GetJsonPage::getSubData(" . $key . ", " . strval($subkeys) . ") : le deuxième argument doit être un tableau.", 1); + } + if ( sizeof( $parents ) > 0 ) { + foreach ($parents as $index => $parent) { + $ret = self::recursiveArrayKey( $parent, $ret ); + } + } + $ret = self::recursiveArrayKey( $key, $ret ); + return $ret; + } + + /** + * Merge all Json data corresponding to a same key (recursive search) + * @param string $key : values to be merged are direct children of this key + * @param array $parents (optionnal) : a list of parent keys to be the searched (out of any tree order) + * @return array + */ + public function mergeSubData( $key, $parents = [] ) + { + if ( !is_array( $parents ) ) { + throw new \Exception("Erreur GetJsonPage::mergeSubData(" . $key . ", " . strval($parents) . ") : le deuxième argument doit être un tableau.", 1); + } + if ( sizeof( $parents ) > 0 ) { + foreach ($parents as $index => $parent) { + $temp = $this->getSubData($parent); + if ( isset( $ret ) ) { + $ret = array_merge( $ret, self::recursiveArrayKeyMerge( $key, $temp ) ); + } + else { + $ret = self::recursiveArrayKeyMerge( $key, $temp ); + } + } + } + else { + $ret = self::recursiveArrayKeyMerge( $key, $this->jsonData ); + } + return $ret; + } + + private function retrieveData( $service ) + { + global $wgMGWikiJsonPages; + if (!isset($wgMGWikiJsonPages[$service])) throw new \Exception("La cible '" . $service . "' n'existe pas.", 1); + + $title = \Title::newFromText( $wgMGWikiJsonPages[$service]['title'], $wgMGWikiJsonPages[$service]['namespace'] ); + if ( $title->getArticleID() == -1 ) { + throw new \Exception("La page ".$wgMGWikiJsonPages['title']." (NS: {$wgMGWikiJsonPages['namespace']}) n'existe pas.", 1); + } + $page = \WikiPage::factory( $title ); + + return json_decode( $page->getContent()->getNativeData(), true ); + } +} diff --git a/includes/Utilities/JsonToForm.php b/includes/Utilities/JsonToForm.php new file mode 100644 index 0000000..79240f0 --- /dev/null +++ b/includes/Utilities/JsonToForm.php @@ -0,0 +1,422 @@ +makeForm( $message = '', $displayCaptcha = false ) + * Captcha + * JsonToForm->isCaptchaPosted() + * JsonToForm->isCaptchaValid() + * Email + * JsonToForm->sendEmail() + * Onclick + * form:*:type:radio + * template + * Divers + * JsonToForm->getHash() + * + * TODO: le module onclick n'est implémenté que sur les + */ +class JsonToForm +{ + /** + * @var string + */ + private $formName; + + /** + * @var string + */ + private $prefix; + + /** + * @var GetJsonPage + */ + private $Json; + + /** + * @var Captcha + */ + private $Captcha; + + /** + * @var array issued from WebRequest::getPostValues() + */ + private $postData; + + /** + * @var array + */ + private $messages; + + /** + * @var array [ ['type' => 'html'/'wiki', 'value' => (string) ] ] + */ + private $output; + + /** + * @param string $jsonService: doit correspondre à une clé de $wgMGWikiJsonPages (LocalSettings.php) + * @return array $this->output + * @param array $messages + */ + public function __construct( $jsonService, &$postData, &$messages ) + { + global $wgMGWikiJsonPages; + + $this->formName = $jsonService; + $this->prefix = 'mgw-' . $this->formName; + + $this->Json = new GetJsonPage( $jsonService ); + if ( is_null( $this->Json->getSubData( 'show', ['structure'] ) ) ) + throw new \Exception('Erreur à la construction de JsonToForm depuis la page ' . + \MWNamespace::getCanonicalName( $wgMGWikiJsonPages[$jsonService]['namespace'] ) . ':' . + $wgMGWikiJsonPages[$jsonService]['title'] . ' : { "structure" : "show" } = null.', 1) + ; + + self::hydratePostData($postData); + + $this->Captcha = new Captcha(); + + $this->messages = $messages; + } + + private function hydratePostData( &$postData ) + { + if ( isset( $postData[$this->formName] ) ) { + foreach ( $postData as $itemKey => $itemData ) { + $this->postData[$itemKey] = htmlspecialchars( $postData[$itemKey] ); + } + } + } + + /** + * @param string $message: message passé au format HTML + * @param bool $displayCaptcha : si vrai l'élément 'captcha' doit prendre une de ces valeurs : ['beforesubmit', 'aftersubmit'] + * @return array $this->output + */ + public function makeForm( $message = '' ) + { + # variables + global $_SERVER; + global $wgMGWikiJsonPages; + + # vérification des constructeurs + if ( !is_null( $this->Json->getSubData( 'captcha' ) ) && + !in_array( $this->Json->getSubData( 'captcha' ), ['beforesubmit', 'aftersubmit'] ) ) + { + throw new \Exception('Erreur à la construction de JsonToForm depuis la page ' . + \MWNamespace::getCanonicalName( $wgMGWikiJsonPages[$jsonService]['namespace'] ) . ':' . + $wgMGWikiJsonPages[$jsonService]['title'] . ' : { [...] "captcha" : "' . $displayCaptcha . + '" } : ! in_array( "' . $displayCaptcha . '", ["beforesubmit", "aftersubmit"])', 1) ; + } + $controllers = $this->Json->getSubData( 'controllers' ); + if ( !is_null( $controllers ) ) { + if ( !is_array( $controllers ) ) + throw new \Exception('Erreur à la construction de JsonToForm depuis la page ' . + \MWNamespace::getCanonicalName( $wgMGWikiJsonPages[$jsonService]['namespace'] ) . ':' . + $wgMGWikiJsonPages[$jsonService]['title'] . ' : { "controllers" : } doit contenir un tableau', 1) ; + foreach ( $controllers as $controllerKey => $controllerArray ) { + if ( !is_array( $controllerArray ) ) + throw new \Exception('Erreur à la construction de JsonToForm depuis la page ' . + \MWNamespace::getCanonicalName( $wgMGWikiJsonPages[$jsonService]['namespace'] ) . ':' . + $wgMGWikiJsonPages[$jsonService]['title'] . ' : { "controllers" : "' . $controllerKey . '" : } doit contenir un tableau', 1); + } + } + + # construction + $this->htmlOut( + '
' + ); + foreach ( $this->Json->getSubData('structure') as $displayKey => $displayValue ) { + $hide = ( $displayKey == 'hide' ); + foreach ( $displayValue as $elementKey => $elementValue ) { + switch ( $elementKey ) + { + case "form": + self::makeFormElement( $elementValue, $hide ); + break; + case "template": + self::makeTemplateElement( $elementValue, $hide ); + break; + case "message": + $this->htmlOut( $message ); + break; + case "captcha": + if ( $elementValue == 'beforesubmit' || + ( $elementValue == 'aftersubmit' && isset( $this->postData[ $this->formName ] ) ) + ) self::makeCaptchaElement( $hide ); + break; + case "submit": + $class = ($hide) ? ' class="mgw-hidden mgw-' . $this->formName . '-field mgw-' . $this->formName . '-field-hide"' : 'class="mgw-specialaccountrequest-field"'; + $style = ($hide) ? ' style="display:none"' : ''; + $this->htmlOut(' + ' + ); + } + } + } + $this->htmlOut( '
' ); + return $this->output; + } + + private function makeFormElement( $elementValue, $hide ) + { + $class = ($hide) ? ' class="mgw-hidden"' : ''; + $style = ($hide) ? ' style="display:none"' : ''; + $this->htmlOut( ' '); + foreach ($elementValue as $fieldKey => $fieldValue) { + + # préparation des attributs + $label = ( isset( $fieldValue['label'] ) ) ? $fieldValue['label'] : ''; + $prefix_field = $this->prefix . '-' . $fieldKey; + $attributes = ''; + if ( isset( $fieldValue['attributes'] ) + && is_array( $fieldValue['attributes'] ) + && sizeof( $fieldValue['attributes'] ) > 0 + ) { + foreach ( $fieldValue['attributes'] as $attributeKey => $attributeValue ) { + $attributes .= ' ' . $attributeKey . '="' . $attributeValue . '" '; + } + } + + # instanciation de la légende + $subclass = ($hide) ? ' mgw-' . $this->formName . '-field-hide' : ''; + $class = 'class="mgw-' . $this->formName . '-field' . $subclass . '"'; + $row = ' + '; + + # instantiation du champs + $row .= ''; + } + + ## TEXT + elseif ( in_array( $fieldValue['type'], ['text', 'email'] ) ) + { + $row .= '' ; + } + + ## TEXTAREA + elseif ( $fieldValue['type'] == 'textarea' ) { + $row .= '' ; + } + $this->htmlOut( $row ); + } + + # clôture + $this->htmlOut( '
' . $label . ''; + $id = 'id="mgw-' . $this->formName . '-' . $fieldKey . '-field"'; + + ## RADIO + if ( $fieldValue['type'] == 'radio' ) + { + $hideclass = ($hide) ? ' mgw-radio-hide' : ''; + foreach ($fieldValue['values'] as $radioKey => $radioValue) + { + $div = 'id="' . $prefix_field . '-' . $radioKey . '-field" class="' . $this->prefix . '-radiovalue'. $hideclass .'" + fieldkey="' . $fieldKey . '" radiokey="' . $radioKey . '"'; + $label = ( isset( $radioValue[ 'label' ] ) ) ? $radioValue[ 'label' ] : ''; + if ( !isset($this->postData[$fieldKey]) || $this->postData[$fieldKey] == '' ) { + $checked = ( $radioValue['checked'] == 'true' ) ? "checked" : ""; + } + else { + $checked = ( $this->postData[$fieldKey] == $radioKey ) ? "checked" : ""; + } + $row .= '
+ +
'; + } + $row .='
' ); + } + + private function makeTemplateElement( $elementValue, $hide) + { + foreach ( $elementValue as $id => $string ) { + + $hideClass = ($hide) ? " mgw-hidden" : ''; + $hideStyle = ($hide) ? ' style="display:none" ' : ''; + + if ( preg_match('/\$/', $string ) != 1 ) { + $this->htmlOut( '' ); + $this->wikiOut( '{{' . $string . '}}' ); + $this->htmlOut( '' ); + } + else { + $string = explode( "$", $string ); + $cases = $this->Json->getSubData( 'values', [ $string[1] ] ); + foreach ( $cases as $caseKey => $caseValue ) + { + $this->htmlOut( '' ) ; + $this->wikiOut( '{{' . $string[0] . $caseKey . '}}' ); + $this->htmlOut( '' ); + } + } + } + } + + private function makeCaptchaElement( $hide ) + { + $class = ($hide) ? ' mgw-hidden' : ''; + $style = ($hide) ? ' style="display:none"' : ''; + $captchaKey = $this->Captcha->getRandomKey(); + $this->htmlOut( ' +
' . $captchaKey . '
+ ' . $this->getMsg( $this->formName . '-label-captcha' ) . ' + + +
' ); + } + + private function htmlOut( $content ) + { + $this->output[] = [ 'type' => 'html', 'content' => $content ]; + } + + private function wikiOut( $content ) + { + $this->output[] = [ 'type' => 'wiki', 'content' => $content ]; + } + + private function onclick( $element ) + { + if ( !is_null( $this->Json->getSubData( $element, ['onclick'] ) ) ) { + foreach ( $this->Json->getSubData( 'onclick' ) as $key => $value ) { + if ( array_key_exists( $element, $value ) ) + return 'onclick="mw.' . $this->formName . '()" '; // + } + } + else return ''; + } + + public function sendEmail() + { + $mailer = new \UserMailer(); + $mail_to = $this->mailAdress( 'mailto' ); + $mail_from = $this->mailAdress( 'mailfrom' ); + $body = $this->composeEmail(); + $mailer->send( + array($mail_to, $mail_from), //to + $mail_to, //from + $this->getMsg( $this->formName . '-email-subject' ), //subject + $body, //body + array( //options + 'replyTo' => $mail_from, + 'contentType' => 'text/html; charset=UTF-8') + ); + } + + private function mailAdress( $dest ) + { + $controller = $this->Json->getSubData( $dest, ['sendmail'] ); + if ( isset( $controller['getvalue'] ) ) { + $email = $this->postData[$controller['getvalue']]; + } + elseif ( isset( $controller['setonvalue'] ) ) { + $email = $this->Json->getSubData( 'setmail', [ $this->postData[$controller['setonvalue']] ] ); + } + switch ( $controller['type'] ) { + case 'user': + $user = \User::newFromName( $email ); + return \MailAddress::newFromUser( $user ); + break; + case 'email': + return new \MailAddress( $email ); + break; + } + } + + private function composeEmail() + { + $body = ' + +

' . $this->getMsg( $this->formName . '-email-intro') . '

+

Votre message :

+ + ' ; + + $fields = []; + # tous les champs "show" + foreach ( $this->Json->getSubData( 'form', ['show'] ) as $fieldKey => $fieldValue ) { + $fields[$fieldKey] = $fieldValue; + } + # les champs "hide" selon les valeurs de "changefields" + if ( !is_null( $this->Json->getSubData( 'changeform' ) ) ) { + foreach ( $this->Json->getSubData('changeform' ) as $key => $value ) { + if ( $value['onfield'] == 'any' || in_array( $this->postData[ $value['onfield'] ], $value['onvalues'] ) ) { + foreach ( $this->Json->getSubData( $this->postData[$key], ['changefields', '$'.$key ] ) as $fieldKey => $fieldValue ) { + $fields[$fieldKey] = $fieldValue; + } + } + } + } + + foreach ( $fields as $fieldKey => $fieldValue ) { + if ( is_null( $this->Json->getSubData( 'hidden', [ $fieldKey ] ) ) && $fieldKey != 'submit' ) + $body .= ' + '; + } + + $body .= '
Date: ' . date('Y-m-d H:i:s') . '
' . $fieldValue[ 'label' ] . ': ' . $this->postData[ $fieldKey ] . '
+
+

' . $this->getMsg( $this->formName . '-email-end' ) . '

+ '; + + return $body; + } + + public function isCaptchaPosted() { + return isset( $this->postData['captchaResponse'] ); + } + + public function isCaptchaValid() + { + if ( isset($this->postData['captchaKey']) && isset($this->postData['captchaResponse']) ){ + return $this->Captcha->isValid($this->postData['captchaKey'], $this->postData['captchaResponse']); + } + else return false; + } + + /** + * gives a unique key from the posted data + * @return string + */ + public function getHash() + { + $string = ''; + $i = 0; + foreach ($this->postData as $key => $value) { + $string .= $value ; + $i++; + if ($i > 2) { break; } + } + return md5( $string ); + } + + private function getMsg ( $mess ) { + if ( isset( $this->messages[ $mess ] ) ) { + return $this->messages[ $mess ]; + } + else { + return '<' . $mess . '>'; + } + } +} diff --git a/includes/Utilities/PhpFunctions.php b/includes/Utilities/PhpFunctions.php new file mode 100644 index 0000000..4d04c4f --- /dev/null +++ b/includes/Utilities/PhpFunctions.php @@ -0,0 +1,73 @@ + $value ) { + if ( $key === $needle ) { + return $value; + } + } + return null; + } + + /** + * recherche récursivement une clé, fusionne le résultat si plusieurs occurences + * @return mixed (valeur, array ou array_merge) + */ + protected function recursiveArrayKeyMerge ( $needle, $array ) + { + $recursive = self::recursiveIterator( $array ); + $ret = []; + foreach ($recursive as $key => $value) { + if ($key === $needle) { + $ret[] = $value; + } + } + switch ( sizeof( $ret ) ) { + + case 0: + return null; + break; + + case 1: + return $ret[0]; + break; + + default: + $merge = []; + foreach ( $ret as $key => $value ) { + if ( is_array( $value ) ) { + foreach ( $value as $kkey => $vvalue ) { + $merge[$kkey] = $vvalue; + } + } + else { + $merge[] = $value; + } + } + return $merge; + break; + } + } + + protected function recursiveIterator( $array ) { + $iterator = new \RecursiveArrayIterator( $array ); + $recursive = new \RecursiveIteratorIterator( + $iterator, + \RecursiveIteratorIterator::SELF_FIRST + ); + return $recursive; + } +} diff --git a/includes/captcha.php b/includes/captcha.php deleted file mode 100644 index 50dea81..0000000 --- a/includes/captcha.php +++ /dev/null @@ -1,35 +0,0 @@ -"6", - "Combien de pis a une vache ?"=> "4", - "Quelle est l'année de la prise de la Bastille ?"=> "1789", - "Combien de lettres comporte 'toutes' au singulier ?"=> "5", - "Combien de mots au pluriel comporte cette phrase ?"=> "1", - "Combien de pattes a une fourmi ?" => "6", - "Combien font sept fois six moins deux"=> "40", - "Combien de mots au singulier comporte cette phrase ?"=> "7", - "Combien de mots finissant par ' e ' comporte cette phrase ?"=> "4", - "Combien de mots finissant par ' t ' comporte cette phrase ?"=> "1", - "Combien de mots commencant par ' c ' comporte cette phrase ?"=> "4", - "Combien vaut pi deux chiffres après la virgule ?"=> "3,14", - "Ecrivez 'forme' en remplaçant le ' m ' par un ' t '"=> "forte", - "Ecrivez 'porte' en remplaçant ' p ' par ' f '"=> "forte", - "Ecrivez 'morne' en mettant ' b ' à la place de ' m '"=> "borne", - "Ecrivez 'sortez' à l'infinitif"=> "sortir", - "Conjuguez 'former' à la troisième personne du singulier pluriel"=> "forment", - "Mangez combien de fruits et légumes par jour ? "=> "5", - "Huit fois deux moins seize ?"=> "0", - "Neuf divisé par trois moins quatre ?"=> "-1", - "Quelle est la couleur du couvercle noir du bidon bleu ?"=>"noir", - "Combien de lettres comporte médecine ?" => "8", - "Combien font deux et deux et deux ?" => "6", - "Combien fait trois puissance deux ?" => "9", - "Quelle lettre est en double dans le deuxième mot de cette phrase ?"=>"t", - "Quel est le dernier mot de cette phrase ?"=>"phrase", - "Quelle couleur fait jaune plus rouge ?"=>"orange", - "Quelle couleur fait bleu plus jaune ?"=>"vert", - "Quelle couleur faut-il mélanger à rouge pour faire du violet ?"=>"bleu", - "Ecrivez ' rien ' ."=>"rien", - "Quel est le premier mot de cette phrase ?"=>"quel" -); -$key = array_rand($captcha); diff --git a/maintenance/CheckHooks.php b/maintenance/CheckHooks.php new file mode 100644 index 0000000..bd983c1 --- /dev/null +++ b/maintenance/CheckHooks.php @@ -0,0 +1,97 @@ + [ + "fileIdentifier" => 'class ApiMain extends ApiBase', // chaîne de caractères unique permettant d'identifier le fichier (ici: includes/api/ApiMain.php) + "stringIdentifier" => '!$user->isAllowed( \'read\' )', // chaîne de caractères unique permettant d'identifier le lieu d'insertion (ici : l.1419) + "customCode" => '&& !Hooks::run( \'ApiAllow\', [ $module, $user ] )' // code à insérer après stringIdentifier + ] +]; + +function checkHooks( $customHooks ) +{ + $info = json_decode(file_get_contents( getPath('extension') . '/extension.json' ), true ); + $endL = ' +'; + $report = fopen('maintenance-report.txt', 'a'); + fputs($report, $endL . ' +-------------------------------- +CheckHooks - ' . date('Y-m-d H:i:s') . ' +--------------------------------' . $endL ); + + foreach ($info['Hooks'] as $key => $value) { + $grep = shell_exec( 'cd ' . getPath('base') . ' && grep -r -n -E "Hooks::run(WithoutAbort)?\( \'' . $key . '\'"' ); + if ( is_null( $grep ) ) { + if ( array_key_exists( $key, $customHooks ) ) { + $add = addHook( $customHooks[$key] ); + fputs($report, $key . $add . $endL); + echo $key . $add . $endL; + } + else { + fputs($report, $key . ' : ECHEC' . $endL); + echo $key . ' : ECHEC' . $endL; + } + } + else { + fputs($report, $key . ' : OK' . $endL); + echo $key . ' : OK' . $endL ; + } + } + + fclose($report); +} + +# $target = 'base' || 'extension' || '' +function getPath( $target ) +{ + $curPath = explode( '/', getcwd() ); + switch ( $target ) { + case 'base': $target = 'extensions'; break; + case 'extension': $target = 'maintenance'; break; + } + $path = ''; $i = 1; + while ( isset( $curPath[$i] ) && ( $curPath[$i] != $target ) ) { + $path .= '/' . $curPath[$i]; $i ++; + } + return $path; +} + +# insère le hook customisé dans MediaWiki-core +# @param array $hook +# @return bool +function addHook( $hook ) +{ + $grep = shell_exec( 'cd ' . getPath('base') . ' && grep -r -n -E "' . $hook[ 'fileIdentifier' ] . '"' ); + if ( is_null( $grep ) ) { + $mess = ' : ECHEC (hook customisé) : occurence "' . $hook[ 'fileIdentifier' ] . '" introuvable dans les fichiers'; + } + else { + $grep = explode( ':', $grep ); + $file = getPath( 'base' ) . '/' . $grep[0]; + $code = file_get_contents( $file ); + $ret = substr_count($code, $hook['stringIdentifier']); + switch ( $ret ) { + case 0: + $mess = ' : ECHEC (MGW-custom) : '. $file .' : "' . $hook[ 'stringIdentifier' ] . '" introuvable'; + break; + case 1: // ajout du code + $code = str_replace( $hook['stringIdentifier'], $hook['stringIdentifier'] . ' ' . $hook['customCode'], $code ); + file_put_contents( $file, $code); + $mess = ' : OK (customized)'; + break; + default: + $mess = ' : ECHEC (MGW-custom) : '. $file .' : "' . $hook[ 'stringIdentifier' ] . '" se trouve plusieurs fois. + "' . $hook['customCode'] . '" doit être ajouté manuellement.'; + break; + } + } + return $mess; +} + +checkHooks( $customHooks ); +?> diff --git a/resources/mgwiki-dev.css b/resources/ext.mgwiki-dev.css similarity index 100% rename from resources/mgwiki-dev.css rename to resources/ext.mgwiki-dev.css diff --git a/resources/mgwiki-dev.js b/resources/ext.mgwiki-dev.js similarity index 85% rename from resources/mgwiki-dev.js rename to resources/ext.mgwiki-dev.js index 58fbde3..f1afda6 100644 --- a/resources/mgwiki-dev.js +++ b/resources/ext.mgwiki-dev.js @@ -2,11 +2,6 @@ /** * MGW customization */ - - mw.mgwImgTooltip(); - $("#mgw-toggle-createUserSubPage-icon").html(' ▼ '); - $("#mgw-toggle-createUserSubPage").attr("onclick","mw.mgwToggleCreateUserSubPage()"); - ( function ( mw, $ ) { //logo aide: //override image link tooltip @@ -39,4 +34,10 @@ $div.attr('class','mgw-toggle-show'); } } + + $( function () { + mw.mgwImgTooltip(); + $("#mgw-toggle-createUserSubPage-icon").html(' ▼ '); + $("#mgw-toggle-createUserSubPage").attr("onclick","mw.mgwToggleCreateUserSubPage()"); + }); }( mediaWiki, jQuery ) ); diff --git a/resources/jsonform.css b/resources/jsonform.css new file mode 100644 index 0000000..151cfe9 --- /dev/null +++ b/resources/jsonform.css @@ -0,0 +1,8 @@ +.mgw-hidden{ + display:none; +} +.mgw-captcha{ + border: 1px solid red; + border-radius: 10px; + background:#f1e8e8; +} diff --git a/resources/jsonform.js b/resources/jsonform.js new file mode 100644 index 0000000..8066b9b --- /dev/null +++ b/resources/jsonform.js @@ -0,0 +1,82 @@ +( function ( mw, $ ) { + + mw.changeform = function( jsonForm ){ + var api = new mw.Api(); + api.get( { + action: 'getjson', + service: jsonForm, + } ).done( function ( data ) { + + // affichage .mgw-hidden ssi un champs coché + if ( $('input[type=radio]:checked').attr('value') ) { + $('.mgw-hidden').show() + } else { $('.mgw-hidden').hide() } + + // construction du controlleur + let reader = {}; + Object.keys(data['controllers']['onclick']['changeform']).forEach( controlfield => { + let checkfield = data['controllers']['onclick']['changeform'][controlfield]['onfield']; + let checkvalues = data['controllers']['onclick']['changeform'][controlfield]['onvalues']; + if ( $('input[name=' + controlfield + ']:checked').attr('value') && + ( checkfield == "any" || checkvalues.indexOf( $('input[name=' + checkfield + ']:checked').attr('value') ) >= 0 ) ) + { + reader[controlfield] = $('input[name=' + controlfield + ']:checked').attr('value'); + } + }); + + // construction de la liste des champs + let fields = {}; + Object.keys(reader).forEach( controlfield => { + fields = Object.assign(fields, data[ 'changefields' ][ '$' + controlfield ][ reader[controlfield] ] ); + }); + + // affichage des champs + $('.mgw-specialaccountrequest-field-hide').each( function () { + let name = $(this).attr('fieldname'); + if ( fields[name] ) { + $(this).show(); + $( '#mgw-' + jsonForm + '-' + name + '-label' ).html( fields[name]['label'] ) ; + if (fields[name]['required'] == "true" ){ + $( '#mgw-' + jsonForm + '-' + name + '-field' ).attr("required", true); + } else { + $( '#mgw-' + jsonForm + '-' + name + '-field' ).attr("required", false); + } + if (fields[name]['value']) { + $( '#mgw-' + jsonForm + '-' + name + '-field').val(fields[name]['value']) + } + } + else { + $( '#mgw-' + jsonForm + '-' + name + '-field' ).attr("required", false); + $(this).hide() + } + }); + + // affichage des boutons radio + $('.mgw-radio-hide').each( function () { + let fieldkey = $(this).attr('fieldkey'); + let radiokey = $(this).attr('radiokey'); + if ( fields[fieldkey] !== undefined && fields[fieldkey]['showvalues'].indexOf(radiokey) >= 0 ) { + $(this).show(); + } + else { + $(this).hide(); + } + }); + + // affichage des templates + $('.mgw-specialaccountrequest-template').each( function () { + if ( $(this).attr('showonfield') ){ + let showonfield = $(this).attr('showonfield'); + let showonvalue = $(this).attr('showonvalue'); + if ( reader[showonfield] !== undefined && reader[showonfield] == showonvalue ) { + $(this).show(); + } + else { + $(this).hide(); + } + } + }); + }); // fin de api.get().done() + + } +}( mediaWiki, jQuery ) ); diff --git a/resources/specialaccountrequest.css b/resources/specialaccountrequest.css deleted file mode 100644 index 68c715f..0000000 --- a/resources/specialaccountrequest.css +++ /dev/null @@ -1,16 +0,0 @@ -/* special account request styles */ -/* -#institution-label{ - vertical-align: top; -}*/ -.mgw-accountrequest-intro{ - display:none; -} -.mgw-hidden{ - display:none; -} -#captcha{ - border: 1px solid red; - border-radius: 10px; - background:#f1e8e8; -} diff --git a/resources/specialaccountrequest.js b/resources/specialaccountrequest.js index 4f79348..9839fee 100644 --- a/resources/specialaccountrequest.js +++ b/resources/specialaccountrequest.js @@ -1,50 +1,14 @@ - - ( function ( mw, $ ) { - mw.accountRequest = function(){ - if ($('input[type=radio][name=institution]:checked').attr('value') == 'lyon1') { - $('.mgw-hidden').show(); - $('span[id=intro-lyon]').show(); - $('span[id=intro-adepul]').hide(); - $('span[id=intro-autre]').hide(); - $('td[class=mgw-tr-formateur]').text('Nom de votre tuteur'); - $('td[class=mgw-tr-year]').text('Année de promotion'); - $('.mgw-tr-formateur').show(); - $('.mgw-tr-formateur').attr("required","true") - $('.mgw-tr-year').show(); - $('.mgw-tr-year').attr("required","true") - $('#mgw-textarea-comment').removeAttr("required"); - } - if ($('input[type=radio][name=institution]:checked').attr('value') == 'adepul') { - $('.mgw-hidden').show(); - $('span[id=intro-lyon]').hide(); - $('span[id=intro-adepul]').show(); - $('span[id=intro-autre]').hide(); - $('td[class=mgw-tr-formateur]').text('Nom de votre formation'); - $('.mgw-tr-formateur').show(); - $('.mgw-tr-formateur').attr("required","true"); - $('.mgw-tr-year').hide(); - $('.mgw-tr-year').removeAttr("required"); - $('#mgw-textarea-comment').attr("required",true); - } - if ($('input[type=radio][name=institution]:checked').attr('value') == 'autre') { - $('.mgw-hidden').show(); - $('span[id=intro-lyon]').hide(); - $('span[id=intro-adepul]').hide(); - $('span[id=intro-autre]').show(); - $('.mgw-tr-formateur').hide(); - $('.mgw-tr-formateur').removeAttr("required"); - $('.mgw-tr-year').hide(); - $('.mgw-tr-year').removeAttr("required"); - $('#mgw-textarea-comment').attr("required", true); - } + + mw.specialaccountrequest = function( ){ + mw.changeform( 'specialaccountrequest' ); } mw.mgwHome = function(){ - $(location).attr("href", "https://mgwiki.univ-lyon1.fr"); + $( location ).attr( "href", "https://mgwiki.univ-lyon1.fr" ); } $( function () { - if ($('input[type=radio]:checked')){mw.accountRequest();} + mw.specialaccountrequest(); }); }( mediaWiki, jQuery ) ); From 7acd0f49ca367c119b4d815ca2277a879b2769fb Mon Sep 17 00:00:00 2001 From: Looxloox <59655204+Looxloox@users.noreply.github.com> Date: Tue, 17 Nov 2020 14:53:59 +0100 Subject: [PATCH 09/15] Update README.md --- README.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 16441a5..4520b51 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,31 @@ Extension MGWiki - module de développement ========================================== +màj: 17/11/2020 + Cette extension permet de personnaliser le site [MGWiki](https://mgwiki.univ-lyon1.fr), wiki privé dédié au partage d’expérience entre internes en médecine et médecins. Modules inclus: -* SpecialAccountRequest (MàJ 23/10/2020) +* Pages spéciales: + - includes/SpecialAccountRequest.php : page spéciale formulaire de demande d'inscription accessible au publique +* Parsers: + - {{#mgw-onclick:|
|}} +* API: + - includes/Api/ApiGetJson: API (?action=getjson) pour récupérer un contenu .json, accès publique autorisé +* Classes php: + - includes/Utilities/GetJsonPage.php: classe pour l'obtention et la manipulation de données au format JSON depuis une page du wiki + permet la gestion de données d'interface directement depuis le wiki: + - MediaWiki:Specialaccountrequest.json : définition du formulaire Special:Account_Request + - MediaWiki:MGWiki-messages.json : messages d'interface + - includes/Utilities/JsonToForm : classe pour la création de formulaires depuis une description au format .json +* Autres: + - maintenance/CheckHooks.php: + - vérification de l'existence des Hooks utilisés par l'extension + - insertion de Hooks customizés si nécessaire (onApiAllow) Todo: * includes/SpecialAccount.php: use WebResponse::setCookie() instead of setcookie() when upgrading Mediawiki >= 1.35 Core changes: -* /includes/api/ApiMain.php l.1420 : - .protected function checkExecutePermissions( $module ) { - . $user = $this->getUser(); - . - . if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) && - . !$user->isAllowed( 'read' ) +* /includes/api/ApiMain.php l.1419 : ++ && !Hooks::run('ApiAllow', [ $module, $user ] ) From 7d4757fff5c02926ca3d04f33442d1b73cac60e9 Mon Sep 17 00:00:00 2001 From: Looxloox Date: Thu, 19 Nov 2020 09:29:28 +0100 Subject: [PATCH 10/15] javascript & css: mw.mgwLink, mw.mgwBorderColor, .mgw-tooltiptext --- .gitignore | 2 +- extension.json | 2 +- images/php/ImageFunctions.php | 39 ++++++++++ images/php/MenuListBackground.php | 18 +++++ images/php/MenuListBorder.php | 20 +++++ images/php/menu-list-background.png | Bin 0 -> 116 bytes includes/SpecialCheckAccounts.php | 39 ++++++++++ resources/ext.mgwiki-dev.css | 34 ++++++++ resources/ext.mgwiki-dev.js | 32 ++++++++ resources/jquery.tancolor.js | 116 ++++++++++++++++++++++++++++ 10 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 images/php/ImageFunctions.php create mode 100644 images/php/MenuListBackground.php create mode 100644 images/php/MenuListBorder.php create mode 100644 images/php/menu-list-background.png create mode 100644 includes/SpecialCheckAccounts.php create mode 100644 resources/jquery.tancolor.js diff --git a/.gitignore b/.gitignore index e6eead8..8b0400d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # syntax: https://www.atlassian.com/git/tutorials/saving-changes/gitignore # Private files -**/Private +Captcha.json # temporary log files *-report.txt diff --git a/extension.json b/extension.json index c2f7ced..ecb6407 100644 --- a/extension.json +++ b/extension.json @@ -39,7 +39,7 @@ }, "ResourceModules": { "ext.mgwiki-dev": { - "packageFiles": [ "ext.mgwiki-dev.js" ], + "packageFiles": [ "ext.mgwiki-dev.js", "jquery.tancolor.js" ], "styles": [ "ext.mgwiki-dev.css" ] }, "ext.mgwiki-jsonform": { diff --git a/images/php/ImageFunctions.php b/images/php/ImageFunctions.php new file mode 100644 index 0000000..cf6f674 --- /dev/null +++ b/images/php/ImageFunctions.php @@ -0,0 +1,39 @@ + diff --git a/images/php/MenuListBorder.php b/images/php/MenuListBorder.php new file mode 100644 index 0000000..1b44c28 --- /dev/null +++ b/images/php/MenuListBorder.php @@ -0,0 +1,20 @@ + diff --git a/images/php/menu-list-background.png b/images/php/menu-list-background.png new file mode 100644 index 0000000000000000000000000000000000000000..daac499acd62ac9521335655aca929f24e3ddf8d GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!2~2v_?jv*C{Pr6zK84P(2m)xs= z_G04>r{-lV82e)9zds{;!!-H&+g<{D47rm*TdXfIZn Q3^azp)78&qol`;+02O#B?*IS* literal 0 HcmV?d00001 diff --git a/includes/SpecialCheckAccounts.php b/includes/SpecialCheckAccounts.php new file mode 100644 index 0000000..70c9739 --- /dev/null +++ b/includes/SpecialCheckAccounts.php @@ -0,0 +1,39 @@ +getOutput(); + $out->addModules('ext.mgwiki-jsonform'); + $out->addModules('ext.mgwiki-specialaccountrequest'); + $out->setPageTitle( $this->getMsg('specialaccountrequest-title') ); + } + + /** + * groupe auquel se rapporte cette page spéciale + */ + protected function getGroupName() { + return 'mgwiki'; + } +} diff --git a/resources/ext.mgwiki-dev.css b/resources/ext.mgwiki-dev.css index fa7e7e7..5f66389 100644 --- a/resources/ext.mgwiki-dev.css +++ b/resources/ext.mgwiki-dev.css @@ -28,3 +28,37 @@ float: right; border-radius: 5px 0 5px 5px; } + +/* Tooltip container */ +.mgw-tooltip { + position: relative; + display: inline-block; +} + +/* Tooltip text */ +.mgw-tooltip .mgw-tooltiptext { + visibility: hidden; + width: 120px; + background-color: #555; + color: #fff; + text-align: center; + padding: 5px 0; + border-radius: 6px; + + /* Position the tooltip text */ + position: absolute; + z-index: 1; + bottom: 125%; + left: 50%; + margin-left: -60px; + + /* Fade in tooltip */ + opacity: 0; + transition: opacity 0.3s; +} + +/* Show the tooltip text when you mouse over the tooltip container */ +.mgw-tooltip:hover .mgw-tooltiptext { + visibility: visible; + opacity: 1; +} diff --git a/resources/ext.mgwiki-dev.js b/resources/ext.mgwiki-dev.js index f1afda6..bba8a2f 100644 --- a/resources/ext.mgwiki-dev.js +++ b/resources/ext.mgwiki-dev.js @@ -2,7 +2,36 @@ /** * MGW customization */ + ( function ( mw, $ ) { + //liens donnés par le modèle {{mgw-link}} + mw.mgwLink = function () { + $(".mgw-link-self").each(function(){ + let url = $(this).children('.mgw-link-url').text(); + $(this).attr("onClick", "location.href='" + url + "'"); + $(this).find('a').attr('href', url); + }); + $(".mgw-link-blank").each(function(){ + let url = $(this).children('.mgw-link-url').text(); + $(this).attr("onClick", "parent.open('" + url + "')"); + $childlink = $(this).children('a'); + $childlink.replaceWith($('

' + $childlink.html() + '

')); + }); + } + + //change menu border color + mw.mgwBorderColor = function() { + if ( $(".mgw-border-color").attr('style') !== undefined ) { + let color = $(".mgw-border-color").attr('style').match(/(#[a-z0-9]{6});/)[1]; + let col = color.substring(1, 7); + $('#content').css('border', '1px solid' + color); + $('.vectorTabs,.vectorTabs span,.vectorTabs ul').css('background-image', + 'url(http://localhost/wiki/extensions/MGWikiDev/images/php/MenuListBorder.php?color='+col+')'); + $('.vectorTabs li:not(.selected)').css('background-image', + 'url(http://localhost/wiki/extensions/MGWikiDev/images/php/MenuListBackground.php?color='+col+')'); + } + } + //logo aide: //override image link tooltip mw.mgwImgTooltip = function () { @@ -36,7 +65,10 @@ } $( function () { + mw.mgwBorderColor(); mw.mgwImgTooltip(); + mw.mgwLink(); + $('.mgw-tooltiptext').css('display',''); $("#mgw-toggle-createUserSubPage-icon").html(' ▼ '); $("#mgw-toggle-createUserSubPage").attr("onclick","mw.mgwToggleCreateUserSubPage()"); }); diff --git a/resources/jquery.tancolor.js b/resources/jquery.tancolor.js new file mode 100644 index 0000000..76d7ce0 --- /dev/null +++ b/resources/jquery.tancolor.js @@ -0,0 +1,116 @@ +(function ($) { + $.fn.tancolor = function(options) { + var settings = $.extend({ + mode: 'grayscale', + r_weight: 0.34, + g_weight: 0.5, + b_weight: 0.16, + r_intensity: 1, + g_intensity: 1, + b_intensity: 1, + load: null + }, options ); + + var r_weight; + var g_weight; + var b_weight; + var r_intensity; + var g_intensity; + var b_intensity; + + // settings value + switch(settings.mode){ + case 'grayscale': + r_weight = 0.34; + g_weight = 0.5; + b_weight = 0.16; + r_intensity = 1; + g_intensity = 1; + b_intensity = 1; + break; + case 'red': + r_weight = 0.34; + g_weight = 0.5; + b_weight = 0.16; + r_intensity = 255; + g_intensity = 1; + b_intensity = 1; + break; + case 'green': + r_weight = 0.34; + g_weight = 0.5; + b_weight = 0.16; + r_intensity = 1; + g_intensity = 255; + b_intensity = 1; + break; + case 'blue': + r_weight = 0.34; + g_weight = 0.5; + b_weight = 0.16; + r_intensity = 1; + g_intensity = 1; + b_intensity = 255; + break; + default: + r_weight = settings.r_weight; + g_weight = settings.g_weight; + b_weight = settings.b_weight; + r_intensity = settings.r_intensity; + g_intensity = settings.g_intensity; + b_intensity = settings.b_intensity; + break; + } + + // convert image to canvas + var img = document.getElementById($(this).attr("id")); + if(settings.load){ + img.src = settings.load; + return; + } + var canvas = convertImageToCanvas(img); + var ctx = canvas.getContext("2d"); + var imageData = ctx.getImageData(0, 0, img.width, img.height) + $(this).replaceWith(canvas); + + // Processing image data + var data = imageData.data; + for(var i = 0; i < data.length; i += 4) { + var brightness = r_weight * data[i] + g_weight * data[i + 1] + b_weight * data[i + 2]; + // red + data[i] = r_intensity * brightness; + // green + data[i + 1] = g_intensity * brightness; + // blue + data[i + 2] = b_intensity * brightness; + } + ctx.putImageData(imageData, 0, 0); + + $('#'+$(this).attr("id")).each(function(i,e){ + var img = e.toDataURL("image/png"); + $(e).replaceWith( $('').attr({width: $(e).attr("width"), height: $(e).attr("height"), style: $(e).attr("style") }) ) }); + + // Converts image to canvas; returns new canvas element + function convertImageToCanvas(image) { + var canvas = document.createElement("canvas"); + canvas.width = image.width; + canvas.height = image.height; + if(image.id) { + canvas.id = image.id; + } + if(image.className) { + canvas.className = image.className; + } + canvas.getContext("2d").drawImage(image, 0, 0); + + return canvas; + } + + // Converts canvas to an image + function convertCanvasToImage(canvas) { + var image = new Image(); + image.src = canvas.toDataURL("image/png"); + return image; + } + }; +}(jQuery)); From e0247906707e8ad5a138a6da6fb55c34522039a3 Mon Sep 17 00:00:00 2001 From: Looxloox Date: Thu, 19 Nov 2020 09:37:13 +0100 Subject: [PATCH 11/15] =?UTF-8?q?m=C3=A0j=20skinning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4520b51..824484b 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,12 @@ Modules inclus: - MediaWiki:MGWiki-messages.json : messages d'interface - includes/Utilities/JsonToForm : classe pour la création de formulaires depuis une description au format .json * Autres: - - maintenance/CheckHooks.php: + - maintenance/CheckHooks.php: - vérification de l'existence des Hooks utilisés par l'extension - insertion de Hooks customizés si nécessaire (onApiAllow) + - skinning: + - resources/ext.mgwiki-dev.js & css + - images/php Todo: * includes/SpecialAccount.php: use WebResponse::setCookie() instead of setcookie() when upgrading Mediawiki >= 1.35 From 5039c4fa36d793f1e3f77074f69b573137c924d7 Mon Sep 17 00:00:00 2001 From: Looxloox Date: Mon, 30 Nov 2020 15:19:53 +0100 Subject: [PATCH 12/15] construction gestion utilisateurs en cours... --- .gitignore | 1 + Hooks.php | 65 +- extension.json | 20 +- i18n/en.json | 3 +- i18n/fr.json | 1 + i18n/qqq.json | 3 +- includes/Api/ApiGetJson.php | 3 +- includes/Foreign/MGWRenameuser.php | 205 +++++ includes/Foreign/MGWUserMerge.php | 98 +++ includes/SpecialAccountRequest.php | 6 +- includes/SpecialCheckAccounts.php | 810 +++++++++++++++++- includes/Utilities/GetJsonPage.php | 48 +- includes/Utilities/PagesFunctions.php | 141 +++ includes/Utilities/PhpFunctions.php | 26 +- includes/Utilities/UsersFunctions.php | 37 + maintenance/CheckHooks.php | 30 +- maintenance/MediawikiUpdate.php | 53 ++ maintenance/ScreenReleaseNotes.php | 278 ++++++ maintenance/Tools.php | 45 + mgwiki.sql | 144 ++++ resources/specialcheckaccounts.css | 34 + resources/specialcheckaccounts.js | 138 +++ sql/addIndex-archive_groupe_lookup.sql | 1 + ...ddIndex-archive_groupes_membres_lookup.sql | 1 + sql/addIndex-archive_utilisateurs_lookup.sql | 1 + sql/addIndex-groupes_lookup.sql | 1 + sql/addIndex-groupes_membres_lookup.sql | 1 + sql/addIndex-utilisateurs_lookup.sql | 1 + sql/addTable-archive_groupes.sql | 13 + sql/addTable-archive_groupes_membres.sql | 10 + sql/addTable-archive_institutions.sql | 8 + sql/addTable-archive_utilisateurs.sql | 9 + sql/addTable-groupes.sql | 13 + sql/addTable-groupes_membres.sql | 9 + sql/addTable-institutions.sql | 8 + sql/addTable-utilisateurs.sql | 9 + 36 files changed, 2219 insertions(+), 55 deletions(-) create mode 100644 includes/Foreign/MGWRenameuser.php create mode 100644 includes/Foreign/MGWUserMerge.php create mode 100644 includes/Utilities/PagesFunctions.php create mode 100644 includes/Utilities/UsersFunctions.php create mode 100644 maintenance/MediawikiUpdate.php create mode 100644 maintenance/ScreenReleaseNotes.php create mode 100644 maintenance/Tools.php create mode 100644 mgwiki.sql create mode 100644 resources/specialcheckaccounts.css create mode 100644 resources/specialcheckaccounts.js create mode 100644 sql/addIndex-archive_groupe_lookup.sql create mode 100644 sql/addIndex-archive_groupes_membres_lookup.sql create mode 100644 sql/addIndex-archive_utilisateurs_lookup.sql create mode 100644 sql/addIndex-groupes_lookup.sql create mode 100644 sql/addIndex-groupes_membres_lookup.sql create mode 100644 sql/addIndex-utilisateurs_lookup.sql create mode 100644 sql/addTable-archive_groupes.sql create mode 100644 sql/addTable-archive_groupes_membres.sql create mode 100644 sql/addTable-archive_institutions.sql create mode 100644 sql/addTable-archive_utilisateurs.sql create mode 100644 sql/addTable-groupes.sql create mode 100644 sql/addTable-groupes_membres.sql create mode 100644 sql/addTable-institutions.sql create mode 100644 sql/addTable-utilisateurs.sql diff --git a/.gitignore b/.gitignore index 8b0400d..49ad46f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Private files Captcha.json +TODO.php # temporary log files *-report.txt diff --git a/Hooks.php b/Hooks.php index ec17fe0..28504c6 100644 --- a/Hooks.php +++ b/Hooks.php @@ -11,7 +11,7 @@ * @license GPL-3.0+ * @package MediaWiki-extension-MGWikiDev */ -class MGWikiHooks { +class MGWikiDevHooks { /** * Chargement du module MGWikiDev @@ -41,4 +41,67 @@ public static function onParserFirstCallInit( Parser $parser ) { // Create a function hook associating the "example" magic word with renderExample() $parser->setFunctionHook( 'mgw-onclick', [ Parsers::class, 'onclickSpan' ] ); } + + /** + * Hook: LoadExtensionSchemaUpdates + * + * fonction exécutée par maintenance/Update.php + * + * @param DatabaseUpdater $updater + */ + public static function onLoadExtensionSchemaUpdates( DatabaseUpdater $updater ) { + + $dir = __DIR__ . '/sql'; + $tables = array( + 'utilisateurs', + 'archive_utilisateurs', + 'institutions', + 'archive_institutions', + 'groupes', + 'archive_groupes', + 'groupes_membres', + 'archive_groupes_membres' + ); + + foreach( $tables as $key => $table ) { + $tableSQLFile = "$dir/addTable-" . $table . ".sql"; + $indexSQLfile = "$dir/addIndex-" . $table . "_lookup.sql"; + $updater->addExtensionTable( 'mgw_' . $table, $baseSQLFile ); + if ( file_exists($indexSQLfile) ) { + $updater->addExtensionIndex( $table, $table.'_lookup', $indexSQLfile ); + } + } + + // $updater->addExtensionTable( 'mgw_utilisateurs', $baseSQLFile ); + // malgré le premier argument toutes les tables et les index sont insérés. + + /* fonctions disponibles: + $updater->addExtensionField( 'flow_revision', 'rev_last_edit_id', "$dir/db_patches/patch-revision_last_editor.sql" ); + $updater->addExtensionIndex( 'flow_workflow', 'flow_workflow_lookup', "$dir/db_patches/patch-workflow_lookup_idx.sql" ); + + if ( $updater->getDB()->getType() === 'sqlite' ) { + $updater->modifyExtensionField( 'flow_summary_revision', 'summary_workflow_id', + "$dir/db_patches/patch-summary2header.sqlite.sql" ); + } else { + // renames columns, alternate patch is above for sqlite + $updater->modifyExtensionField( 'flow_summary_revision', 'summary_workflow_id', + "$dir/db_patches/patch-summary2header.sql" ); + } + + $updater->dropExtensionTable( 'flow_definition', + "$dir/db_patches/patch-drop_definition.sql" ); + $updater->dropExtensionField( 'flow_workflow', 'workflow_user_ip', + "$dir/db_patches/patch-drop_workflow_user.sql" ); + $updater->dropExtensionIndex( 'flow_ext_ref', 'flow_ext_ref_pk', + "$dir/db_patches/patch-remove_unique_ref_indices.sql" ); + + require_once __DIR__ . '/maintenance/FlowUpdateRecentChanges.php'; + $updater->addPostDatabaseUpdateMaintenance( FlowUpdateRecentChanges::class ); + + if ( $updater->updateRowExists( 'FlowSetUserIp' ) ) { + $updater->dropExtensionField( 'flow_revision', 'rev_user_text', + "$dir/db_patches/patch-remove_usernames_2.sql" ); + } + */ + } } diff --git a/extension.json b/extension.json index ecb6407..496f22e 100644 --- a/extension.json +++ b/extension.json @@ -17,18 +17,22 @@ "AutoloadNamespaces": { "MediaWiki\\Extension\\MGWikiDev\\": "includes/", "MediaWiki\\Extension\\MGWikiDev\\Utilities\\": "includes/Utilities/", - "MediaWiki\\Extension\\MGWikiDev\\Api\\": "includes/Api/" + "MediaWiki\\Extension\\MGWikiDev\\Api\\": "includes/Api/", + "MediaWiki\\Extension\\MGWikiDev\\Foreign\\": "includes/Foreign/" }, "AutoloadClasses": { - "MGWikiHooks": "Hooks.php" + "MGWikiDevHooks": "Hooks.php" }, "SpecialPages": { - "SpecialAccountRequest": "MediaWiki\\Extension\\MGWikiDev\\SpecialAccountRequest" + "SpecialAccountRequest": "MediaWiki\\Extension\\MGWikiDev\\SpecialAccountRequest", + "SpecialCheckAccounts": "MediaWiki\\Extension\\MGWikiDev\\SpecialCheckAccounts" + }, "Hooks": { - "BeforePageDisplay": "MGWikiHooks::onBeforePageDisplay", - "ApiAllow": "MGWikiHooks::onApiAllow", - "ParserFirstCallInit": "MGWikiHooks::onParserFirstCallInit" + "BeforePageDisplay": "MGWikiDevHooks::onBeforePageDisplay", + "ApiAllow": "MGWikiDevHooks::onApiAllow", + "ParserFirstCallInit": "MGWikiDevHooks::onParserFirstCallInit", + "LoadExtensionSchemaUpdates": "MGWikiDevHooks::onLoadExtensionSchemaUpdates" }, "APIModules": { "getjson" : "MediaWiki\\Extension\\MGWikiDev\\Api\\ApiGetJson" @@ -48,6 +52,10 @@ }, "ext.mgwiki-specialaccountrequest": { "packageFiles": [ "specialaccountrequest.js" ] + }, + "ext.mgwiki-specialcheckaccounts": { + "packageFiles": [ "specialcheckaccounts.js" ], + "styles": [ "specialcheckaccounts.css" ] } }, "ExtensionMessagesFiles": { diff --git a/i18n/en.json b/i18n/en.json index db67817..9b0f62d 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -7,5 +7,6 @@ }, "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", "specialpages-group-mgwiki": "MGWiki", - "specialaccountrequest": "Demande de création de compte" + "specialaccountrequest": "Demande de création de compte", + "specialcheckaccounts": "Administration des utilisateurs" } diff --git a/i18n/fr.json b/i18n/fr.json index 08e15f3..4ee38db 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -8,6 +8,7 @@ "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", "specialpages-group-mgwiki": "MGWiki", "specialaccountrequest": "Demande de création de compte", + "specialcheckaccounts": "Administration des utilisateurs", "apihelp-getjson-summary": "Permet de récupérer des données json", "apihelp-getjson-param-service": "'messages' -> messages d'interface pour MGWiki
'specialAccountRequest' -> choix d'institutions pour la page Special:Demande_de_création_de_compte" } diff --git a/i18n/qqq.json b/i18n/qqq.json index db67817..9b0f62d 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -7,5 +7,6 @@ }, "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", "specialpages-group-mgwiki": "MGWiki", - "specialaccountrequest": "Demande de création de compte" + "specialaccountrequest": "Demande de création de compte", + "specialcheckaccounts": "Administration des utilisateurs" } diff --git a/includes/Api/ApiGetJson.php b/includes/Api/ApiGetJson.php index 8609fc9..40f2ff4 100644 --- a/includes/Api/ApiGetJson.php +++ b/includes/Api/ApiGetJson.php @@ -4,11 +4,10 @@ */ namespace MediaWiki\Extension\MGWikiDev\Api; -use ApiBase; use ApiCrossWiki; use MediaWiki\Extension\MGWikiDev\Utilities\GetJsonPage; -class ApiGetJson extends ApiBase { +class ApiGetJson extends \ApiBase { public function execute( ) { global $IP; diff --git a/includes/Foreign/MGWRenameuser.php b/includes/Foreign/MGWRenameuser.php new file mode 100644 index 0000000..00bf5ea --- /dev/null +++ b/includes/Foreign/MGWRenameuser.php @@ -0,0 +1,205 @@ + bool, 'message' => bool, ] + */ + public function execute( $oldname, $newname, $movepages, $suppressRedirect, $reason ) { + global $wgContLang, $wgCapitalLinks, $wgUser; + + // ! on AUTORISE les utilisateurs à renommer leur propre compte + $user = &$wgUser; + if ( !( $user->isAllowed( 'renameuser' ) || $user->getName() == $oldname ) ) { + throw new PermissionsError( 'renameuser' ); + } + if ( $user->isBlocked() ) { + throw new UserBlockedError( $wgUser->mBlock ); + } + + $oldusername = Title::makeTitle( NS_USER, $oldname ); + $newusername = Title::makeTitleSafe( NS_USER, $wgContLang->ucfirst( $newname ) ); + + $oun = is_object( $oldusername ) ? $oldusername->getText() : ''; + $nun = is_object( $newusername ) ? $newusername->getText() : ''; + $token = $user->getEditToken(); + + if ( $oun == '' ) { + $message = str_replace( '$1', $oldname, wfMessage( 'renameusererrorinvalid' )->text() ); + return [ 'done' => false, 'message' => $message ]; + } + if ( $nun == '' ) { + $message = str_replace( '$1', $newname, wfMessage( 'renameusererrorinvalid' )->text() ); + return [ 'done' => false, 'message' => $message ]; + } + + // Suppress username validation of old username + $olduser = User::newFromName( $oldusername->getText(), false ); + $newuser = User::newFromName( $newusername->getText(), 'creatable' ); + + // It won't be an object if for instance "|" is supplied as a value + if ( !is_object( $olduser ) ) { + $message = str_replace( '$1', $oldname, wfMessage( 'renameusererrorinvalid' )->text() ); + return [ 'done' => false, 'message' => $message ]; + } + if ( !is_object( $newuser ) || !User::isCreatableName( $newuser->getName() ) ) { + $message = str_replace( '$1', $newname, wfMessage( 'renameusererrorinvalid' )->text() ); + return [ 'done' => false, 'message' => $message ]; + } + + // Check for the existence of lowercase oldusername in database. + // Until r19631 it was possible to rename a user to a name with first character as lowercase + if ( $oldusername->getText() !== $wgContLang->ucfirst( $oldusername->getText() ) ) { + // oldusername was entered as lowercase -> check for existence in table 'user' + $dbr = wfGetDB( DB_REPLICA ); + $uid = $dbr->selectField( 'user', 'user_id', + [ 'user_name' => $oldusername->getText() ], + __METHOD__ ); + if ( $uid === false ) { + if ( !$wgCapitalLinks ) { + $uid = 0; // We are on a lowercase wiki but lowercase username does not exists + } else { + // We are on a standard uppercase wiki, use normal + $uid = $olduser->idForName(); + $oldusername = Title::makeTitleSafe( NS_USER, $olduser->getName() ); + } + } + } else { + // oldusername was entered as upperase -> standard procedure + $uid = $olduser->idForName(); + } + + if ( $uid === 0 ) { + $message = str_replace( '$1', $oldname, wfMessage( 'renameusererrordoesnotexist' )->text() ); + return [ 'done' => false, 'message' => $message ]; + } + + if ( $newuser->idForName() !== 0 ) { + $message = str_replace( + ['$1', '{{GENDER:$1|utilisateur|utilisatrice}}'], + [ $oldname,'utilisateur.ice' ], + wfMessage( 'renameusererrorexists' )->text() ); + return [ 'done' => false, 'message' => $message ]; + } + + // Do the heavy lifting... + $rename = new RenameuserSQL( + $oldusername->getText(), + $newusername->getText(), + $uid, + $wgUser, + [ 'reason' => $reason ] + ); + if ( !$rename->rename() ) { + return [ 'done' => false, 'message' => 'User '.$oldname.' does not exist, bailing out' ]; + } + + // If this user is renaming his/herself, make sure that MovePage::move() + // doesn't make a bunch of null move edits under the old name! + if ( $user->getId() === $uid ) { + $user->setName( $newusername->getText() ); + } + + // Move any user pages + if ( $movepages ) { + $dbr = wfGetDB( DB_REPLICA ); + + $pages = $dbr->select( + 'page', + [ 'page_namespace', 'page_title' ], + [ + 'page_namespace' => [ NS_USER, NS_USER_TALK ], + $dbr->makeList( [ + 'page_title ' . $dbr->buildLike( $oldusername->getDBkey() . '/', $dbr->anyString() ), + 'page_title = ' . $dbr->addQuotes( $oldusername->getDBkey() ), + ], LIST_OR ), + ], + __METHOD__ + ); + + $output = ''; + $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); + foreach ( $pages as $row ) { + $oldPage = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); + $newPage = Title::makeTitleSafe( $row->page_namespace, + preg_replace( '!^[^/]+!', $newusername->getDBkey(), $row->page_title ) ); + + $movePage = new MovePage( $oldPage, $newPage ); + $validMoveStatus = $movePage->isValidMove(); + + # Do not autodelete or anything, title must not exist + if ( $newPage->exists() && !$validMoveStatus->isOK() ) { + $link = $linkRenderer->makeKnownLink( $newPage ); + $output .= Html::rawElement( + 'li', + [ 'class' => 'mw-renameuser-pe' ], + wfMessage( 'renameuser-page-exists' )->rawParams( $link )->escaped() + ); + } else { + $logReason = wfMessage( + 'renameuser-move-log', $oldusername->getText(), $newusername->getText() + )->inContentLanguage()->text(); + + $moveStatus = $movePage->move( $user, $logReason, !$suppressRedirect ); + + if ( $moveStatus->isOK() ) { + # oldPage is not known in case of redirect suppression + $oldLink = $linkRenderer->makeLink( $oldPage, null, [], [ 'redirect' => 'no' ] ); + + # newPage is always known because the move was successful + $newLink = $linkRenderer->makeKnownLink( $newPage ); + + $output .= Html::rawElement( + 'li', + [ 'class' => 'mw-renameuser-pm' ], + wfMessage( 'renameuser-page-moved' )->rawParams( $oldLink, $newLink )->escaped() + ); + } else { + $oldLink = $linkRenderer->makeKnownLink( $oldPage ); + $newLink = $linkRenderer->makeLink( $newPage ); + $output .= Html::rawElement( + 'li', [ 'class' => 'mw-renameuser-pu' ], + wfMessage( 'renameuser-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped() + ); + } + } + } + } + + // Output success message stuff :) + + $message = str_replace( + ['$1', '$2', '{{GENDER:$1|utilisateur|utilisatrice}}', '{{GENDER:$1|renommé|renommée}}'], + [ $oldname, $newname, 'utilisateur.ice', 'renomé.e' ], + wfMessage( 'renameusersuccess' )->text() ); + if ( $output ) $message .= $output . 'ATTENTION: la prise en compte des modifications peut prendre du temps.'; + + return [ 'done' => true, 'message' => $message ]; + } +} diff --git a/includes/Foreign/MGWUserMerge.php b/includes/Foreign/MGWUserMerge.php new file mode 100644 index 0000000..ee89c2b --- /dev/null +++ b/includes/Foreign/MGWUserMerge.php @@ -0,0 +1,98 @@ +getId() === 0 ) { + return [ + 'done' => false, + 'message' => wfMessage('usermerge-badolduser')->text() + ]; + } + if ( $wgUser->getId() === $oldUser->getId() ) { + return [ + 'done' => false, + 'message' => wfMessage('usermerge-noselfdelete', $wgUser->getName() )->parse() + ]; + } + $protectedGroups = MediaWikiServices::getInstance()->getMainConfig()->get( 'UserMergeProtectedGroups' ); + if ( count( array_intersect( $oldUser->getGroups(), $protectedGroups ) ) ) { + return [ + 'done' => false, + 'message' => wfMessage( 'usermerge-protectedgroup', $oldUser->getName() )->parse() + ]; + } + + // validate new user + if ( $newname !== null ) { + $newUser = User::newFromName( $newname ); + if ( !$newUser || $newUser->getId() === 0 ) { + return [ + 'done' => false, + 'message' => wfMessage('usermerge-badnewuser')->text() + ]; + } + } + + // check if the users are different + $newUser = User::newFromName( $newname ); + // Handle "Anonymous" as a special case for user deletion + if ( $newname === null ) { + $newUser = User::newFromName( '(compte erronné)' ); + $newUser->mId = 0; + } + $oldUser = User::newFromName( $oldname ); + if ( $newname !== null && $newUser->getName() === $oldUser->getName() ) { + return [ + 'done' => false, + 'message' => wfMessage('usermerge-same-old-and-new-user')->text() + ]; + } + + // Validation passed, let's merge the user now. + $message = ''; + $um = new MergeUser( $oldUser, $newUser, new UserMergeLogger() ); + $um->merge( $wgUser, __METHOD__ ); + + $message .= wfMessage( 'usermerge-success' )->rawParams( + $oldUser->getName(), $oldUser->getId(), + $newUser->getName(), $newUser->getId() )->parse(); + + if ( $delete ) { + $failed = $um->delete( $wgUser, $msg ); + $message .= wfMessage( 'usermerge-userdeleted')->rawParams( $oldUser->getName(), $oldUser->getId() )->escaped() ; + if ( $failed ) return [ + 'done' => false, + 'message' => wfMessage('usermerge-page-unmoved')->text() + ]; + } + return [ + 'done' => true, + 'message' => $message + ]; + } +} diff --git a/includes/SpecialAccountRequest.php b/includes/SpecialAccountRequest.php index ecba552..9fdcbe2 100644 --- a/includes/SpecialAccountRequest.php +++ b/includes/SpecialAccountRequest.php @@ -1,6 +1,7 @@ messages[ $mess ]; } else { - return '<' . $mess . '>'; + $link = GetJsonPage::getLink('messages'); + return '<' . $mess . '>'; } } diff --git a/includes/SpecialCheckAccounts.php b/includes/SpecialCheckAccounts.php index 70c9739..7119255 100644 --- a/includes/SpecialCheckAccounts.php +++ b/includes/SpecialCheckAccounts.php @@ -2,37 +2,833 @@ namespace MediaWiki\Extension\MGWikiDev; +use SpecialPage; +use MediaWiki\Extension\MGWikiDev\Utilities\GetJsonPage; +use MediaWiki\MediaWikiServices; +use Wikimedia\Rdbms\Database; +use MediaWiki\Extension\MGWikiDev\Foreign\MGWRenameuser; +use MediaWiki\Extension\MGWikiDev\Foreign\MGWUserMerge; +use MediaWiki\Extension\MGWikiDev\Utilities\PagesFunctions as PageF; +use MediaWiki\Extension\MGWikiDev\Utilities\UsersFunctions as UserF; +use WikitextContent; + /** * Page spéciale demande de création de compte * Accessible du public (whitelisted) */ -class SpecialCheckAccounts extends \SpecialPage { +class SpecialCheckAccounts extends SpecialPage { + + /** + * Array of messages values + * + * @var array + */ + private $messages = []; + + /** + * @var array + */ + private $triOptions, $filtreOptions, $filtreOptionsTitles; + + /** + * Array of sort values + * + * @var array + */ + private $select = []; + + /** + * Array of filter values + * + * @var array + */ + private $check = []; /** * Initialize the special page. */ public function __construct() { - parent::__construct( 'SpecialCheckAccounts' ); + parent::__construct( 'SpecialCheckAccounts', 'editinterface' ); // restrict to sysops + # messages d'interface + $this->messages = GetJsonPage::getData('messages'); + + # définition des options + $this->triOptions = ['id', 'username', 'nom', 'prenom', 'email']; + $this->filtreOptions = [ + 'nouserpage', + 'nousertemplate', + 'redirect', + 'nouseremail', + 'differentname-real', + 'differentname-page', + 'sysop', + 'bureaucrat', + 'U2']; + $this->filtreOptionsTitles = [ + 'usergroup' => [ 'sysop', 'bureaucrat', 'U2' ] + ]; } /** * Shows the page to the user. * @param string $sub The subpage string argument (if any). */ + public function execute( $sub ) { - /* Construction de la page */ + global $tri; + $postData = $this->getRequest()->getPostValues(); + + // définition de $select[] et $check[] + $this->setOptions( $postData ); + + // affichage du formulaire d'entête + $this->setHeaders(); $out = $this->getOutput(); - $out->addModules('ext.mgwiki-jsonform'); - $out->addModules('ext.mgwiki-specialaccountrequest'); - $out->setPageTitle( $this->getMsg('specialaccountrequest-title') ); + $out->addModules('ext.mgwiki-specialcheckaccounts'); + $out->setPageTitle( $this->getMsg('specialcheckaccounts-title') ); + $out->addHTML( $this->makeHeadForm() ); + + // ACTIONS + $empty = [ + 'id' => '', + 'username' => '', + 'userpagelink' => '' + ]; + + # affichage brut + if ( isset( $postData['afficher'] ) ) { + $users = self::getUsers( false ); + } + + # emails en doublon uniquement + if ( isset( $postData['emailduplicates'] ) ) { + $emailduplicates = self::getEmailDuplicates(); + $users = []; + foreach ( $emailduplicates as $mail ) { + if ( $mail != '' ) { + $users = array_merge( $users, self::getUsers( false, 'user_email = \'' . $mail . '\'' ) ); + } + } + if ( sizeof( $users ) < 1 ) { + $empty['id'] = '0'; + $empty['message'] = 'Aucun mail en doublon n\'a été trouvé.'; + $actionInfo[] = $empty; + } + } + + # utilisateurs cochés uniquement + if ( isset( $postData['select'] ) ) { + $users = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); + } + + # harmonisation de la casse 'Prénom NOM' sur les pages utilisateurs + if ( isset( $postData['sanitize'] ) ) { + $actionInfo = $this->sanitizeNomPrenom( explode( ',', $postData['addusers'] ) ); + $users = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); + } + + # renommer les utilisateurs sur la base du formulaire Personne (pages utilisateurs) + if ( isset( $postData['rename'] ) ) { + $reason = $this->getMsg('specialcheckaccount-rename-summary'); + $users = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); + foreach ( $users as $user ) { + $renameInfo = MGWRenameuser::execute( + $user['username'], // username actuel + $user['prenom'] . ' ' . $user['nom'], // username de destination + true, // renommer toutes les pages + false, // supprimer les redirections + $reason + ); + $message = ($renameInfo['done']) ? 'SUCCES' : 'ECHEC'; + $actionInfo[] = [ + 'id' => $user['id'], + 'username' => $user['username'], + 'userpagelink' => $user['userpagelink'], + 'message' => $message . ' : ' . $renameInfo['message'] + ]; + } + } + + # fusionner ou effacer les utilisateurs + ## contrôle des entrées + $checkMerge = true; + $checkDelete = true; + if ( isset( $postData['merge'] ) && count( explode( ',', $postData['addusers'] ) ) > 1 ) { + $empty['message'] = '"in" :: un seul utilisateur de destination doit être coché.'; + $actionInfo[] = $empty; + if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) + $postData['addusers'] .= ',' . $postData['deleteusers']; + $users = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); + $checkMerge = false; + } + if ( isset( $postData['merge'] ) && ( !isset( $postData['addusers'] ) || $postData['addusers'] == '' ) ) { + $empty['message'] = '"in" :: un utilisateur de destination doit être coché.'; + $actionInfo[] = $empty; + if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) { + $users = self::getUsers( false, 'user_id IN(' . $postData['deleteusers'] . ')' ); + } + $checkMerge = false; + } + if ( ( isset( $postData['merge'] ) || isset( $postData['delete'] ) ) && + ( !isset( $postData['deleteusers'] ) || $postData['deleteusers'] == '' ) ) { + $empty['message'] = 'Au moins un utilisateur doit être coché :: "out"'; + $actionInfo[] = $empty; + if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) { + $users = self::getUsers( false, 'user_id IN(' . $postData['deleteusers'] . ')' ); + } + $checkMerge = false; + $checkDelete = false; + } + ## fusion ou délétion en tant que telle + if ( isset( $postData['merge'] ) && $checkMerge ) { + $delete = ( $postData['merge'] == 'delete' ); + $targetUser = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); + $usersToMerge = self::getUsers( false, 'user_id IN(' . $postData['deleteusers'] . ')' ); + foreach ( $usersToMerge as $key => $userToMerge ) { + $r = MGWUserMerge::execute( $userToMerge['username'], $targetUser[0]['username'], $delete, [ $this, 'msg' ] ); + + if ( !$delete && $r['done'] ) { + $s = self::oldUserClean( $userToMerge['username'], $targetUser[0]['username'] ); + } + else + $s = true; + + if ( $r['done'] && $s ) + $m = 'SUCCES'; + elseif ( $r['done'] && !$s ) { + $m = 'ECHEC'; + $r['message'] .= '
La fusion a réussi mais l\'ancien utilisateur n\'a pas été nettoyé.'; + } + else + $m = 'ECHEC'; + + $userToMerge['message'] = $m . ' : ' . $r['message']; + $actionInfo[] = $userToMerge; + + $uDisplay = $postData['addusers']; + if ( !$delete ) $uDisplay .= ',' . $postData['deleteusers']; + $users = self::getUsers( false, 'user_id IN(' . $uDisplay . ')' ); + } + } + + # affichage des utilisateurs valides + if ( isset( $postData['validusers'] ) ) { + $users = self::getUsers( true ); + } + + # mise à jour de la table mgw_utilisateurs + if ( isset( $postData['populate'] ) ) { + $users = self::getUsers( true, 'user_id IN(' . $postData['addusers'] . ')' ); + foreach ( $users as $validUser ) { + $r = self::updateUtilisateurs( $validUser ); + $mess = ( $r['done'] ) ? 'SUCCES : ' : 'ECHEC : '; + $validUser['message'] = $mess . $r['message']; + $actionInfo[] = $validUser; + } + } + + // TRI + if ( isset($users) ) { + // tri d'un tableau à 2 dimentions + uasort ( $users , function ($a, $b) { + global $tri; + if ( $a[$tri[1]] == $b[$tri[1]] ) { + if ( $tri[2] != '' ) { + if ( $a[$tri[2]] == $b[$tri[2]] ) return 0; + return ( $a[$tri[2]] < $b[$tri[2]] ) ? -1 : 1; + } + return 0; + } + return ( $a[$tri[1]] < $b[$tri[1]] ) ? -1 : 1; + }); + } + else $users = []; + if ( isset( $actionInfo ) ) { + $users = array_merge( $actionInfo, $users ); + } + if ( sizeof( $users ) < 1 ) { + $users[] = array( + 'id' => '0', + 'username' => '', + 'userpagelink' => '', + 'message' => 'Aucun utilisateur ne correspond à la requête.' + ); + } + + // AFFICHAGE DES UTILISATEURS + $out->addHTML('
'); + $select = &$this->select; + $check = &$this->check; + foreach( $users as $row ) { + $test = true; + //'nouserpage', 'nousertemplate', 'sysop', 'bureaucrat', 'U2', 'nouseremail', 'emailduplicate' + if ( ( $check['nouserpage']['hide'] == 'checked' && is_null( $row['userpagelink']) ) || + ( $check['nouserpage']['only'] == 'checked' && !is_null( $row['userpagelink']) ) + ) $test = false; + if ( ( $check['nousertemplate']['hide'] == 'checked' && is_null($row['nom']) ) || + ( $check['nousertemplate']['only'] == 'checked' && !is_null($row['nom']) ) + ) $test = false; + if ( ( $check['redirect']['hide'] == 'checked' && !is_null($row['redirect']) ) || + ( $check['redirect']['only'] == 'checked' && is_null($row['redirect']) ) + ) $test = false; + if ( ( $check['sysop']['hide'] == 'checked' && in_array('sysop', $row['groups']) ) || + ( $check['sysop']['only'] == 'checked' && !in_array('sysop', $row['groups']) ) + ) $test = false; + if ( ( $check['bureaucrat']['hide'] == 'checked' && in_array('bureaucrat', $row['groups']) ) || + ( $check['bureaucrat']['only'] == 'checked' && !in_array('bureaucrat', $row['groups']) ) + ) $test = false; + if ( ( $check['U2']['hide'] == 'checked' && in_array('U2', $row['groups']) ) || + ( $check['U2']['only'] == 'checked' && !in_array('U2', $row['groups']) ) + ) $test = false; + if ( ( $check['nouseremail']['hide'] == 'checked' && $row['email'] == '' ) || + ( $check['nouseremail']['only'] == 'checked' && $row['email'] != '' ) + ) $test = false; + if ( ( $check['differentname-real']['hide'] == 'checked' && $row['username'] != $row['realname'] ) || + ( $check['differentname-real']['only'] == 'checked' && $row['username'] == $row['realname'] ) + ) $test = false; + if ( ( $check['differentname-page']['hide'] == 'checked' && + $row['username'] != $row['prenom'].' '.$row['nom'] && !is_null($row['nom']) ) || + ( $check['differentname-page']['only'] == 'checked' && + $row['username'] == $row['prenom'].' '.$row['nom'] && !is_null($row['nom']) ) + ) $test = false; + if ( isset( $row['message'] ) ) $test = true; + + if ( $test ) { + $out->addHTML( $this->displayUser( $row ) ); + } + } + $out->addHTML('
'); + $out->addHTML( $this->displayActions() ); + } + + + /** + * @param array $postData + */ + private function setOptions( &$postData ) + { + global $tri; + $select = &$this->select; + $check = &$this->check; + + $tri[1] = ( isset( $postData['tri1'] ) ) ? $postData['tri1'] : 'id'; + $tri[2] = ( isset( $postData['tri2'] ) ) ? $postData['tri2'] : ''; + + foreach ( $this->triOptions as $value ) { + if ( isset($postData['tri1'] ) && $postData['tri1'] == $value ) $select[1][$value] = 'selected'; + else $select[1][$value] = ''; + if ( isset($postData['tri2'] ) && $postData['tri2'] == $value ) $select[2][$value] = 'selected'; + else $select[2][$value] = ''; + if ( !in_array( 'selected', $select[2] ) ) $select[2]['...'] = 'selected'; + else $select[2]['...'] = ''; + } + + foreach ( $this->filtreOptions as $value ) { + $check[$value]['view'] = ( isset( $postData[$value] ) && $postData[$value] == 'view' ) ? 'checked' : ''; + $check[$value]['hide'] = ( isset( $postData[$value] ) && $postData[$value] == 'hide' ) ? 'checked' : ''; + $check[$value]['only'] = ( isset( $postData[$value] ) && $postData[$value] == 'only' ) ? 'checked' : ''; + if ( !in_array('checked', [ $check[$value]['view'], $check[$value]['hide'], $check[$value]['only'] ] ) ) + $check[$value]['view'] = 'checked'; + } + } + + + /** + * @param string $option { 'tri' | 'filtres' } + * @return mixed (string ou false) + */ + private function makeHeadForm ( ) + { + global $_SERVER; + $select = &$this->select; + $check = &$this->check; + + $return = '
'; + // TRI + $return .= ' +
Tri + + + + + +
+ + + + + +
+
'; + // FILTRES + $return .= ' +
Filtres + + + + + + + '; + $writeTitle = false; + foreach ( $this->filtreOptions as $filtreOption ) { + foreach ( $this->filtreOptionsTitles as $title => $titleOptions ) { + if ( in_array( $filtreOption, $titleOptions ) && !$writeTitle ){ + $return .= ''; + $writeTitle = true; + } + elseif ( !in_array( $filtreOption, $titleOptions ) ) + $writeTitle = false; + } + $return .= ' + + + + + + '; + } + $return .= ' +
seul
'.$this->getMsg( 'specialcheckaccounts-'.$title ).'
'.$this->getMsg( 'specialcheckaccounts-'.$filtreOption ).'
+
+ + +
'; + return $return; } + /** - * groupe auquel se rapporte cette page spéciale + * @param array $row + * @return mixed (string HTML ou false) */ + private function displayUser ( &$row ) { + // si l'entrée correspond à un d'erreur on l'affiche... + if ( isset( $row['message'] ) ) { + return '
   '.$row['id'].' ' . $row['username'] . ' : +    ' . $row['message'] . '
'; + } + // ... sinon on affiche l'utilisateur + $return = ' + + + + + + '; + $return .= ' + + + + + '; + $return .= ' + + + + + '; + $return .= ' + + + + + '; + if ( is_null( $row['userpagelink'] ) ) { + $return .= ' + + + + + + '; + } + else { + $return .= ' + + + '; + if ( !is_null($row['redirect'] ) ) + $return .= ' + + + '; + elseif ( is_null($row['nom'] ) ) + $return .= ' + + + '; + else { + $return .= ' + + + '; + } + } + $return .= '
'.$row['id'].'' . $row['username'] . '
realname' . $row['realname'] . '
email' . $row['email'] . '
groups' . implode( ', ', $row['groups'] ) . '
User pagepage utilisateur inexistante
User page#redirection: ' . $row['redirect'] . '
Modèle {{Personne}} inexistant et redirection absence ou invalide
' . $row['prenom'] . ' ' . $row['nom'] . '
'; + return $return; + } + + /** + * @return string HTML + */ + private function displayActions() { + global $_SERVER; + return ' + + + + + + + + + + + + + + + + + + + + + + + + + +
inout
->
<- - -
+ +
+ + + + + + + + +
'; + } + + + /** + * Requête la base et les pages utilisateur + * + * @param bool $isValid + * @param mixed $where string ou false (ex. : 'cat_pages > 0') + * @param mixed $options array ou false (ex. : array( 'ORDER BY' => 'cat_title ASC' )) + * @return array|null + */ + private function getUsers ( $isValid, $where = false, $options = false ) { + + $return = []; + + /* Construction de la liste des utilisateurs depuis la db */ + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $colnames = array( + 'user_id', + 'user_name', + 'user_real_name', + 'user_email'); + if ( !$where && !$options ) { + $res = $dbr->select( 'user', $colnames ); + } + elseif ( is_string($where) && !$options ) { + $res = $dbr->select( 'user', $colnames, $where ); + } + elseif ( is_string($where) && is_array($options) ) { + $res = $dbr->select( 'user', $colnames, $where, __METHOD__, $options ); + } + elseif ( !$where && is_array($options) ) { + $res = $dbr->select( 'user', $colnames, '', __METHOD__, $options ); + } + else throw new \Exception("Erreur SpecialCheckAccounts::getUsers() : arguments invalides", 1); + + ///////// PATCH TEMPORAIRE ///////////// + foreach( $res as $row ) { + $user = UserF::getUserFromId( $row->user_id ); + $userpage = PageF::getPageFromTitleText( $user->getName(), NS_USER , true ); + if ( !is_null( $userpage ) ) { + $userpagelink = $userpage->getTitle()->getFullURL(); + $infos = PageF::getPageTemplateInfos( $userpage, 'Personne', [ 'Nom', 'Prénom' ] ); + if ( !is_null( $infos ) ) { + $nom = $infos['Nom']; + $prenom = $infos['Prénom']; + $redirect = null; + $redirectURL = null; + $valid = true; + } + else { + $nom = null; + $prenom = null; + $valid = false; + $redirect = PageF::getPageRedirect( $userpage ); + if ( !is_null( $redirect ) ) { + // vérification de la validité de la redirection + $screen = preg_match( '/^Utilisateur:(.*)$/', $redirect, $matches ); + if ( $screen < 1 ) { + $redirect = null; + $redirectURL = null; + } + else { + $title = PageF::getTitleFromText( $matches[1], NS_USER, true ); + if ( is_null($title) ) $redirect = null; + else $redirectURL = $title->getFullURL(); + } + } + } + } + else { + $userpagelink = null; + $redirect = null; + $nom = null; + $prenom = null; + $valid = false; + } + + if ( !isset( $redirectURL ) ) + $redirectURL = null; + + if ( !isset($row->user_email) || is_null($row->user_email) || $row->user_email == '' ) + $valid = false; + + if ( !$isValid ) + $valid = true; + + ///////////////////////////////////////////// + if ( $valid ) { + $return[] = array( + 'id' => $row->user_id, + 'username' => $row->user_name, + 'realname' => $row->user_real_name, + 'email' => $row->user_email, + 'groups' => $user->getGroups(), + 'registration' => $user->getRegistration(), + 'nom' => $nom, + 'prenom' => $prenom, + 'userpagelink' => $userpagelink, + 'redirect' => $redirect, + 'redirectURL' => $redirectURL + ); + } + } + return $return; + } + + /** + * Recherche les adresses mail en doublon + * + * @return mixed (array ou null) + */ + private function getEmailDuplicates ( ) { + $return = []; + + /* Requête */ + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $colnames = array( 'user_email' ); + $where = ''; + $options = array( + "GROUP BY" => "user_email", + "HAVING" => "COUNT(user_email) > 1" + ); + $res = $dbr->select( 'user', $colnames, $where, __METHOD__, $options ); + + foreach( $res as $row ) { + $return[] = $row->user_email; + } + return $return; + } + + /** + * @param array $idList : liste des id utilisateurs + * @return array $return : utilisateurs et messages + */ + private function sanitizeNomPrenom ( $idList ) { + $return = []; + foreach ( $idList as $id ) { + $user = UserF::getUserFromId( $id ); + $userpage = PageF::getPageFromTitleText( $user->getName(), NS_USER, true ); + $oldtext = $userpage->getContent()->getNativeData(); + + // Prénom + $newtext = preg_replace_callback( + '/(\|Prénom=)([^\|]+)/', + function ($text) { + $space = false; + $str = $text[2]; + if ( preg_match('/ /', $text[2]) > 0 ) { + $str = str_replace(' ','-',$str); + $space = true; + } + $str = explode( '-', $str); + foreach ( $str as $key => $substr ) { + $str[$key] = ucfirst( strtolower( $substr ) ); + } + $text[2] = ( $space ) ? implode( ' ', $str ) : implode( '-', $str ); + return $text[1] . $text[2] ; + }, + $oldtext ); + + // Nom + $newtext = preg_replace_callback( + '/(\|Nom=)([^\|]+)/', + function ($text) { + return $text[1] . strtoupper($text[2]); + }, + $newtext ); + + // If there's at least one replacement, modify the page + if ( $newtext != $oldtext ) { + $edit_summary = $this->getMsg('specialcheckaccount-sanitize-summary'); + $newcontent = new WikitextContent( $newtext ); + $userpage->doEditContent( $newcontent, $edit_summary, EDIT_AUTOSUMMARY ); + } + else { + $return[] = array( + 'id' => $id, + 'username' => $userpage->getTitle()->getFullText(), + 'userpagelink' => $userpage->getTitle()->getFullURL(), + 'message' => 'Aucune modification n\'a été faite.' + ); + } + } + return $return; + } + + /** + * @param string $oldName + * @param string $newName + * @return array $return : utilisateurs et messages + */ + private function oldUserClean( $oldName, $newName ) { + + $oldUser = UserF::getUserFromNames( $oldName, null, true ); + if ( is_null($oldUser) ) { + throw new \Exception('oldUserClean: $oldUser doit être un utilisateur valide.', 1); + return false; + } + + # nettoyage de l'utilisateur dans la base + $oldUser->setEmail(''); + foreach ( $oldUser->getGroups() as $group ) { + $oldUser->removeGroup( $group ); + } + $oldUser->saveSettings(); + + # nettoyage de la page utilisateur + $oldUserPage = PageF::getPageFromTitleText( $oldName, NS_USER ); + $newtext = '#REDIRECTION [[Utilisateur:' . $newName . ']]'; + $edit_summary = $this->getMsg('specialcheckaccount-merge-summary', [ $newName ] ); + $flags = EDIT_AUTOSUMMARY; + $newcontent = new WikitextContent( $newtext ); + $oldUserPage->doEditContent( $newcontent, $edit_summary, $flags ); + /* ne marche pas ... ?? + if ( PageF::writeContent( $oldUserPage, $newtext, $edit_summary, $flags ) ) + return true; + else return false; + */ + } + + /** + * @param array $arrUser : une entrée du tableau retourné par $this->getUsers() + */ + public function updateUtilisateurs( $userArray ) { + + global $wgUser; + + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + + $res = $dbw->select( 'mgw_utilisateurs', [ 'utilisateur_user_id', 'utilisateur_nom', 'utilisateur_prenom' ], + 'utilisateur_id = ' . $userArray['id'] ); + + if ( sizeof( $res < 1 ) ) { + $dbw->insert( + 'mgw_utilisateurs', + [ + 'utilisateur_user_id' => $userArray['id'], + 'utilisateur_nom' => $userArray['nom'], + 'utilisateur_prenom' => $userArray['prenom'], + 'utilisateur_updater_user_id' => $wgUser->getId(), + 'utilisateur_update_time' => date('Y-m-d H:i:s') + ] + ); + $done = true; + $message = $userArray['prenom'] . ' ' . $userArray['nom'] . ' a été ajouté à la table mgw_utilisateurs.'; + } + elseif ( sizeof( $res < 1 ) ) { + $done = false; + $message = 'Erreur : l\'utilisateur ' . $userArray['id'] . ' figure ' . + sizeof( $res ) . ' fois dans la table mgw_utilisateurs'; + } + elseif ( $res[0]->utilisateur_nom != $userArray['nom'] ) { + $dbw->update( + 'mgw_utilisateurs', [ + 'utilisateur_nom' => $userArray['nom'], + 'utilisateur_updater_user_id' => $wgUser->getId(), + 'utilisateur_update_time' => date('Y-m-d H:i:s') + ], + 'utilisateur_id = ' . $userArray['id'] ); + $done = true; + $message = $userArray['prenom'] . ' ' . $userArray['nom'] . ' a été mis à jour.'; + } + elseif ( $res[0]->utilisateur_prenom != $userArray['prenom'] ) { + $dbw->update( + 'mgw_utilisateurs', + [ + 'utilisateur_prenom' => $userArray['prenom'], + 'utilisateur_updater_user_id' => $wgUser->getId(), + 'utilisateur_update_time' => date('Y-m-d H:i:s') + ], + 'utilisateur_id = ' . $userArray['id'] + ); + $done = true; + $message = $userArray['prenom'] . ' ' . $userArray['nom'] . ' a été mis à jour.'; + } + return [ 'done' => $done, 'message' => $message ]; + } + + /** + * @param string $mess : clé du message + * @param array $args : liste des arguments à passer dans le message ( $1, $2, etc. ) + * @return array $return : utilisateurs et messages + */ + private function getMsg ( $mess, $args = [] ) { + if ( isset( $this->messages[ $mess ] ) ) { + $out = $this->messages[ $mess ]; + if ( sizeof($args) > 0 ) { + foreach ( $args as $key => $arg ) { + $needle = '$' . ($key + 1); + $out = str_replace( $needle, $arg, $out ); + } + } + return $out; + } + else { + $link = GetJsonPage::getLink('messages'); + return '<' . $mess . '>'; + } + } + protected function getGroupName() { return 'mgwiki'; } diff --git a/includes/Utilities/GetJsonPage.php b/includes/Utilities/GetJsonPage.php index f94ad63..8aa70a5 100644 --- a/includes/Utilities/GetJsonPage.php +++ b/includes/Utilities/GetJsonPage.php @@ -2,7 +2,9 @@ namespace MediaWiki\Extension\MGWikiDev\Utilities; -use MediaWiki\Extension\MGWikiDev\Utilities\PhpFunctions; +use MediaWiki\Extension\MGWikiDev\Utilities\PhpFunctions as PhpF; +use Title; +use WikiPage; /** * Class to handle json data from a wiki page @@ -13,10 +15,17 @@ class GetJsonPage private $jsonData; // array private $service; // string - use PhpFunctions; + /** + * @param string $service : one of the first-level keys of (array) $wgMGWikiJsonPages as defined in LocalSettings.php + */ + public function __construct( $service ) + { + $this->jsonData = self::retrieveData( $service ); + } /** - * pour usage statique, sans constructeur + * récupérer les données en usage statique, sans constructeur + * @param string $service */ public static function getData( $service ) { @@ -24,11 +33,12 @@ public static function getData( $service ) } /** - * @param string $service : one of the first-level keys of (array) $wgMGWikiJsonPages as defined in LocalSettings.php + * récupérer l'url de la page + * @param string $service */ - public function __construct( $service ) + public static function getLink( $service ) { - $this->jsonData = self::retrieveData( $service ); + return self::getFullURL( $service ); } public function getFullData() @@ -50,10 +60,10 @@ public function getSubData( $key, $parents = [] ) } if ( sizeof( $parents ) > 0 ) { foreach ($parents as $index => $parent) { - $ret = self::recursiveArrayKey( $parent, $ret ); + $ret = PhpF::recursiveArrayKey( $parent, $ret ); } } - $ret = self::recursiveArrayKey( $key, $ret ); + $ret = PhpF::recursiveArrayKey( $key, $ret ); return $ret; } @@ -72,15 +82,15 @@ public function mergeSubData( $key, $parents = [] ) foreach ($parents as $index => $parent) { $temp = $this->getSubData($parent); if ( isset( $ret ) ) { - $ret = array_merge( $ret, self::recursiveArrayKeyMerge( $key, $temp ) ); + $ret = array_merge( $ret, PhpF::recursiveArrayKeyMerge( $key, $temp ) ); } else { - $ret = self::recursiveArrayKeyMerge( $key, $temp ); + $ret = PhpF::recursiveArrayKeyMerge( $key, $temp ); } } } else { - $ret = self::recursiveArrayKeyMerge( $key, $this->jsonData ); + $ret = PhpF::recursiveArrayKeyMerge( $key, $this->jsonData ); } return $ret; } @@ -90,12 +100,24 @@ private function retrieveData( $service ) global $wgMGWikiJsonPages; if (!isset($wgMGWikiJsonPages[$service])) throw new \Exception("La cible '" . $service . "' n'existe pas.", 1); - $title = \Title::newFromText( $wgMGWikiJsonPages[$service]['title'], $wgMGWikiJsonPages[$service]['namespace'] ); + $title = Title::newFromText( $wgMGWikiJsonPages[$service]['title'], $wgMGWikiJsonPages[$service]['namespace'] ); if ( $title->getArticleID() == -1 ) { throw new \Exception("La page ".$wgMGWikiJsonPages['title']." (NS: {$wgMGWikiJsonPages['namespace']}) n'existe pas.", 1); } - $page = \WikiPage::factory( $title ); + $page = WikiPage::factory( $title ); return json_decode( $page->getContent()->getNativeData(), true ); } + + private function getFullURL( $service ) + { + global $wgMGWikiJsonPages; + if (!isset($wgMGWikiJsonPages[$service])) throw new \Exception("La cible '" . $service . "' n'existe pas.", 1); + + $title = Title::newFromText( $wgMGWikiJsonPages[$service]['title'], $wgMGWikiJsonPages[$service]['namespace'] ); + if ( $title->getArticleID() == -1 ) { + throw new \Exception("La page ".$wgMGWikiJsonPages['title']." (NS: {$wgMGWikiJsonPages['namespace']}) n'existe pas.", 1); + } + return $title->getFullURL(); + } } diff --git a/includes/Utilities/PagesFunctions.php b/includes/Utilities/PagesFunctions.php new file mode 100644 index 0000000..54aa07c --- /dev/null +++ b/includes/Utilities/PagesFunctions.php @@ -0,0 +1,141 @@ +getArticleID() <= 0 ) { + return null; + } else return $title; + } + + /** + * @param Title $title + * @param bool $check = false (return null if page does not exist) + * + * @return mixed WikiPage ou null + */ + public function getPageFromTitle ( Title $title, $check = false ) { + if ( $check ) { + if ( $title->getArticleID() <= 0 ) return null; + } + return WikiPage::factory( $title ); + } + + /** + * @param string $pagename : titre de la page + * @param int $namespace : constante de l'espace de nom de la page (ex.: 'NS_MAIN') + * @param bool $check = false (return null if title or page does not exist) + * + * @return mixed WikiPage ou null + */ + public function getPageFromTitleText ( $pagename, $namespace, $check = false ) { + $title = self::getTitleFromText( $pagename, $namespace, $check ); + if ( is_null( $title ) ) { + return null; + } + return self::getPageFromTitle( $title, $check ); + } + + /** + * @param string $pagename : titre de la page + * @param int $namespace : constante de l'espace de nom de la page (ex.: 'NS_MAIN') + * + * @return mixed string ou null + */ + public function getPageContentFromTitleText ( $pagename, $namespace ) { + $page = self::getPageFromTitleText( $pagename, $namespace, true ); + if ( is_null( $page ) ) { + return null; + } + return $page->getContent()->getNativeData(); + } + + /** + * recherche l'existence d'une redirection + * renvoie le texte du titre de la page redirigée + * + * @param WikiPage $page : titre de la page + * @return mixed string ou false + */ + public function getPageRedirect ( $page ) { + $return = []; + $content = $page->getContent()->getNativeData(); + $screen = preg_match( '/^\#REDIRECTION \[\[(.*)\]\]/', $content, $matches ); + if ( $screen > 0 ) return $matches[1]; + return null; + } + + /** + * recherche la valeur des paramètres d'un modèle inclus + * + * @param WikiPage $page : titre de la page + * @param string $template : nom du modèle + * @param array $fields : champs recherchés + * + * @return mixed array( field => data, ... ) ou null + */ + public function getPageTemplateInfos ( $page, $template, $fields ) { + $return = []; + $content = $page->getContent()->getNativeData(); + $content = str_replace( '}}','',$content ); + $content = explode('{{', $content ); + foreach ( $content as $key => $string ) { + $screen = preg_match( '/^' . $template . '/', $string); + if ( $screen > 0 ) { + $data = explode( '|', $string ); + foreach ( $fields as $kkey => $field ) { + foreach ( $data as $kkkey => $dat ) { + $screen = preg_match('/^'.$field.'/', $dat, $matches ); + if ( $screen > 0 ) { + $dat = trim(str_replace( $field.'=', '', $dat )); + $return[$field] = $dat; + } + } + } + } + } + if ( sizeof( $return ) == 0 ) $return = null; + return $return; + } + + /** + * écriture du contenu d'une page + * + * @param WikiPage $page + * @param string $newtext + * @param string $edit_summary + * @param int $flags + * + * @return bool + */ + public function writeContent ( $page, $newtext, $edit_summary, $flags = 0 ) { + global $wgUser; + $newcontent = new WikitextContent( $newtext ); + // cf: docs/pageupdater.txt + $updater = $page->newPageUpdater( $wgUser ); + $updater->setContent( 'main', $newcontent ); // SlotRecord::MAIN = 'main' + $updater->setRcPatrolStatus( 1 ); // RecentChange::PRC_PATROLLED = 1 + $comment = CommentStoreComment::newUnsavedComment( $edit_summary ); + $newRev = $updater->saveRevision( $comment, $flags ); + + return ( !is_null( $newRev ) && $updater->wasSuccessful() ); + } +} diff --git a/includes/Utilities/PhpFunctions.php b/includes/Utilities/PhpFunctions.php index 4d04c4f..b686828 100644 --- a/includes/Utilities/PhpFunctions.php +++ b/includes/Utilities/PhpFunctions.php @@ -5,13 +5,13 @@ /** * Fonctions diverses */ -trait PhpFunctions +class PhpFunctions { /** * recherche récursivement une clé * @return mixed (première valeur retrouvée ou null) */ - protected function recursiveArrayKey ( $needle, $array ) + public function recursiveArrayKey ( $needle, $array ) { $recursive = self::recursiveIterator( $array ); foreach ( $recursive as $key => $value ) { @@ -26,7 +26,7 @@ protected function recursiveArrayKey ( $needle, $array ) * recherche récursivement une clé, fusionne le résultat si plusieurs occurences * @return mixed (valeur, array ou array_merge) */ - protected function recursiveArrayKeyMerge ( $needle, $array ) + public function recursiveArrayKeyMerge ( $needle, $array ) { $recursive = self::recursiveIterator( $array ); $ret = []; @@ -62,7 +62,7 @@ protected function recursiveArrayKeyMerge ( $needle, $array ) } } - protected function recursiveIterator( $array ) { + private function recursiveIterator( $array ) { $iterator = new \RecursiveArrayIterator( $array ); $recursive = new \RecursiveIteratorIterator( $iterator, @@ -70,4 +70,22 @@ protected function recursiveIterator( $array ) { ); return $recursive; } + + /** + * recherche les doublons dans un tableau + * @return mixed array ou null + */ + public function array_doublons( $array ) { + if ( !is_array( $array ) ) return false; + $r_valeur = Array(); + $array_unique = array_unique($array); + + if ( count( $array ) - count( $array_unique ) ) { + for ( $i=0; $i< count( $array ); $i++ ) { + if ( !array_key_exists( $i, $array_unique ) ) + $r_valeur[] = $array[$i]; + } + } + return $r_valeur; + } } diff --git a/includes/Utilities/UsersFunctions.php b/includes/Utilities/UsersFunctions.php new file mode 100644 index 0000000..5287409 --- /dev/null +++ b/includes/Utilities/UsersFunctions.php @@ -0,0 +1,37 @@ +getId() <= 0 ) return null; + else return $user; + } + + /** + * @param int $id + * + * @return User object + */ + public function getUserFromId ( $id ) { + return User::newFromId ( $id ); + } +} diff --git a/maintenance/CheckHooks.php b/maintenance/CheckHooks.php index bd983c1..729a5d1 100644 --- a/maintenance/CheckHooks.php +++ b/maintenance/CheckHooks.php @@ -4,8 +4,9 @@ * Les Hooks spécifiques à MGWiki sont automatiquement insérés (si l'accroche du code n'a pas changé). * (doit être exécuté avec les droits d'écriture) */ +if ( !isset($includeTools) ) include ('Tools.php'); -$customHooks = [ +if ( !isset($customHooks) ) $customHooks = [ "ApiAllow" => [ "fileIdentifier" => 'class ApiMain extends ApiBase', // chaîne de caractères unique permettant d'identifier le fichier (ici: includes/api/ApiMain.php) "stringIdentifier" => '!$user->isAllowed( \'read\' )', // chaîne de caractères unique permettant d'identifier le lieu d'insertion (ici : l.1419) @@ -25,7 +26,11 @@ function checkHooks( $customHooks ) --------------------------------' . $endL ); foreach ($info['Hooks'] as $key => $value) { - $grep = shell_exec( 'cd ' . getPath('base') . ' && grep -r -n -E "Hooks::run(WithoutAbort)?\( \'' . $key . '\'"' ); + $shell = ' && stdbuf -oL grep -r -n -E "Hooks::run(WithoutAbort)?\( \'' . $key . '\'" | head -n1'; + $grep = shell_exec( 'cd ' . getPath('base') . '/includes' . $shell ); + if ( is_null( $grep ) ) { $grep = shell_exec( 'cd ' . getPath('base') . '/maintenance' . $shell ); } + if ( is_null( $grep ) ) { $grep = shell_exec( 'cd ' . getPath('base') . '/skins' . $shell ); } + if ( is_null( $grep ) ) { $grep = shell_exec( 'cd ' . getPath('base') . '/extensions' . $shell ); } if ( is_null( $grep ) ) { if ( array_key_exists( $key, $customHooks ) ) { $add = addHook( $customHooks[$key] ); @@ -42,25 +47,14 @@ function checkHooks( $customHooks ) echo $key . ' : OK' . $endL ; } } - + echo $endL . ' + ================================== + => voir: maintenance-report.txt <= + ================================== + '; fclose($report); } -# $target = 'base' || 'extension' || '' -function getPath( $target ) -{ - $curPath = explode( '/', getcwd() ); - switch ( $target ) { - case 'base': $target = 'extensions'; break; - case 'extension': $target = 'maintenance'; break; - } - $path = ''; $i = 1; - while ( isset( $curPath[$i] ) && ( $curPath[$i] != $target ) ) { - $path .= '/' . $curPath[$i]; $i ++; - } - return $path; -} - # insère le hook customisé dans MediaWiki-core # @param array $hook # @return bool diff --git a/maintenance/MediawikiUpdate.php b/maintenance/MediawikiUpdate.php new file mode 100644 index 0000000..e24302a --- /dev/null +++ b/maintenance/MediawikiUpdate.php @@ -0,0 +1,53 @@ + [ + "fileIdentifier" => 'class ApiMain extends ApiBase', // chaîne de caractères unique permettant d'identifier le fichier (ici: includes/api/ApiMain.php) + "stringIdentifier" => '!$user->isAllowed( \'read\' )', // chaîne de caractères unique permettant d'identifier le lieu d'insertion (ici : l.1419) + "customCode" => '&& !Hooks::run( \'ApiAllow\', [ $module, $user ] )' // code à insérer après stringIdentifier + ] +]; + +/** + * Liste des versions de MediaWiki conçernées par cette màj + */ +$mw_releases = array( + '34' => '1', + '35' => '1'); + +/** + * déclaration des chaînes de caractères à rechercher dans les releases notes + */ +$searchInCode = array( + [ + 'type' => 'classe', + 'regexp' => '/^use ([a-zA-Z]+\\\)*([a-zA-Z]+)[ ]?;/', + 'match' => 2 ], + [ + 'type' => 'classe', + 'regexp' => '/extends (\\\)?([a-zA-Z]+) /', + 'match' => 2 ], + [ + 'type' => 'skin', + 'regexp' => '/wfLoadSkin\( \'([a-zA-Z]+)\' \)/', + 'match' => 1 ], + [ + 'type' => 'extension', + 'regexp' => '/wfLoadExtension\( \'([a-zA-Z]+)\' \)/', + 'match' => 1 ], + [ + 'type' => 'variable globale', + 'regexp' => '/wg[a-zA-Z]+/', + 'match' => 0 ] +); + +include ('Tools.php'); +$includeTools = true; + +include ('CheckHooks.php'); +include ('ScreenReleaseNotes.php'); + +?> diff --git a/maintenance/ScreenReleaseNotes.php b/maintenance/ScreenReleaseNotes.php new file mode 100644 index 0000000..d028e93 --- /dev/null +++ b/maintenance/ScreenReleaseNotes.php @@ -0,0 +1,278 @@ + '1', + '35' => '1'); + +if (!isset( $searchInCode ) ) $searchInCode = array( + [ + 'type' => 'classe', + 'regexp' => '/^use ([a-zA-Z]+\\\)*([a-zA-Z]+)[ ]?;/', + 'match' => 2 ], + [ + 'type' => 'classe', + 'regexp' => '/extends (\\\)?([a-zA-Z]+) /', + 'match' => 2 ], + [ + 'type' => 'skin', + 'regexp' => '/wfLoadSkin\( \'([a-zA-Z]+)\' \)/', + 'match' => 1 ], + [ + 'type' => 'extension', + 'regexp' => '/wfLoadExtension\( \'([a-zA-Z]+)\' \)/', + 'match' => 1 ], + [ + 'type' => 'variable globale', + 'regexp' => '/wg[a-zA-Z]+/', + 'match' => 0 ] +); + +/** + * Recherche et extraction des commentaires de versions + */ +function extractReleaseNotes ( $relN, $relV ) { + $file = "RELEASE-NOTES-$relN.$relV.txt"; + + $ofile = fopen( $file, 'a'); + $url = 'https://phabricator.wikimedia.org/source/mediawiki/browse/REL'.$relN.'_'.$relV.'/RELEASE-NOTES-'.$relN.'.'.$relV; + $curl = " +"; + $curl = shell_exec( "curl $url" ); + fputs($ofile, $curl); + fclose($ofile); + // lecture du fichier html + $ofile = fopen($file, 'r'); + $out = ''; + $endL = ' +'; + while(! feof( $ofile ) ) { + $line = fgets( $ofile ); + preg_match('/^.*(.*)/', $line, $matchL); + preg_match( '//', $line, $matchN ); + if ( isset($matchL[1]) ){ + $out .= htmlspecialchars_decode($matchL[1],ENT_QUOTES) . '@ligne@' . $matchN[1] . $endL; + } + } + fclose($ofile); + + // réécriture sans les balises html + $ofile = fopen($file, 'w'); + fwrite ( $ofile , $out) ; + fclose ( $ofile ) ; + + // lecture des données + $ofile = fopen($file, 'r'); + $data = []; + $h1 = ''; + $h2 = ''; + $h3 = ''; + $h4 = ''; + $l = '1'; + $text = null; + $plain = false; + while(! feof( $ofile ) ) { + $line = fgets( $ofile ); + $line = explode( '@ligne@', $line ); + if ( isset( $line[1] ) ) { + preg_match('/[0-9]+/', $line[1], $matches); + $line[1] = $matches[0]; + } + $done = false; + $title = false; + $screen = preg_match('/^(= )(.*)( =)/', $line[0], $matches); + if ( isset($screen) && $screen > 0 ) { + $new_h1 = $matches[2]; + $new_h2 = ''; + $new_h3 = ''; + $new_h4 = ''; + $new_l = $line[1]; + $done = true; + $title = true; + } + $screen = preg_match('/^(== )(.*)( ==)/', $line[0], $matches); + if ( isset($screen) && $screen > 0 ) { + $new_h2 = $matches[2]; + $new_h3 = ''; + $new_h4 = ''; + $new_l = $line[1]; + $done = true; + $title = true; + } + $screen = preg_match('/^(=== )(.*)( ===)/', $line[0], $matches); + if ( isset($screen) && $screen > 0 ) { + $new_h3 = $matches[2]; + $new_h4 = ''; + $new_l = $line[1]; + $done = true; + $title = true; + } + $screen = preg_match('/^(==== )(.*)( ====)/', $line[0], $matches); + if ( isset($screen) && $screen > 0 ) { + $new_h4 = $matches[2]; + $new_l = $line[1]; + $done = true; + $title = true; + } + $screen = preg_match('/^[a-zA-Z0-9]/', $line[0], $matches); + if ( isset($screen) && $screen < 1 ) { + $screen2 = preg_match('/^\*/', $line[0]); + if ( isset($screen2) && $screen2 > 0 ) { + $done = true; + $new_l = $line[1]; + } + } else $plain = true; + + if ($title) $plain = false; + $empty = [""," +"]; + if ( $done && !$plain && !is_null($text) && !in_array($text,$empty) ) { + $data[] = [ + 'h1' => $h1, + 'h2' => $h2, + 'h3' => $h3, + 'h4' => $h4, + 'l' => $l, + 'url' => $url, + 'text' => $text + ]; + if ( isset( $new_h1 ) ) $h1 = $new_h1; + if ( isset( $new_h2 ) ) $h2 = $new_h2; + if ( isset( $new_h3 ) ) $h3 = $new_h3; + if ( isset( $new_h4 ) ) $h4 = $new_h4; + if ( isset( $new_l ) ) $l = $new_l; + $text = null; + } + + if ( !$title ) { + if ( is_null( $text ) ) $text = $line[0]; + else $text .= $endL . $line[0]; + } + } + fclose( $ofile ); + unlink( $file ); + return( $data ); +} + +/** + * Recherche et extraction des classes, skins et $wg dans le code MGWiki ... + */ +function screenCode ( $searchInCode, $self ) { + $return = []; + $endL = ' +'; + $mgw_phpfiles = listDir( getPath( 'extension' ), 'php' ); // recherche dans tous les fichiers .php de l'extension + $mgw_phpfiles[] = getPath( 'base' ) . '/LocalSettings.php'; // on ajoute LocalSettings + foreach ( $mgw_phpfiles as $key => $file ) { + $ofile = fopen($file, 'r'); + while(! feof( $ofile ) ) { + $line = fgets( $ofile ); + $done = false; + foreach( $searchInCode as $num => $exp ) { + preg_match( $exp['regexp'], $line, $matches ); + $m = preg_match('/'.$self.'/', $line ); + $n = $exp['match']; + if ( isset( $matches[ $n ] ) && $m < 1 ){ + if ( sizeof( $return ) > 0 ) { + foreach ( $return as $key => $value ) { + if ( $return[$key]['string'] == $matches[ $n ] && in_array( $file, $return[$key]['files'])) { + $done = true; + } + if ( $return[$key]['string'] == $matches[ $n ] && !in_array( $file, $return[$key]['files'])) { + $return[$key]['files'][] = $file; + $done = true; + } + } + } + if ( !$done ) { + $return[] = [ + 'string' => $matches[ $n ], + 'files' => [ $file ], + 'type' => $exp['type'] + ]; + } + } + } + } + fclose($ofile); + } + return $return; +} + +// import des releases notes au format html +$releaseNotes = []; +foreach ( $mw_releases as $relV => $relN ) { + $data = extractReleaseNotes( $relN, $relV ); + $releaseNotes = array_merge( $releaseNotes, $data ); +} + +// recherche des classes utilisées dans les releases notes +$includedClasses = screenCode( $searchInCode, $self ); +$endL = ' +'; +$out = ' +---------------------------------------- +ScreenReleaseNotes - ' . date('Y-m-d H:i:s') . ' +---------------------------------------- +Les modifications suivantes peuvent impacter le fonctionnement de MGWiki. +Sources : +'; +foreach ( $mw_releases as $relV => $relN ) { + $out .= ' https://phabricator.wikimedia.org/source/mediawiki/browse/REL'.$relN.'_'.$relV.'/RELEASE-NOTES-'.$relN.'.'.$relV.$endL; +} + +try +{ + $out1 = null; + foreach ( $includedClasses as $classKey => $classArray ) { + $out1 = $endL . ' -- ' . $classArray['string'] . ' ('. $classArray['type'] .') --' . $endL; + $out1 .= ' ' . implode( $endL . ' ', $classArray['files'] ) . $endL . $endL; + $out2 = null; + $url = null; + foreach ( $releaseNotes as $noteKey => $noteArray ) { + if ( preg_match( '/'.$classArray['string'].'/', $noteArray['text'] ) > 0 ) { + if ( is_null( $out2 ) ) $out2 = ''; + if ( $noteArray['url'] != $url ) { + $url = $noteArray['url']; + $out2 .= ' ' . strtoupper( $noteArray['h1'] ) . $endL . $endL; + //$out2 .= $url . $endL . $endL; + } + if ( $noteArray['h2'] != '' ) $chapter[] = $noteArray['h2']; + if ( $noteArray['h3'] != '' ) $chapter[] = $noteArray['h3']; + if ( $noteArray['h4'] != '' ) $chapter[] = $noteArray['h4']; + if ( isset( $chapter ) ) { + $out2 .= '(l.'. $noteArray['l'] .') >> ' . implode( ' > ', $chapter) . ' :' . $endL; + $out2 .= $noteArray['text'] . $endL . $endL; + unset($chapter); + } + } + } + if ( !is_null( $out2 ) ) $out1 .= $out2; + else $out1 = null; + if ( !is_null( $out1 ) ) { + $out .= $out1; + $out1 = null; + } + } +} catch (\Exception $e) { + $out = $e; +} + +$report = fopen('maintenance-report.txt', 'a'); +fputs($report, $endL . $out); +fclose($report); +echo $out . $endL . ' +================================== +=> voir: maintenance-report.txt <= +================================== +'; +?> diff --git a/maintenance/Tools.php b/maintenance/Tools.php new file mode 100644 index 0000000..eade336 --- /dev/null +++ b/maintenance/Tools.php @@ -0,0 +1,45 @@ + diff --git a/mgwiki.sql b/mgwiki.sql new file mode 100644 index 0000000..c4cdd05 --- /dev/null +++ b/mgwiki.sql @@ -0,0 +1,144 @@ +-- Database schema for MGWiki (only for MYSQL database) + +CREATE TABLE /*_*/mgw_utilisateurs ( + utilisateur_id int not null auto_increment, + utilisateur_user_id int not null, + utilisateur_nom varchar(64) not null, + utilisateur_prenom varchar(64) not null, + utilisateur_level smallint, + utilisateur_updater_user_id int not null, + utilisateur_update_time datetime not null, + PRIMARY KEY (utilisateur_id) +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/mgw_utilisateurs_lookup ON /*_*/mgw_utilisateurs (utilisateur_user_id, utilisateur_nom, utilisateur_prenom); + +CREATE TABLE /*_*/mgw_archive_utilisateurs ( + utilisateur_id int not null, + utilisateur_user_id int not null, + utilisateur_nom varchar(64) not null, + utilisateur_prenom varchar(64) not null, + utilisateur_level smallint not null, + utilisateur_updater_user_id int not null, + utilisateur_update_time datetime not null, + PRIMARY KEY (utilisateur_id, utilisateur_update_time) +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/mgw_archive_utilisateurs_lookup ON /*_*/mgw_archive_utilisateurs (utilisateur_user_id, utilisateur_nom, utilisateur_prenom); + +-- Triggers +DELIMITER | +CREATE TRIGGER mgw_after_update_utilisateur AFTER UPDATE + ON /*_*/mgw_utilisateurs FOR EACH ROW + BEGIN + INSERT INTO mgw_archive_utilisateurs ( + utilisateur_id, + utilisateur_user_id, + utilisateur_nom, + utilisateur_prenom, + utilisateur_level, + utilisateur_updater_user_id, + utilisateur_update_time + ) + VALUES ( + OLD.utilisateur_id, + OLD.utilisateur_user_id, + OLD.utilisateur_nom, + OLD.utilisateur_prenom, + OLD.utilisateur_level, + OLD.utilisateur_updater_user_id, + OLD.utilisateur_update_time + ); + END | +CREATE TRIGGER mgw_after_delete_utilisateur AFTER DELETE + ON /*_*/mgw_utilisateurs FOR EACH ROW + BEGIN + INSERT INTO mgw_archive_utilisateurs ( + utilisateur_id, + utilisateur_user_id, + utilisateur_nom, + utilisateur_prenom, + utilisateur_level, + utilisateur_updater_user_id, + utilisateur_update_time + ) + VALUES ( + OLD.utilisateur_id, + OLD.utilisateur_user_id, + OLD.utilisateur_nom, + OLD.utilisateur_prenom, + OLD.utilisateur_level, + OLD.utilisateur_updater_user_id, + OLD.utilisateur_update_time + ); + END | +DELIMITER ; + +CREATE TABLE IF NOT EXISTS /*_*/mgw_institutions ( + institution_id int unsigned auto_increment not null, + institution_page_id int not null, + institution_nom varchar(64) not null, + institution_update_utilisateur_id int not null, + institution_update_time datetime not null, + PRIMARY KEY (institution_id) +) /*$wgDBTableOptions*/; + +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_institutions ( + institution_id int unsigned not null, + institution_page_id int not null, + institution_nom varchar(64) not null, + institution_update_utilisateur_id int not null, + institution_update_time datetime not null, + PRIMARY KEY (institution_id, institution_update_time) +) /*$wgDBTableOptions*/; + +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupes ( + groupe_id int unsigned auto_increment not null, + groupe_level smallint not null, + groupe_institution_id int not null, + groupe_page_id int not null, + groupe_nom varchar(64) not null, + groupe_start_time datetime, + groupe_end_time datetime, + groupe_actif boolean default true, + groupe_update_utilisateur_id int not null, + groupe_update_time datetime not null, + PRIMARY KEY (groupe_id) +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/mgw_groupes_lookup ON /*_*/mgw_groupes (groupe_page_id); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupes ( + groupe_id int not null, + groupe_level smallint not null, + groupe_institution_id int not null, + groupe_page_id int not null, + groupe_nom varchar(64) not null, + groupe_start_time datetime, + groupe_end_time datetime, + groupe_actif boolean not null, + groupe_update_time datetime not null, + groupe_update_utilisateur_id int not null, + PRIMARY KEY (groupe_id, groupe_update_time) +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/mgw_archive_groupes_lookup ON /*_*/mgw_archive_groupes (groupe_page_id); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupes_membres ( + groupe_membre_id int unsigned auto_increment not null, + groupe_membre_groupe_id int unsigned not null, + groupe_membre_utilisateur_id int unsigned not null, + groupe_membre_isadmin boolean default false, + groupe_membre_update_time datetime not null, + groupe_membre_update_utilisateur_id int not null, + PRIMARY KEY (groupe_membre_id) +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/mgw_groupes_membres_lookup ON /*_*/mgw_groupes_membres (groupe_membre_groupe_id, groupe_membre_utilisateur_id); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupes_membres ( + groupe_membre_id int unsigned not null, + groupe_membre_groupe_id int unsigned not null, + groupe_membre_utilisateur_id int unsigned not null, + groupe_membre_isadmin boolean default false, + groupe_membre_update_time datetime not null, + groupe_membre_drop_time datetime, -- uniquement lors de la suppression + groupe_membre_update_utilisateur_id int not null, + PRIMARY KEY (groupe_membre_id, groupe_membre_update_time) +) /*$wgDBTableOptions*/; +CREATE INDEX /*i*/mgw_archive_groupes_membres_lookup ON /*_*/mgw_archive_groupes_membres (groupe_membre_groupe_id, groupe_membre_utilisateur_id); diff --git a/resources/specialcheckaccounts.css b/resources/specialcheckaccounts.css new file mode 100644 index 0000000..acbbdec --- /dev/null +++ b/resources/specialcheckaccounts.css @@ -0,0 +1,34 @@ +.mgw-radio{ + display:block; + margin:auto; +} + +.mgw-select-submit{ + margin-left:50px; + margin-bottom:20px; +} +.mgw-displayusers{ + border: 1px solid black; + height:600px; + overflow: auto; +} + +.mgw-specialcheckaccount-user{ + background:#f6f6f6; + border-radius:10px; + margin:15px; + width:85%; + line-height:1.3; +} + +.mgw-specialcheckaccount-user td:nth-child(1){ + width:30px; +} +.mgw-specialcheckaccount-user td:nth-child(2){ + width:100px; +} +.mgw-specialcheckaccount-user td:nth-child(3){ +} +.mgw-specialcheckaccount-user td:nth-child(4){ + width:30px; +} diff --git a/resources/specialcheckaccounts.js b/resources/specialcheckaccounts.js new file mode 100644 index 0000000..27b8b7f --- /dev/null +++ b/resources/specialcheckaccounts.js @@ -0,0 +1,138 @@ +( function ( mw, $ ) { + + mw.mgwFiltresShow = function() { + $('.mgw-view').attr('checked', true ); + } + + mw.mgwFiltresHide = function() { + $('.mgw-hide').attr('checked', true ); + } + + mw.mgwAddAll = function() { + let inputs = document.getElementById('mgw-users-addall'); + if ( inputs.checked ) { + $('.mgw-user-add').each( function(){ + let id = $(this).val(); + if ( ! $('.mgw-user-delete[value='+id+']').is(':checked') ) { + $(this).attr('checked', true ); + } + }); + } + if ( ! inputs.checked ) { + $('.mgw-user-add').attr('checked', false ); + } + } + + mw.mgwDeleteAll = function() { + let inputs = document.getElementById('mgw-users-deleteall'); + if ( inputs.checked ) { + $('.mgw-user-delete').each( function(){ + let id = $(this).val(); + if ( ! $('.mgw-user-add[value='+id+']').is(':checked') ) { + $(this).attr('checked', true ); + } + }); + } + if ( ! inputs.checked ) { + $('.mgw-user-delete').attr('checked', false ); + } + } + + mw.mgwSelectUsers = function(){ + mw.mgwChecksConcat(); + $('#mgw-action-select').click(); + } + + mw.mgwSanitize = function(){ + mw.mgwChecksConcat(); + $('#mgw-action-sanitize').click(); + } + + mw.mgwRename = function(){ + mw.mgwChecksConcat(); + $('#mgw-action-rename').click(); + } + + mw.mgwUserMerge = function( opt ){ + mw.mgwChecksConcat(); + let merged = ''; + let m = 0; + let targeted = ''; + let t = 0; + $('.mgw-user-delete:checked').each( function(){ + m += 1; + let id = $(this).val(); + merged += $('.mgw-username[userid='+id+']').text() + ', '; + }); + if ( m < 1 ) { + alert( 'Vous devez sélectionner au moins un utilisateur à '+opt+' (cases "out").'); + return; + } + $('.mgw-user-add:checked').each( function(){ + t += 1; + let id = $(this).val(); + targeted += $('.mgw-username[userid='+id+']').text() + ' '; + }); + if ( t != 1 ) { + alert( 'Vous devez sélectionner un (et un seul) utilisateur à '+opt+' (cases "in").'); + return; + } + let message = 'ATTENTION vous êtes sur le point de '+ opt +' :\n\n' + merged + + '\n\n vers : \n\n' + targeted + '\n\nCette opération est irréversible.'; + if ( confirm( message ) ) { + if ( opt == 'supprimer') { + $('#mgw-action-merge').val('delete'); + } + $('#mgw-action-merge').click(); + } + } + + mw.mgwMerge = function(){ + mw.mgwUserMerge('fusionner') + } + + mw.mgwDelete = function(){ + mw.mgwUserMerge('supprimer') + } + + mw.mgwPopulate = function(){ + mw.mgwChecksConcat(); + $('#mgw-action-populate').click(); + } + + mw.mgwValidUsers = function(){ + $('#mgw-action-validusers').click(); + } + +// cumuler toutes les id cochée en 1 valeur "multiple" puis submit + mw.mgwChecksConcat = function(){ + var arr = []; + $('.mgw-user-add:checked').each( function(){ arr.push( $(this).val() ); }); + $('#mgw-select-addusers').val( arr.join(',') ); + arr=[]; + $('.mgw-user-delete:checked').each( function(){ arr.push( $(this).val() ); }); + $('#mgw-select-deleteusers').val( arr.join(',') ); + } + + $( function () { + + $('.mgw-user-add').click( function(){ + let id = $(this).val(); + if( $(this).is(':checked') ){ + if ( $('.mgw-user-delete[value='+id+']').is(':checked') ) { + $('.mgw-user-delete[value='+id+']').removeAttr('checked'); + } + } + }); + + $('.mgw-user-delete').click( function(){ + let id = $(this).val(); + if ( $(this).is(':checked') ){ + if ( $('.mgw-user-add[value='+id+']').is(':checked') ){ + $('.mgw-user-add[value='+id+']').removeAttr('checked'); + } + } + }); + }); + +}( mediaWiki, jQuery ) ); diff --git a/sql/addIndex-archive_groupe_lookup.sql b/sql/addIndex-archive_groupe_lookup.sql new file mode 100644 index 0000000..86c9483 --- /dev/null +++ b/sql/addIndex-archive_groupe_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_archive_groupes_lookup ON /*_*/mgw_archive_groupes (groupe_page_id); diff --git a/sql/addIndex-archive_groupes_membres_lookup.sql b/sql/addIndex-archive_groupes_membres_lookup.sql new file mode 100644 index 0000000..519865f --- /dev/null +++ b/sql/addIndex-archive_groupes_membres_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_archive_groupes_membres_lookup ON /*_*/mgw_archive_groupes_membres (groupe_membre_groupe_id, groupe_membre_utilisateur_id); diff --git a/sql/addIndex-archive_utilisateurs_lookup.sql b/sql/addIndex-archive_utilisateurs_lookup.sql new file mode 100644 index 0000000..0053ed7 --- /dev/null +++ b/sql/addIndex-archive_utilisateurs_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_archive_utilisateurs_lookup ON /*_*/mgw_archive_utilisateurs (utilisateur_nom, utilisateur_prenom); diff --git a/sql/addIndex-groupes_lookup.sql b/sql/addIndex-groupes_lookup.sql new file mode 100644 index 0000000..34a3228 --- /dev/null +++ b/sql/addIndex-groupes_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_groupes_lookup ON /*_*/mgw_groupes (groupe_page_id); diff --git a/sql/addIndex-groupes_membres_lookup.sql b/sql/addIndex-groupes_membres_lookup.sql new file mode 100644 index 0000000..381380a --- /dev/null +++ b/sql/addIndex-groupes_membres_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_groupes_membres_lookup ON /*_*/mgw_groupes_membres (groupe_membre_groupe_id, groupe_membre_utilisateur_id); diff --git a/sql/addIndex-utilisateurs_lookup.sql b/sql/addIndex-utilisateurs_lookup.sql new file mode 100644 index 0000000..cc7fefd --- /dev/null +++ b/sql/addIndex-utilisateurs_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_utilisateurs_lookup ON /*_*/mgw_utilisateurs (utilisateur_nom, utilisateur_prenom); diff --git a/sql/addTable-archive_groupes.sql b/sql/addTable-archive_groupes.sql new file mode 100644 index 0000000..faab478 --- /dev/null +++ b/sql/addTable-archive_groupes.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupes ( + groupe_id int not null, + groupe_level smallint not null, + groupe_institution_id int not null, + groupe_page_id int not null, + groupe_nom varchar(64) not null, + groupe_start_time varbinary(14), + groupe_end_time varbinary(14), + groupe_actif boolean not null, + groupe_update_time varbinary(14) not null, + groupe_update_utilisateur_id int not null, + PRIMARY KEY (groupe_id, groupe_update_time) +) /*$wgDBTableOptions*/; diff --git a/sql/addTable-archive_groupes_membres.sql b/sql/addTable-archive_groupes_membres.sql new file mode 100644 index 0000000..9067406 --- /dev/null +++ b/sql/addTable-archive_groupes_membres.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupes_membres ( + groupe_membre_id int unsigned not null, + groupe_membre_groupe_id int unsigned not null, + groupe_membre_utilisateur_id int unsigned not null, + groupe_membre_isadmin boolean default false, + groupe_membre_update_time varbinary(14) not null, + groupe_membre_drop_time varbinary(14), -- uniquement lors de la suppression + groupe_membre_update_utilisateur_id int not null, + PRIMARY KEY (groupe_membre_id, groupe_membre_update_time) +) /*$wgDBTableOptions*/; diff --git a/sql/addTable-archive_institutions.sql b/sql/addTable-archive_institutions.sql new file mode 100644 index 0000000..cc5cfa1 --- /dev/null +++ b/sql/addTable-archive_institutions.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_institutions ( + institution_id int unsigned not null, + institution_page_id int not null, + institution_nom varchar(64) not null, + institution_update_utilisateur_id int not null, + institution_update_time varbinary(14) not null, + PRIMARY KEY (institution_id, institution_update_time) +) /*$wgDBTableOptions*/; diff --git a/sql/addTable-archive_utilisateurs.sql b/sql/addTable-archive_utilisateurs.sql new file mode 100644 index 0000000..dbcb565 --- /dev/null +++ b/sql/addTable-archive_utilisateurs.sql @@ -0,0 +1,9 @@ +CREATE TABLE /*_*/mgw_archive_utilisateurs ( + utilisateur_id int not null, + utilisateur_nom varchar(64) not null, + utilisateur_prenom varchar(64) not null, + utilisateur_level smallint not null, + utilisateur_update_utilisateur_id int not null, + utilisateur_update_time varbinary(14) not null, + PRIMARY KEY (utilisateur_id, utilisateur_update_time) +) /*$wgDBTableOptions*/; diff --git a/sql/addTable-groupes.sql b/sql/addTable-groupes.sql new file mode 100644 index 0000000..b69adc8 --- /dev/null +++ b/sql/addTable-groupes.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupes ( + groupe_id int unsigned auto_increment not null, + groupe_level smallint not null, + groupe_institution_id int not null, + groupe_page_id int not null, + groupe_nom varchar(64) not null, + groupe_start_time varbinary(14), + groupe_end_time varbinary(14), + groupe_actif boolean default true, + groupe_update_utilisateur_id int not null, + groupe_update_time varbinary(14) not null, + PRIMARY KEY (groupe_id) +) /*$wgDBTableOptions*/; diff --git a/sql/addTable-groupes_membres.sql b/sql/addTable-groupes_membres.sql new file mode 100644 index 0000000..51c4cc1 --- /dev/null +++ b/sql/addTable-groupes_membres.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupes_membres ( + groupe_membre_id int unsigned auto_increment not null, + groupe_membre_groupe_id int unsigned not null, + groupe_membre_utilisateur_id int unsigned not null, + groupe_membre_isadmin boolean default false, + groupe_membre_update_time varbinary(14) not null, + groupe_membre_update_utilisateur_id int not null, + PRIMARY KEY (groupe_membre_id) +) /*$wgDBTableOptions*/; diff --git a/sql/addTable-institutions.sql b/sql/addTable-institutions.sql new file mode 100644 index 0000000..bc48317 --- /dev/null +++ b/sql/addTable-institutions.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_institutions ( + institution_id int unsigned auto_increment not null, + institution_page_id int not null, + institution_nom varchar(64) not null, + institution_update_utilisateur_id int not null, + institution_update_time varbinary(14) not null, + PRIMARY KEY (institution_id) +) /*$wgDBTableOptions*/; diff --git a/sql/addTable-utilisateurs.sql b/sql/addTable-utilisateurs.sql new file mode 100644 index 0000000..7b20054 --- /dev/null +++ b/sql/addTable-utilisateurs.sql @@ -0,0 +1,9 @@ +CREATE TABLE /*_*/mgw_utilisateurs ( + utilisateur_id int not null, + utilisateur_nom varchar(64) not null, + utilisateur_prenom varchar(64) not null, + utilisateur_level smallint, + utilisateur_update_utilisateur_id int not null, + utilisateur_update_time varbinary(14) not null, + PRIMARY KEY (utilisateur_id) +) /*$wgDBTableOptions*/; From b0121a02b67e285ea6c9c7e59db613e5a5ac0abc Mon Sep 17 00:00:00 2001 From: Looxloox Date: Fri, 11 Dec 2020 15:03:18 +0100 Subject: [PATCH 13/15] construction des pages special:admin en cours ... --- Hooks.php | 106 +- README.md | 3 + extension.json | 27 +- i18n/en.json | 6 +- i18n/fr.json | 10 +- i18n/qqq.json | 6 +- includes/Classes/MGWSpecialAdmin.php | 401 +++++++ includes/Classes/MGWStatus.php | 67 ++ includes/Classes/MGWUser.php | 593 ++++++++++ includes/Foreign/MGWRenameuser.php | 26 +- includes/Foreign/MGWReplaceText.php | 264 +++++ includes/Foreign/MGWUserMerge.php | 78 +- includes/SpecialAccountRequest.php | 31 +- includes/SpecialAdminGroupTypes.php | 473 ++++++++ includes/SpecialCheckAccounts.php | 1018 +++++++++-------- includes/SpecialCheckGroups.php | 487 ++++++++ includes/SpecialMgwikiTest.php | 188 +++ includes/Utilities/GetMessage.php | 41 + includes/Utilities/HtmlFunctions.php | 31 + includes/Utilities/JsonToForm.php | 28 +- includes/Utilities/MgwDataFunctions.php | 335 ++++++ includes/Utilities/PagesFunctions.php | 58 +- includes/Utilities/PhpFunctions.php | 73 ++ includes/Utilities/UsersFunctions.php | 401 ++++++- maintenance/Tests/bacasable.php | 4 + maintenance/UpdateSQLfiles.php | 35 + mgwiki.sql | 223 ++-- resources/ext.mgwiki-dev.css | 5 + resources/ext.mgwiki-dev.js | 15 + resources/ext.mgwiki-specialadmin.css | 34 + resources/ext.mgwiki-specialadmin.js | 95 ++ resources/specialadmingrouptypes.css | 39 + resources/specialadmingrouptypes.js | 3 + resources/specialcheckaccounts.css | 45 +- resources/specialcheckaccounts.js | 155 ++- resources/specialcheckgroups.css | 34 + resources/specialcheckgroups.js | 86 ++ sql/addIndex-archive_groupe_lookup.sql | 1 - ...ddIndex-archive_groupes_membres_lookup.sql | 1 - sql/addIndex-archive_utilisateurs_lookup.sql | 1 - sql/addIndex-groupes_lookup.sql | 1 - sql/addIndex-groupes_membres_lookup.sql | 1 - sql/addIndex-mgw_archive_groupe_lookup.sql | 1 + ...Index-mgw_archive_groupe_membre_lookup.sql | 1 + ...ddIndex-mgw_archive_groupe_type_lookup.sql | 1 + ...ddIndex-mgw_archive_utilisateur_lookup.sql | 1 + sql/addIndex-mgw_groupe_lookup.sql | 1 + sql/addIndex-mgw_groupe_membre_lookup.sql | 1 + sql/addIndex-mgw_groupe_type_lookup.sql | 1 + sql/addIndex-mgw_utilisateur_lookup.sql | 1 + sql/addIndex-utilisateurs_lookup.sql | 1 - sql/addTable-archive_groupes.sql | 13 - sql/addTable-archive_groupes_membres.sql | 10 - sql/addTable-archive_institutions.sql | 8 - sql/addTable-archive_utilisateurs.sql | 9 - sql/addTable-groupes.sql | 13 - sql/addTable-groupes_membres.sql | 9 - sql/addTable-institutions.sql | 8 - sql/addTable-mgw_archive_groupe.sql | 1 + sql/addTable-mgw_archive_groupe_membre.sql | 1 + sql/addTable-mgw_archive_groupe_type.sql | 1 + sql/addTable-mgw_archive_institution.sql | 1 + sql/addTable-mgw_archive_utilisateur.sql | 1 + sql/addTable-mgw_groupe.sql | 1 + sql/addTable-mgw_groupe_membre.sql | 1 + sql/addTable-mgw_groupe_type.sql | 1 + sql/addTable-mgw_institution.sql | 1 + sql/addTable-mgw_institution_groupe.sql | 1 + sql/addTable-mgw_utilisateur.sql | 1 + sql/addTable-utilisateurs.sql | 9 - 70 files changed, 4732 insertions(+), 896 deletions(-) create mode 100644 includes/Classes/MGWSpecialAdmin.php create mode 100644 includes/Classes/MGWStatus.php create mode 100644 includes/Classes/MGWUser.php create mode 100644 includes/Foreign/MGWReplaceText.php create mode 100644 includes/SpecialAdminGroupTypes.php create mode 100644 includes/SpecialCheckGroups.php create mode 100644 includes/SpecialMgwikiTest.php create mode 100644 includes/Utilities/GetMessage.php create mode 100644 includes/Utilities/HtmlFunctions.php create mode 100644 includes/Utilities/MgwDataFunctions.php create mode 100644 maintenance/Tests/bacasable.php create mode 100644 maintenance/UpdateSQLfiles.php create mode 100644 resources/ext.mgwiki-specialadmin.css create mode 100644 resources/ext.mgwiki-specialadmin.js create mode 100644 resources/specialadmingrouptypes.css create mode 100644 resources/specialadmingrouptypes.js create mode 100644 resources/specialcheckgroups.css create mode 100644 resources/specialcheckgroups.js delete mode 100644 sql/addIndex-archive_groupe_lookup.sql delete mode 100644 sql/addIndex-archive_groupes_membres_lookup.sql delete mode 100644 sql/addIndex-archive_utilisateurs_lookup.sql delete mode 100644 sql/addIndex-groupes_lookup.sql delete mode 100644 sql/addIndex-groupes_membres_lookup.sql create mode 100644 sql/addIndex-mgw_archive_groupe_lookup.sql create mode 100644 sql/addIndex-mgw_archive_groupe_membre_lookup.sql create mode 100644 sql/addIndex-mgw_archive_groupe_type_lookup.sql create mode 100644 sql/addIndex-mgw_archive_utilisateur_lookup.sql create mode 100644 sql/addIndex-mgw_groupe_lookup.sql create mode 100644 sql/addIndex-mgw_groupe_membre_lookup.sql create mode 100644 sql/addIndex-mgw_groupe_type_lookup.sql create mode 100644 sql/addIndex-mgw_utilisateur_lookup.sql delete mode 100644 sql/addIndex-utilisateurs_lookup.sql delete mode 100644 sql/addTable-archive_groupes.sql delete mode 100644 sql/addTable-archive_groupes_membres.sql delete mode 100644 sql/addTable-archive_institutions.sql delete mode 100644 sql/addTable-archive_utilisateurs.sql delete mode 100644 sql/addTable-groupes.sql delete mode 100644 sql/addTable-groupes_membres.sql delete mode 100644 sql/addTable-institutions.sql create mode 100644 sql/addTable-mgw_archive_groupe.sql create mode 100644 sql/addTable-mgw_archive_groupe_membre.sql create mode 100644 sql/addTable-mgw_archive_groupe_type.sql create mode 100644 sql/addTable-mgw_archive_institution.sql create mode 100644 sql/addTable-mgw_archive_utilisateur.sql create mode 100644 sql/addTable-mgw_groupe.sql create mode 100644 sql/addTable-mgw_groupe_membre.sql create mode 100644 sql/addTable-mgw_groupe_type.sql create mode 100644 sql/addTable-mgw_institution.sql create mode 100644 sql/addTable-mgw_institution_groupe.sql create mode 100644 sql/addTable-mgw_utilisateur.sql delete mode 100644 sql/addTable-utilisateurs.sql diff --git a/Hooks.php b/Hooks.php index 28504c6..44abaf0 100644 --- a/Hooks.php +++ b/Hooks.php @@ -1,6 +1,7 @@ 'U0', + 1 => 'U1', + 2 => 'U2', + 3 => 'U3', + 4 => 'sysop' + ]; + } + /** * Chargement du module MGWikiDev */ @@ -53,55 +85,51 @@ public static function onLoadExtensionSchemaUpdates( DatabaseUpdater $updater ) $dir = __DIR__ . '/sql'; $tables = array( - 'utilisateurs', - 'archive_utilisateurs', - 'institutions', - 'archive_institutions', - 'groupes', - 'archive_groupes', - 'groupes_membres', - 'archive_groupes_membres' + 'mgw_utilisateur', + 'mgw_archive_utilisateur', + 'mgw_institution', + 'mgw_archive_institution', + 'mgw_groupe', + 'mgw_archive_groupe', + 'mgw_groupe_membre', + 'mgw_archive_groupe_membre', + 'mgw_groupe_type', + 'mgw_archive_groupe_type', + 'mgw_institution_groupe' ); - foreach( $tables as $key => $table ) { + foreach( $tables as $table ) { $tableSQLFile = "$dir/addTable-" . $table . ".sql"; $indexSQLfile = "$dir/addIndex-" . $table . "_lookup.sql"; - $updater->addExtensionTable( 'mgw_' . $table, $baseSQLFile ); + $updater->addExtensionTable( 'mgw_' . $table, $tableSQLFile ); if ( file_exists($indexSQLfile) ) { $updater->addExtensionIndex( $table, $table.'_lookup', $indexSQLfile ); } } + } - // $updater->addExtensionTable( 'mgw_utilisateurs', $baseSQLFile ); - // malgré le premier argument toutes les tables et les index sont insérés. + /** + * login avec mail + authentification insensible à la casse + */ + public static function onAuthChangeFormFields( $requests, $fieldInfo, &$formDescriptor, $action ) { + global $wgRequest; + // uniquement pour le formulaire de login + if ( $wgRequest->getGlobalRequestURL() == '/wiki/index.php/Sp%C3%A9cial:Connexion' ) + { + $formDescriptor['username']['label'] = 'E-mail ou nom d\'utilisateur'; + $formDescriptor['username']['filter-callback'] = function ( $val, $array ) { + $val = htmlspecialchars($val, ENT_QUOTES ); - /* fonctions disponibles: - $updater->addExtensionField( 'flow_revision', 'rev_last_edit_id', "$dir/db_patches/patch-revision_last_editor.sql" ); - $updater->addExtensionIndex( 'flow_workflow', 'flow_workflow_lookup', "$dir/db_patches/patch-workflow_lookup_idx.sql" ); + // si mail valide on récupère le nom d'utilisateur correspondant + if ( preg_match( '/@/', $val ) > 0 ) + $r = UserF::emailExists( $val ); - if ( $updater->getDB()->getType() === 'sqlite' ) { - $updater->modifyExtensionField( 'flow_summary_revision', 'summary_workflow_id', - "$dir/db_patches/patch-summary2header.sqlite.sql" ); - } else { - // renames columns, alternate patch is above for sqlite - $updater->modifyExtensionField( 'flow_summary_revision', 'summary_workflow_id', - "$dir/db_patches/patch-summary2header.sql" ); - } + // on corrige les erreurs de casse + else $r = UserF::userExists( $val, false ); - $updater->dropExtensionTable( 'flow_definition', - "$dir/db_patches/patch-drop_definition.sql" ); - $updater->dropExtensionField( 'flow_workflow', 'workflow_user_ip', - "$dir/db_patches/patch-drop_workflow_user.sql" ); - $updater->dropExtensionIndex( 'flow_ext_ref', 'flow_ext_ref_pk', - "$dir/db_patches/patch-remove_unique_ref_indices.sql" ); - - require_once __DIR__ . '/maintenance/FlowUpdateRecentChanges.php'; - $updater->addPostDatabaseUpdateMaintenance( FlowUpdateRecentChanges::class ); - - if ( $updater->updateRowExists( 'FlowSetUserIp' ) ) { - $updater->dropExtensionField( 'flow_revision', 'rev_user_text', - "$dir/db_patches/patch-remove_usernames_2.sql" ); - } - */ - } + if ( !$r ) return htmlspecialchars_decode( $val, ENT_QUOTES ); // = utilisateur inconnu + else return $r; + }; + } + } } diff --git a/README.md b/README.md index 824484b..fb01895 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,11 @@ Modules inclus: - skinning: - resources/ext.mgwiki-dev.js & css - images/php + - login avec email proposé ( Hooks::onAuthChangeFormFields ) Todo: +* /var/www/html/wiki/includes/preferences/DefaultPreferencesFactory.php : modif automatique +* /var/www/html/wiki/includes/specials/SpecialChangeEmail.php : modif automatique * includes/SpecialAccount.php: use WebResponse::setCookie() instead of setcookie() when upgrading Mediawiki >= 1.35 Core changes: diff --git a/extension.json b/extension.json index 496f22e..875f028 100644 --- a/extension.json +++ b/extension.json @@ -17,6 +17,7 @@ "AutoloadNamespaces": { "MediaWiki\\Extension\\MGWikiDev\\": "includes/", "MediaWiki\\Extension\\MGWikiDev\\Utilities\\": "includes/Utilities/", + "MediaWiki\\Extension\\MGWikiDev\\Classes\\": "includes/Classes/", "MediaWiki\\Extension\\MGWikiDev\\Api\\": "includes/Api/", "MediaWiki\\Extension\\MGWikiDev\\Foreign\\": "includes/Foreign/" }, @@ -25,15 +26,19 @@ }, "SpecialPages": { "SpecialAccountRequest": "MediaWiki\\Extension\\MGWikiDev\\SpecialAccountRequest", - "SpecialCheckAccounts": "MediaWiki\\Extension\\MGWikiDev\\SpecialCheckAccounts" - + "SpecialCheckAccounts": "MediaWiki\\Extension\\MGWikiDev\\SpecialCheckAccounts", + "SpecialCheckGroups": "MediaWiki\\Extension\\MGWikiDev\\SpecialCheckGroups", + "SpecialMgwikiTest": "MediaWiki\\Extension\\MGWikiDev\\SpecialMgwikiTest", + "SpecialAdminGroupTypes": "MediaWiki\\Extension\\MGWikiDev\\SpecialAdminGroupTypes" }, "Hooks": { "BeforePageDisplay": "MGWikiDevHooks::onBeforePageDisplay", "ApiAllow": "MGWikiDevHooks::onApiAllow", "ParserFirstCallInit": "MGWikiDevHooks::onParserFirstCallInit", - "LoadExtensionSchemaUpdates": "MGWikiDevHooks::onLoadExtensionSchemaUpdates" + "LoadExtensionSchemaUpdates": "MGWikiDevHooks::onLoadExtensionSchemaUpdates", + "AuthChangeFormFields": "MGWikiDevHooks::onAuthChangeFormFields" }, + "callback": "MGWikiDevHooks::onExtensionLoad", "APIModules": { "getjson" : "MediaWiki\\Extension\\MGWikiDev\\Api\\ApiGetJson" }, @@ -53,9 +58,25 @@ "ext.mgwiki-specialaccountrequest": { "packageFiles": [ "specialaccountrequest.js" ] }, + "ext.mgwiki-specialadmin": { + "packageFiles": [ "ext.mgwiki-specialadmin.js" ], + "styles": [ "ext.mgwiki-specialadmin.css" ] + }, "ext.mgwiki-specialcheckaccounts": { "packageFiles": [ "specialcheckaccounts.js" ], "styles": [ "specialcheckaccounts.css" ] + }, + "ext.mgwiki-specialcheckgroups": { + "packageFiles": [ "specialcheckgroups.js" ], + "styles": [ "specialcheckgroups.css" ] + }, + "ext.mgwiki-specialadmingrouptypes": { + "dependencies": [ + "oojs-ui-core", + "oojs-ui-windows" + ], + "packageFiles": [ "specialadmingrouptypes.js" ], + "styles": [ "specialadmingrouptypes.css" ] } }, "ExtensionMessagesFiles": { diff --git a/i18n/en.json b/i18n/en.json index 9b0f62d..fb2f352 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -8,5 +8,9 @@ "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", "specialpages-group-mgwiki": "MGWiki", "specialaccountrequest": "Demande de création de compte", - "specialcheckaccounts": "Administration des utilisateurs" + "specialcheckaccounts": "Maintenance : utilisateurs", + "specialcheckgroups": "Maintenance : groupes", + "specialadmingrouptypes": "Maintenance: types de groupes", + "specialmgwikilogin": "Login", + "specialmgwikilogin-unknownuser": "utilisateur inconnu" } diff --git a/i18n/fr.json b/i18n/fr.json index 4ee38db..9a24bf6 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -8,7 +8,13 @@ "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", "specialpages-group-mgwiki": "MGWiki", "specialaccountrequest": "Demande de création de compte", - "specialcheckaccounts": "Administration des utilisateurs", + "specialcheckaccounts": "Maintenance : utilisateurs", + "specialcheckgroups": "Maintenance : groupes", + "specialadmingrouptypes": "Maintenance: types de groupes", "apihelp-getjson-summary": "Permet de récupérer des données json", - "apihelp-getjson-param-service": "'messages' -> messages d'interface pour MGWiki
'specialAccountRequest' -> choix d'institutions pour la page Special:Demande_de_création_de_compte" + "apihelp-getjson-param-service": "'messages' -> messages d'interface pour MGWiki
'specialAccountRequest' -> choix d'institutions pour la page Special:Demande_de_création_de_compte", + "usermerge-badoldid": "user_id de l'ancien utilisateur inexistant", + "usermerge-badnewuserid": "user_id du nouvel utilisateur inexistant", + "specialmgwikilogin": "Login", + "specialmgwikilogin-unknownuser": "utilisateur inconnu" } diff --git a/i18n/qqq.json b/i18n/qqq.json index 9b0f62d..fb2f352 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -8,5 +8,9 @@ "mgwiki-dev-desc": "{{desc|name=MGWikiDev|url=https://mgwiki.univ-lyon1.fr}}", "specialpages-group-mgwiki": "MGWiki", "specialaccountrequest": "Demande de création de compte", - "specialcheckaccounts": "Administration des utilisateurs" + "specialcheckaccounts": "Maintenance : utilisateurs", + "specialcheckgroups": "Maintenance : groupes", + "specialadmingrouptypes": "Maintenance: types de groupes", + "specialmgwikilogin": "Login", + "specialmgwikilogin-unknownuser": "utilisateur inconnu" } diff --git a/includes/Classes/MGWSpecialAdmin.php b/includes/Classes/MGWSpecialAdmin.php new file mode 100644 index 0000000..0bb2dfd --- /dev/null +++ b/includes/Classes/MGWSpecialAdmin.php @@ -0,0 +1,401 @@ +triOptions = []; + * $this->filtreOptions = []; + * $this->filtreOptionsTitles = []; + * $this->headActions = [ 'action' => 'label' ] + * $this->headHiddenFields = [ 'name' => 'value' ] + */ + public function __construct( $specialSettings ) + { + parent::__construct( ...$specialSettings ); + + $out = $this->getOutput(); + $out->addModules('ext.mgwiki-specialadmin'); + } + + + /******************* + ** TO OVERRIDE ** + *******************/ + + /** + * TODO OVERRIDE + */ + public function execute( $sub ) + { + # les fonctions doivent être appelées dans l'ordre suivant: + $reqData = $this->set_reqData(); // TO OVERRIDE + $this->set_select( $reqData ); + $this->set_check( $reqData ); + + // (...) + + $out->addHTML( $this->makeHeadForm() ); + + $out->addHTML( $this->makeHeadTable() ); // TO OVERRIDE (optionnel) + + // construction des données + if ( isset( $reqData['action'] ) && $reqData['action'] == 'show' ) { + $showArray = $this->getShowArray( ); + } + + // définition des actions (...) + + $this->sortShowArray( $showArray, $actionInfo = [] ); + + $out->addHTML( $this->displayShowArray( $showArray ) ); // TO OVERRIDE + + $out->addHTML( $this->displayFooterActions() ); // TO OVERRIDE (optionnel) + } + + /** + * TODO OVERRIDE + */ + private function set_reqData() { + return $this->getRequest()->getPostValues(); + } + + /** + * TODO OVERRIDE + */ + private function makeHeadTable () {} + + /** + * TODO OVERRIDE + * @param array $row + * @return string + */ + private function displayShowArray ( &$row ) { + $content = ''; + foreach( $showArray as $row ) { + $table = ' (...) '; + $class = 'active'; // 'active'|'inactive'|'deleted' + $content .= $this->tagRowTable( $table, $class ); + // (...) + } + return $this->tagShowArray( $content ); + } + + /** + * TODO OVERRIDE + * @return array $showArray + */ + private function getShowArray () {} + + /** + * TODO OVERRIDE + */ + private function displayFooterActions() {} + + + /*********************** + ** TO USE fuctions ** + ***********************/ + /** + * TODO USE in $this->displayShowArray() + * @param array $hidden [ 'name' => 'value' ] + * @param string $ctrlFn 'mw.maFonction()' + */ + private function actionButton ( + $action, + $label, + $check = [ 'add' => 'false', 'del' => 'false' ], + $ctrlFn = '', + $hidden = [] ) + { + $extra = ' extra="' . json_encode( $hidden ) . '"'; + return ''; + } + + /** + * TODO USE in $this->displayShowArray() + */ + private function tagShowArray( $content ) { + return '
' . $content . '
'; + } + + /** + * TODO USE in $this->displayShowArray() + * @param string $content ' (...) ' + * @param string $class 'active'|'inactive'|'deleted' + */ + private function tagRowTable( $content, $class ) { + return '' . $content . '
'; + } + + /** + * TODO USE in $this->displayShowArray() + */ + private function addBox ( $value ) { + return ''; + } + + /** + * TODO USE in $this->displayShowArray() + */ + private function delBox ( $value ) { + return ''; + } + + /** + * TODO USE in $this->displayShowArray() + */ + private function addAllBox ( $label ) { + return ' + '; + } + + /** + * TODO USE in $this->displayShowArray() + */ + private function delAllBox ( $label ) { + return ' + '; + } + + + /*********************** + ** Native fuctions ** + ***********************/ + + /** + * @param array $reqData + */ + private function set_select( &$reqData ) + { + global $tri; + $select = &$this->select; + + $tri[1] = ( isset( $reqData['tri1'] ) ) + ? $reqData['tri1'] : 'user_id'; + + $tri[2] = ( isset( $reqData['tri2'] ) ) + ? $reqData['tri2'] : ''; + + foreach ( $this->triOptions as $value ) { + + if ( isset($reqData['tri1'] ) && $reqData['tri1'] == $value ) { + $select[1][$value] = 'selected'; + } else { + $select[1][$value] = ''; + } + + if ( isset($reqData['tri2'] ) && $reqData['tri2'] == $value ) { + $select[2][$value] = 'selected'; + } else { + $select[2][$value] = ''; + } + + if ( !in_array( 'selected', $select[2] ) ) { + $select[2]['...'] = 'selected'; + } else { + $select[2]['...'] = ''; + } + } + + return $tri; + } + + /** + * @param array $reqData + */ + private function set_check( &$reqData ) + { + $check = &$this->check; + + foreach ( $this->filtreOptions as $value ) { + + $check[$value]['view'] = + ( isset( $reqData[$value] ) && $reqData[$value] == 'view' ) + ? 'checked' : ''; + + $check[$value]['hide'] = + ( isset( $reqData[$value] ) && $reqData[$value] == 'hide' ) + ? 'checked' : ''; + + $check[$value]['only'] = + ( isset( $reqData[$value] ) && $reqData[$value] == 'only' ) + ? 'checked' : ''; + + if ( !in_array( + 'checked', + [ + $check[$value]['view'], + $check[$value]['hide'], + $check[$value]['only'] + ] ) + ) { + $check[$value]['view'] = 'checked'; + } + } + } + + /** + * @param string $msg_prefix + * le préfixe des messages à récupérer + * + * @return string + */ + private function makeHeadForm ( $msg_prefix ) + { + $select = &$this->select; + $check = &$this->check; + + $return = ' +
'; + + // TRI + $return .= ' +
Tri + + + + + +
+ + + + + +
+
'; + // FILTRES + $return .= ' +
Filtres + + + + + + + '; + $writeTitle = false; + foreach ( $this->filtreOptions as $filtreOption ) { + foreach ( $this->filtreOptionsTitles as $title => $titleOptions ) { + if ( in_array( $filtreOption, $titleOptions ) && !$writeTitle ){ + $return .= ''; + $writeTitle = true; + } + elseif ( !in_array( $filtreOption, $titleOptions ) ) + $writeTitle = false; + } + $return .= ' + + + + + + '; + } + $return .= ' +
seul
' . Msg::get( $msg_prefix.'-'.$title ) . '
' . Msg::get( $msg_prefix.'-'.$filtreOption ) . '
+
'; + + if ( isset( $this->headActions ) ) { + foreach ( $this->headActions as $action => $label ) { + $return .= $this->actionButton( $action, $label ); + } + } + + if ( isset( $this->headHiddenFields ) ) { + foreach ( $this->headHiddenFields as $field => $value ) { + $return .= ''; + } + } + + $return .= ' + + + + +
'; + return $return; + } + + /** + * @param array $showArray + * @param array $actionInfo + * @return void + */ + private function sortShowArray( &$showArray, $actionInfo = [] ) { + if ( isset( $showArray ) ) { + // tri d'un tableau à 2 dimentions + uasort ( $showArray , function ($a, $b) { + global $tri; + if ( $a[$tri[1]] == $b[$tri[1]] ) { + if ( $tri[2] != '' ) { + if ( $a[$tri[2]] == $b[$tri[2]] ) return 0; + return ( $a[$tri[2]] < $b[$tri[2]] ) ? -1 : 1; + } + return 0; + } + return ( $a[$tri[1]] < $b[$tri[1]] ) ? -1 : 1; + }); + } + else $showArray = []; + + if ( count( $actionInfo ) > 0 ) { + $showArray = array_merge( $actionInfo, $showArray ); + } + } + + /** + * @param array $get associative array of GET request parameters + * [key => value] + */ + private function selfURL( array $get = [] ) { + return SpecialPage::getTitleFor( 'specialadmingrouptypes' )->getLinkURL( $get ); + } + + protected function getGroupName() { + return 'mgwiki'; + } +} diff --git a/includes/Classes/MGWStatus.php b/includes/Classes/MGWStatus.php new file mode 100644 index 0000000..0a2bb56 --- /dev/null +++ b/includes/Classes/MGWStatus.php @@ -0,0 +1,67 @@ +set_done( $done ); + $status->set_message( $message ); + if ( !is_null( $extra ) ) $status->set_extra( $extra ); + return $status; + } + + public static function newDone( $message, $extra = null ) { + return MGWStatus::new( true, $message, $extra ); + } + + public static function newFailed( $message, $extra = null ) { + return MGWStatus::new( false, $message, $extra ); + } + + public function set_done( $bool ) { + $this->done = $bool; + } + + public function set_message( $string ) { + $this->message = $string; + } + + public function set_extra( $extra ) { + $this->extra = $extra; + } + + public function done() { + return $this->done; + } + + public function mess() { + return $this->message; + } + + public function extra() { + return $this->extra; + } +} diff --git a/includes/Classes/MGWUser.php b/includes/Classes/MGWUser.php new file mode 100644 index 0000000..af684ce --- /dev/null +++ b/includes/Classes/MGWUser.php @@ -0,0 +1,593 @@ +set_user_id( $user_id ); + $r = $MGWuser->userExists( $user_id ); + if ( !$r ) + return null; + + // données de la table user + $MGWuser->set_mw_exists( true ); + $MGWuser->retrieveBaseData(); + + // données de la table mgw_utilisateurs + $r = $MGWuser->mgwUserExists( 'user_id', $user_id ); + if ( !$r ) $MGWuser->set_mgw_exists( false ); + else { + $MGWuser->set_mgw_exists( true ); + $MGWuser->retrieveMGWData(); + } + + // données de la page utilisateur + $r = PageF::getPageFromTitleText( $MGWuser->data['user_name'], NS_USER, true ); + if ( is_null( $r ) ) $MGWuser->set_page_exists( false ); + else { + $MGWuser->set_page_exists( true ); + $MGWuser->retrievePageData( $r ); + } + + // définition des variables principales + $MGWuser->set_nom(); + $MGWuser->set_prenom(); + $MGWuser->set_email(); + + return $MGWuser; + } + + ######################### + ## Fonctions publiques ## + ######################### + + // Setters + public function set_user_id( $user_id ) { + $this->user_id = $user_id; + } + + public function set_level( $level ) { + $this->level = $level; + } + + public function set_nom( $nom = null ) { + + if ( is_null( $nom ) && isset( $this->data['utilisateur_nom'] ) ) + $this->nom = $this->data['utilisateur_nom']; + + elseif ( is_null( $nom ) && isset( $this->data['page_template_nom'] ) ) + $this->nom = $this->data['page_template_nom']; + + elseif ( !is_null( $nom ) ) + $this->nom = $nom; + + else $this->nom = null; + } + + public function set_prenom( $prenom = null ) { + + if ( is_null( $prenom ) && isset( $this->data['utilisateur_prenom'] ) ) + $this->prenom = $this->data['utilisateur_prenom']; + + elseif ( is_null( $prenom ) && isset( $this->data['page_template_prenom'] ) ) + $this->prenom = $this->data['page_template_prenom']; + + elseif ( !is_null( $prenom ) ) + $this->prenom = $prenom; + + else $this->prenom = null; + } + + public function set_email( $email = null ) { + + if ( is_null( $email ) && isset( $this->data['user_email'] ) ) + $this->email = $this->data['user_email']; + + elseif ( is_null( $email ) && isset( $this->data['page_template_email'] ) ) + $this->email = $this->data['page_template_email']; + + elseif ( !is_null( $email ) ) + $this->email = $email; + + else $this->email = null; + } + + public function set_mw_exists( $mw_exists ) { + $this->mw_exists = $mw_exists; + } + + public function set_mgw_exists( $mgw_exists ) { + $this->mgw_exists = $mgw_exists; + } + + public function set_page_exists( $page_exists ) { + $this->page_exists = $page_exists; + } + + // getters + public function get_user_id( ) { + return $this->user_id; + } + + public function get_level( ) { + return $this->level; + } + + public function get_nom( ) { + return $this->nom; + } + + public function get_prenom( ) { + return $this->prenom; + } + + public function get_email( ) { + return $this->email; + } + + public function get_mw_exists( ) { + return $this->mw_exists; + } + + public function get_mgw_exists( ) { + return $this->mgw_exists; + } + + public function get_page_exists( ) { + return $this->page_exists; + } + + public function get_data( ) { + return $this->data; + } + + /** + * @return string 'Prénom NOM' + * construit à partir de $this->prenom et $this->nom + */ + public function make_name() { + if ( isset( $this->prenom ) && isset( $this->nom ) ) { + return $this->prenom . ' ' . $this->nom; + } + return false; + } + + public function same_names() { + + if ( !$this->mw_exists || !$this->page_exists ) + return false; + + if ( !$this->data['page_template'] || + is_null( $this->data['page_template_nom'] ) || + is_null( $this->data['page_template_prenom'] ) ) + return false; + + if ( $this->data['user_name'] != $this->data['page_template_prenom'] . ' ' . $this->data['page_template_nom'] ) + return false; + + return true; + } + + public function same_email() { + + if ( !$this->mw_exists || !$this->page_exists ) + return false; + + if ( !$this->data['page_template'] || is_null( $this->data['page_template_email'] ) ) + return false; + + if ( $this->data['user_email'] != $this->data['page_template_email'] ) + return false; + + return true; + } + + /** + * définit : $user_email = $page_template_email + * envoie un mail de confirmation + */ + public function update_email() { + $user = $this->getUserFromId( $this->user_id ); + $user->setEmail( $this->data['page_template_email'] ); + $user->saveSettings(); + $user->sendConfirmationMail( 'changed' ); + } + + /** + * définit : $user_email = null + */ + public function delete_email() { + $user = $this->getUserFromId( $this->user_id ); + $user->setEmail( null ); + $user->saveSettings(); + } + + + /** + * définit : $user_groups = null + * @return string message de retour + */ + public function delete_all_groups() { + $user = $this->getUserFromId( $this->user_id ); + $deleted = []; + $kept = []; + foreach ( $user->getGroups() as $group ) { + if ( $group != 'sysop' ){ + $user->removeGroup( $group ); + $deleted[] = $group; + } + else { + $kept[] = $group; + } + } + $user->saveSettings(); + + if ( count( $deleted ) > 0 ){ + $mess = 'Groupes supprimés: ' . implode( ',', $deleted ); + } + else { + $mess = 'Aucun groupe n\'a été supprimé'; + } + + if ( count( $kept ) > 0 ){ + $mess .= '
Groupes conservés: ' . implode( ',', $kept ); + } + else { + $mess .= '
Aucun groupe n\'a été conservé.'; + } + + return $mess; + } + + public function same_real_name() { + if ( $this->data['user_real_name'] != $this->data['user_name'] ) { + return false; + } + return true; + } + + /** + * définit : $user_real_name = $user_name + */ + public function update_real_name() { + $user = $this->getUserFromId( $this->user_id ); + $user->setRealName( $this->data['user_name'] ); + $user->saveSettings(); + } + + /** + * définit : $user_real_name = null + */ + public function delete_real_name() { + $user = $this->getUserFromId( $this->user_id ); + $user->setRealName( null ); + $user->saveSettings(); + } + + public function sanitize_nom() { + if ( !isset( $this->nom ) ) { + return false; + } + $this->nom = strtoupper( $this->nom ); + return true; + } + + public function sanitize_prenom() { + if ( !isset( $this->prenom ) ) { + return false; + } + $space = false; + $str = $this->prenom; + if ( preg_match('/ /', $str ) > 0 ) { + $str = str_replace(' ','-',$str); + $space = true; + } + + $str = explode( '-', $str ); + foreach ( $str as $key => $substr ) { + $str[$key] = ucfirst( strtolower( $substr ) ); + } + + $this->prenom = ( $space ) ? implode( ' ', $str ) : implode( '-', $str ); + + return true; + } + + /** + * FONCTION TEMPORAIRE + * @param string $summary le résumé de modification de page + * @return MGWStatus + */ + public function update_page_template_names( $summary ) { + + $mess = ''; + + if ( $this->nom != $this->data['page_template_nom'] ) { + $status = $this->update_page_template_info( 'Nom', $this->nom, $summary ); + $mess .= $status->mess() . '
'; + $done = $status->done(); + } + + if ( $this->prenom != $this->data['page_template_prenom'] ) { + $status = $this->update_page_template_info( 'Prénom', $this->prenom, Msg::get('mgwuser-sanitize-names') ); + $mess .= $status->mess() . '
'; + if ( isset( $done ) && $done ) $done = $status->done(); + elseif ( !isset( $done ) ) $done = $status->done(); + } + + return Status::new( $done, $mess ); + } + + /** + * Renomme l'utilisateur à partir de $this->prenom et $this->nom + * @param string $summary + * @param bool $contentsReplace + * @param bool $nsAll recherche et remplace le texte dans toutes les pages + * @param string $n liste numérique des NS séparés par des virgules + * @return MGWStatus + */ + public function rename( $summary, $contentsReplace = false, $nsAll = false, $ns = null, $user = null ) { + + if ( is_null( $user ) ) { + global $wgUser; + $user = $wgUser; + } + + if ( !$this->mw_exists ) { + return Status::newFailed( 'Utilisateur inexistant' ); + } + if ( !isset( $this->nom ) ) { + return Status::newFailed( 'Variable non définie : "NOM"' ); + } + if ( !isset( $this->prenom ) ) { + return Status::newFailed( 'Variable non définie : "Prénom"' ); + } + + $oldName = $this->data['user_name']; + $newName = $this->make_name(); + if ( $oldName == $newName ) { + return Status::newFailed( 'Nom d\'utilisateur inchangé : valeurs identiques.' ); + } + + $status = Status::newDone( 'processing...' ); + + // on renomme l'utilisateur + $rename = MGWRenameuser::execute( + $oldName, // user_name actuel + $newName, // user_name de destination + true, // renommer toutes les pages + false, // supprimer les redirections + $summary, + $user + ); + if ( !$rename->done() ) $status->set_done( false ); + $status->extra[] = $rename; + + if ( $contentsReplace ) { + // on remplace l'ancien nom par le nouveau dans le contenu de toutes les pages + $replace = new MGWReplaceText( [ + "target" => $oldName, + "replace" => $newName, + "regex" => false, + "nsall" => $nsAll, + "ns" => $ns, + "summary" => $summary, + "user" => $user->getName() + ] ); + $replace = $replace->execute(); + + if ( !$replace->done() ) $status->set_done( false ); + $status->extra = $replace; + } + + if ( !$status->done() ) { + $status->set_message( 'Certaines étapes du renommage ont échoué (voir détails)' ); + } + else $status->set_message( 'Renommage réussi (voir détails)' ); + + return $status; + } + + /** + * Mise à jour de la table mgw_utilisateurs + * insert ou update sur les données $this->user_id, $this->nom, $this->prenom + * @return MGWStatus + */ + public function db_update( $updater_id = null ) { + + if ( !$this->mw_exists ) { + return Status::newFailed("Erreur: un utilisateur MGWiki ne peut être créé que s'il existe déjà ". + "en tant qu'utilisateur MediaWiki"); + } + if ( is_null( $this->nom ) || is_null( $this->prenom ) ) { + return Status::newFailed("Erreur: un utilisateur MGWiki ne peut être créé que si nom et prénom ". + "sont définis (null renvoyé)"); + } + + if ( is_null($updater_id) ) { + global $wgUser; + $updater_id = $wgUser->getId(); + } + + return DbF::update_or_insert( + 'utilisateur', + [ 'user_id' => $this->user_id ], + [ 'nom' => $this->nom, 'prenom' => $this->prenom ], + $updater_id + ); + } + + ######################## + ## Fonctions privées ## + ######################## + private function retrieveBaseData() { + + if ( ! $this->mw_exists ) + return false; + + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $res = $dbr->select( + 'user', + [ + 'user_id', + 'user_name', + 'user_real_name', + 'user_email' + ], + 'user_id = ' . $this->user_id + ); + + if ( $res->numRows() > 0 ) { + $r = $res->fetchRow(); + foreach ( $r as $field => $value ) { + if ( is_string($field) ) $this->data[ $field ] = $value; + } + return true; + } + return false; + } + + private function retrieveMGWData() { + + if ( ! $this->mgw_exists ) return false; + + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $res = $dbr->select( + 'mgw_utilisateur', + [ + 'utilisateur_nom', + 'utilisateur_prenom', + 'utilisateur_level', + 'utilisateur_updater_id', + 'utilisateur_update_time' + ], + 'utilisateur_user_id = ' . $this->user_id + ); + + if ( $res->numRows() > 0 ) { + $r = $res->fetchRow(); + foreach ( $r as $field => $value ) { + if ( is_string($field) ) $this->data[ $field ] = $value; + } + return true; + } + + return false; + } + + private function retrievePageData( $wikiPage ) { + + if ( ! $this->page_exists ) return false; + + $this->data['page_title'] = $wikiPage->getTitle()->getFullText(); + $this->data['page_url'] = $wikiPage->getTitle()->getFullURL(); + + $redirect = PageF::getPageRedirect ( $wikiPage, 'title' ); + if ( !is_null( $redirect ) ) { + $this->data['page_redirect'] = true; + $this->data['page_redirect_title'] = $redirect->getFullText(); + $this->data['page_redirect_url'] = $redirect->getFullURL(); + } else { + $this->data['page_redirect'] = false; + } + + $template = PageF::getPageTemplateInfos( $wikiPage, 'Personne', [ 'Nom', 'Prénom', 'E-mail' ] ); + if ( !is_null( $template ) ) { + $this->data['page_template'] = true; + $this->data['page_template_nom'] = $template['Nom']; + $this->data['page_template_prenom'] = $template['Prénom']; + $this->data['page_template_email'] = $template['E-mail']; + } else { + $this->data['page_template'] = false; + } + + return true; + } + + /** + * @return MGWStatus + */ + private function update_page_template_info( $field, $value, $summary ) { + + if ( !$this->mw_exists ) { + return Status::newFailed( 'Utilisateur inexistant' ); + } + if ( !$this->page_exists ) { + return Status::newFailed( 'Page utilisateur inexistante' ); + } + if ( !$this->data['page_template'] ) { + return Status::newFailed( 'Modèle {{Personne}} inexistant' ); + } + + $userpage = PageF::getPageFromTitleText( $this->data['user_name'], NS_USER, true ); + $oldtext = $userpage->getContent()->getNativeData(); + $regexp = '/(\|' . $field . '=)([^\|\n]+)/'; + if ( preg_match( $regexp, $oldtext ) < 1 ) { + return Status::newFailed( 'Champs "' . $field . '" inexistant.' ); + } + $newtext = preg_replace( $regexp, '$1' . $value , $oldtext ); + if ( $newtext != $oldtext ) { + try { + $edit_summary = $summary; + $newcontent = new WikitextContent( $newtext ); + $userpage->doEditContent( $newcontent, $edit_summary, EDIT_AUTOSUMMARY ); + return Status::newDone( 'Champs "' . $field . '" mis à jour avec succès.' ); + } + catch (\Exception $e) { + return Status::newFailed( $e ); + } + } + return Status::newFailed( 'Champs "' . $field . '" inchangé.' ); + } +} diff --git a/includes/Foreign/MGWRenameuser.php b/includes/Foreign/MGWRenameuser.php index 00bf5ea..46b8430 100644 --- a/includes/Foreign/MGWRenameuser.php +++ b/includes/Foreign/MGWRenameuser.php @@ -7,13 +7,13 @@ namespace MediaWiki\Extension\MGWikiDev\Foreign; -use MediaWiki\Extension\MGWikiDev\Utilities\GetJsonPage; use Title; use User; use MovePage; use RenameuserSQL; use MediaWiki\MediaWikiServices; use Html; +use MediaWiki\Extension\MGWikiDev\Classes\MGWStatus as Status; class MGWRenameuser { @@ -24,6 +24,7 @@ class MGWRenameuser { * @param bool $movepages * @param bool $suppressRedirect * @param string $reason + * @param User $user * * @throws PermissionsError * @throws ReadOnlyError @@ -31,12 +32,15 @@ class MGWRenameuser { * * @return array [ 'done' => bool, 'message' => bool, ] */ - public function execute( $oldname, $newname, $movepages, $suppressRedirect, $reason ) { + public function execute( $oldname, $newname, $movepages, $suppressRedirect, $reason, $user = null ) { global $wgContLang, $wgCapitalLinks, $wgUser; // ! on AUTORISE les utilisateurs à renommer leur propre compte - $user = &$wgUser; - if ( !( $user->isAllowed( 'renameuser' ) || $user->getName() == $oldname ) ) { + if ( is_null( $user ) ) { + $user = $wgUser; + } + + if ( !( $user->isAllowed( 'renameuser' ) || $user->getName() != $oldname ) ) { throw new PermissionsError( 'renameuser' ); } if ( $user->isBlocked() ) { @@ -52,11 +56,11 @@ public function execute( $oldname, $newname, $movepages, $suppressRedirect, $rea if ( $oun == '' ) { $message = str_replace( '$1', $oldname, wfMessage( 'renameusererrorinvalid' )->text() ); - return [ 'done' => false, 'message' => $message ]; + return Status::newFailed( $message ); } if ( $nun == '' ) { $message = str_replace( '$1', $newname, wfMessage( 'renameusererrorinvalid' )->text() ); - return [ 'done' => false, 'message' => $message ]; + return Status::newFailed( $message ); } // Suppress username validation of old username @@ -66,11 +70,11 @@ public function execute( $oldname, $newname, $movepages, $suppressRedirect, $rea // It won't be an object if for instance "|" is supplied as a value if ( !is_object( $olduser ) ) { $message = str_replace( '$1', $oldname, wfMessage( 'renameusererrorinvalid' )->text() ); - return [ 'done' => false, 'message' => $message ]; + return Status::newFailed( $message ); } if ( !is_object( $newuser ) || !User::isCreatableName( $newuser->getName() ) ) { $message = str_replace( '$1', $newname, wfMessage( 'renameusererrorinvalid' )->text() ); - return [ 'done' => false, 'message' => $message ]; + return Status::newFailed( $message ); } // Check for the existence of lowercase oldusername in database. @@ -97,7 +101,7 @@ public function execute( $oldname, $newname, $movepages, $suppressRedirect, $rea if ( $uid === 0 ) { $message = str_replace( '$1', $oldname, wfMessage( 'renameusererrordoesnotexist' )->text() ); - return [ 'done' => false, 'message' => $message ]; + return Status::newFailed( $message ); } if ( $newuser->idForName() !== 0 ) { @@ -105,7 +109,7 @@ public function execute( $oldname, $newname, $movepages, $suppressRedirect, $rea ['$1', '{{GENDER:$1|utilisateur|utilisatrice}}'], [ $oldname,'utilisateur.ice' ], wfMessage( 'renameusererrorexists' )->text() ); - return [ 'done' => false, 'message' => $message ]; + return Status::newFailed( $message ); } // Do the heavy lifting... @@ -200,6 +204,6 @@ public function execute( $oldname, $newname, $movepages, $suppressRedirect, $rea wfMessage( 'renameusersuccess' )->text() ); if ( $output ) $message .= $output . 'ATTENTION: la prise en compte des modifications peut prendre du temps.'; - return [ 'done' => true, 'message' => $message ]; + return Status::newDone( $message ); } } diff --git a/includes/Foreign/MGWReplaceText.php b/includes/Foreign/MGWReplaceText.php new file mode 100644 index 0000000..01d9273 --- /dev/null +++ b/includes/Foreign/MGWReplaceText.php @@ -0,0 +1,264 @@ + ReplaceTextSearch et ReplaceTextJob + * + * TODO: boucle en cas de nombre de pages max atteint (250) + * + * https://www.mediawiki.org/wiki/Extension:Replace_Text + */ + +namespace MediaWiki\Extension\MGWikiDev\Foreign; + +use User; +use MWNamespace; +use ReplaceTextSearch; +use ReplaceTextJob; +use TitleArrayFromResult; +use MediaWiki\Extension\MGWikiDev\Classes\MGWStatus as Status; + +class MGWReplaceText { + private $user; + private $target; + private $replacement; + private $namespaces; + private $category; + private $prefix; + private $useRegex; + private $doAnnounce; + private $rename; + + private $nsall; + private $ns; + private $summary; + private $status; + + /** + * @param $args list of arrays: + * [ "target" => string ] MANDATORY Target text to find. + * [ "replace" => string ] MANDATORY Text to replace. + * [ "regex" => bool ] This is a regex (false). + * [ "user" => string ] The user to attribute this to (uid 1). + * [ "summary" => string ] Alternate edit summary (%r is where to place the replacement text, %f the text to look for.) + * [ "nsall" => bool ] Search all canonical namespaces (false) If true, this option overrides the ns option. + * [ "ns" => array ] Comma separated namespaces to search in (Main). + * [ 'rename' => bool ] Rename page titles instead of replacing contents. + * [ "no-announce" => bool ] Do not announce edits on Special:RecentChanges or watchlists. + * + * test: + * MGWReplaceText::do( [ "target" => "interne TEST1", "target" => "anonyme", "regex" => false, "nsall" => true, "summary" => "MGW replacetext test.", "user" => "Webmaster" ] ); + */ + + + /** + * @param array $args + * @return MGWStatus + */ + public static function do ( $args ) { + $replace = new MGWReplaceText( $args ); + return $replace->execute(); + } + + + /** + * @param array $args + * @return MGWReplaceText + */ + public function __construct( $args ) { + + $this->category = null; + $this->prefix = null; + + foreach ($args as $arg => $value ) { + switch ( $arg ) { + + case "user": + $user = is_numeric( $value ) ? + User::newFromId( $value ) : + User::newFromName( $value ); + + if ( get_class( $user ) !== 'User' ) { + $this->status = Status::newFailed( 'Couldn\'t translate '.$value.' to a user.'); + } + $this->user = $user; + break; + + case "target": + $this->target = $value; + break; + + case "replace": + $this->replacement = $value; + break; + + case "summary": + $this->summary = $value; + break; + + case "nsall": + $this->nsall = $value; + break; + + case "ns": + $this->ns = $value; + break; + + case "regex": + $this->useRegex = $value; + break; + + case "rename": + $this->rename = $value; + break; + + case "no-announce": + $this->doAnnounce = !($value); + + default: + $this->status = Status::newFailed( "Paramètre [ '" . $arg . + "' => '" . $value . "' ] inconnu." ); + } + } + + if ( is_null( $this->target ) ) { + $this->status = Status::newFailed( "You have to specify a target." ); + } + + if ( is_null( $this->replacement ) ) { + $this->status = Status::newFailed( "You have to specify replacement text." ); + } + + if ( is_null( $this->rename ) ) { + $this->rename = false; + } + + if ( is_null( $this->nsall ) && is_null( $this->ns ) ) { + $namespaces = [ NS_MAIN ]; + } else { + $canonical = MWNamespace::getCanonicalNamespaces(); + $canonical[NS_MAIN] = "_"; + $namespaces = array_flip( $canonical ); + if ( is_null( $this->nsall ) ) { + $namespaces = array_map( + function ( $n ) use ( $canonical, $namespaces ) { + if ( is_numeric( $n ) ) { + if ( isset( $canonical[ $n ] ) ) { + return intval( $n ); + } + } else { + if ( isset( $namespaces[ $n ] ) ) { + return $namespaces[ $n ]; + } + } + return null; + }, explode( ",", $this->ns ) ); + $namespaces = array_filter( + $namespaces, + function ( $val ) { + return $val !== null; + } ); + } + } + $this->namespaces = $namespaces; + if ( $this->namespaces === [] ) { + $this->status = Status::newFailed( "No matching namespaces." ); + } + + if ( is_null( $this->doAnnounce ) ) { + $this->doAnnounce = true; + } + + if ( is_null( $this->status ) ) { + $this->status = Status::newDone( 'ReplaceText object construction done.'); + } + } + + private function getSummary( $target, $replacement ) { + $msg = wfMessage( 'replacetext_editsummary', $target, $replacement )->plain(); + if ( isset( $this->summary ) ) { + $msg = str_replace( [ '%f', '%r' ], [ $this->target, $this->replacement ], $this->summary ); + } + return $msg; + } + + private function replaceTitles( $titles, $target, $replacement, $useRegex, $rename ) { + foreach ( $titles as $title ) { + $params = [ + 'target_str' => $target, + 'replacement_str' => $replacement, + 'use_regex' => $useRegex, + 'user_id' => $this->user->getId(), + 'edit_summary' => $this->getSummary( $target, $replacement ), + 'doAnnounce' => $this->doAnnounce + ]; + + if ( $rename ) { + $params[ 'move_page' ] = true; + $params[ 'create_redirect' ] = true; + $params[ 'watch_page' ] = false; + } + + $job = new ReplaceTextJob( $title, $params ); + if ( $job->run() !== true ) { + $this->status->extra[] = [ + "title" => $title, + "done" => false, + "message" => "Trouble replacing on the page '$title'." + ]; + } + else $this->status->extra[] = [ + "title" => $title, + "done" => true, + "message" => "Replacing on '$title' done." + ]; + } + } + + + /** + * @return MGWStatus + */ + public function execute() { + + if ( !$this->status->done() ) { + return $this->status; + } + + if ( $this->rename ) { + $res = ReplaceTextSearch::getMatchingTitles( + $this->target, + $this->namespaces, + $this->category, + $this->prefix, + $this->useRegex + ); + } else { + $res = ReplaceTextSearch::doSearchQuery( + $this->target, + $this->namespaces, + $this->category, + $this->prefix, + $this->useRegex + ); + } + + $titles = new TitleArrayFromResult( $res ); + + if ( count( $titles ) === 0 ) { + $this->status->set_done( false ); + $this->status->set_message( 'No targets found to replace.' ); + return $this->status; + } + + $this->replaceTitles( + $titles, $this->target, $this->replacement, $this->useRegex, $this->rename + ); + $this->status->set_message( 'ReplaceText done (see details).' ); + + return $this->status; + } +} diff --git a/includes/Foreign/MGWUserMerge.php b/includes/Foreign/MGWUserMerge.php index ee89c2b..95d3fdd 100644 --- a/includes/Foreign/MGWUserMerge.php +++ b/includes/Foreign/MGWUserMerge.php @@ -2,75 +2,63 @@ /** * Adaptation de SpecialUserMerge.php * ! DEPENDANCE : extension UserMerge + * ! NE PAS FUSIONNER SANS DELETE (ancien compte toujours valide) + * ! DELETE SEUL NE FONCTIONNE PAS + * => utiliser seulement la fusion avec l'option delete + * + * ! la trace de l'utilisateur peut persister dans le contenu des pages + * * https://www.mediawiki.org/wiki/Extension:UserMerge */ namespace MediaWiki\Extension\MGWikiDev\Foreign; use User; -use Status; use MediaWiki\MediaWikiServices; use MergeUser; // extension UserMerge use UserMergeLogger; // extension UserMerge +use MediaWiki\Extension\MGWikiDev\Classes\MGWStatus as Status; class MGWUserMerge { /** - * @param string $oldname - * @param string|null $newname + * @param int $old_id + * @param int|null $new_id # !! null NE FONCTIONNE PAS * @param bool $delete * @param callable $msg Function that returns a Message object * @return Status */ - public function execute( $oldname, $newname, $delete, /* callable */ $msg ) { + public function execute( $old_id, $new_id, $delete, /* callable */ $msg ) { global $wgUser; // Validate old user - $oldUser = User::newFromName( $oldname ); - if ( !$oldUser || $oldUser->getId() === 0 ) { - return [ - 'done' => false, - 'message' => wfMessage('usermerge-badolduser')->text() - ]; + $oldUser = User::newFromId( $old_id ); + if ( !$oldUser ) { + return Status::newFailed( wfMessage('usermerge-badoldid')->text() ); } - if ( $wgUser->getId() === $oldUser->getId() ) { - return [ - 'done' => false, - 'message' => wfMessage('usermerge-noselfdelete', $wgUser->getName() )->parse() - ]; + if ( $wgUser->getId() === $old_id ) { + return Status::newFailed( wfMessage('usermerge-noselfdelete', $wgUser->getName() )->parse() ); } $protectedGroups = MediaWikiServices::getInstance()->getMainConfig()->get( 'UserMergeProtectedGroups' ); if ( count( array_intersect( $oldUser->getGroups(), $protectedGroups ) ) ) { - return [ - 'done' => false, - 'message' => wfMessage( 'usermerge-protectedgroup', $oldUser->getName() )->parse() - ]; + return Status::newFailed( wfMessage( 'usermerge-protectedgroup', $oldUser->getName() )->parse() ); } // validate new user - if ( $newname !== null ) { - $newUser = User::newFromName( $newname ); - if ( !$newUser || $newUser->getId() === 0 ) { - return [ - 'done' => false, - 'message' => wfMessage('usermerge-badnewuser')->text() - ]; + if ( $new_id !== null ) { + $newUser = User::newFromId( $new_id ); + if ( !$newUser ) { + return Status::newFailed( wfMessage('usermerge-badnewuserid')->text() ); } } - - // check if the users are different - $newUser = User::newFromName( $newname ); // Handle "Anonymous" as a special case for user deletion - if ( $newname === null ) { - $newUser = User::newFromName( '(compte erronné)' ); + if ( $new_id === null ) { + $newUser = User::newFromName( 'Anonymous' ); $newUser->mId = 0; } - $oldUser = User::newFromName( $oldname ); - if ( $newname !== null && $newUser->getName() === $oldUser->getName() ) { - return [ - 'done' => false, - 'message' => wfMessage('usermerge-same-old-and-new-user')->text() - ]; + + if ( $new_id !== null && $newUser->getName() === $oldUser->getName() ) { + return Status::newFailed( wfMessage('usermerge-same-old-and-new-user')->text() ); } // Validation passed, let's merge the user now. @@ -79,20 +67,14 @@ public function execute( $oldname, $newname, $delete, /* callable */ $msg ) { $um->merge( $wgUser, __METHOD__ ); $message .= wfMessage( 'usermerge-success' )->rawParams( - $oldUser->getName(), $oldUser->getId(), - $newUser->getName(), $newUser->getId() )->parse(); + $oldUser->getName(), $old_id, + $newUser->getName(), $new_id )->parse(); if ( $delete ) { $failed = $um->delete( $wgUser, $msg ); - $message .= wfMessage( 'usermerge-userdeleted')->rawParams( $oldUser->getName(), $oldUser->getId() )->escaped() ; - if ( $failed ) return [ - 'done' => false, - 'message' => wfMessage('usermerge-page-unmoved')->text() - ]; + $message .= wfMessage( 'usermerge-userdeleted')->rawParams( $oldUser->getName(), $old_id )->escaped() ; + if ( $failed ) return Status::newFailed( wfMessage('usermerge-page-unmoved')->text() ); } - return [ - 'done' => true, - 'message' => $message - ]; + return Status::newDone( $message ); } } diff --git a/includes/SpecialAccountRequest.php b/includes/SpecialAccountRequest.php index 9fdcbe2..226a315 100644 --- a/includes/SpecialAccountRequest.php +++ b/includes/SpecialAccountRequest.php @@ -2,7 +2,7 @@ namespace MediaWiki\Extension\MGWikiDev; use SpecialPage; -use MediaWiki\Extension\MGWikiDev\Utilities\GetJsonPage; +use MediaWiki\Extension\MGWikiDev\Utilities\GetMessage as Msg; use MediaWiki\Extension\MGWikiDev\Utilities\JsonToForm; /** @@ -11,7 +11,6 @@ */ class SpecialAccountRequest extends SpecialPage { - private $messages; // array private $JsonForm; // obj private $cookieData; // array private $isPosted; // bool @@ -22,16 +21,12 @@ class SpecialAccountRequest extends SpecialPage { public function __construct() { parent::__construct( 'SpecialAccountRequest' ); - # messages d'interface - $this->messages = GetJsonPage::getData('messages'); - # gestion du formulaire $postData = $this->getRequest()->getPostValues(); // récupération des variables POST si elles existent $this->isPosted = ( sizeof($postData) > 0 ); $this->JsonForm = new JsonToForm( 'specialaccountrequest', - $postData, - $this->messages + $postData ); # récupération des cookies @@ -53,7 +48,7 @@ public function execute( $sub ) { # demande déjà finalisée if ( $this->cookieData['done'] ) { - $messHTML = $this->messageHTML( 'done', $this->getMsg('specialaccountrequest-mess-alreadydone') ); + $messHTML = $this->messageHTML( 'done', Msg::get('specialaccountrequest-mess-alreadydone') ); $done = true; } # envoi du mail si réponse valide @@ -62,13 +57,13 @@ public function execute( $sub ) { $this->JsonForm->sendEmail(); //Todo: use WebResponse::setCookie() when upgrade Mediawiki > 1.35 setcookie( $this->cookieData['label'], "Account request sent.", time()+3600 ); - $messHTML = $this->messageHTML( 'done', $this->getMsg('specialaccountrequest-mess-confirm') ); + $messHTML = $this->messageHTML( 'done', Msg::get('specialaccountrequest-mess-confirm') ); $done = true; } # captcha invalide, nombre d'essais dépassés elseif ( $this->cookieData['attempt'] > 4 ){ - $messHTML = $this->messageHTML( 'done', $this->getMsg('specialaccountrequest-mess-attemptslimit') ); + $messHTML = $this->messageHTML( 'done', Msg::get('specialaccountrequest-mess-attemptslimit') ); $done = true; } @@ -76,7 +71,7 @@ public function execute( $sub ) { else { $this->cookieData['attempt'] += 1; - $messHTML = $this->messageHTML( 'captchaError', $this->getMsg('specialaccountrequest-mess-captchaerror') ); + $messHTML = $this->messageHTML( 'captchaError', Msg::get('specialaccountrequest-mess-captchaerror') ); setcookie('mgw_accountRequestAttempt', $this->cookieData['attempt'], time()+60*60); } } @@ -85,7 +80,7 @@ public function execute( $sub ) { $out = $this->getOutput(); $out->addModules('ext.mgwiki-jsonform'); $out->addModules('ext.mgwiki-specialaccountrequest'); - $out->setPageTitle( $this->getMsg('specialaccountrequest-title') ); + $out->setPageTitle( Msg::get('specialaccountrequest-title') ); if (!$done){ $form = $this->JsonForm->makeForm( $messHTML ); @@ -111,7 +106,7 @@ protected function messageHTML($option, $mess) case 'done': return '

' . $mess . '

- '; + '; break; case 'captchaError': @@ -137,16 +132,6 @@ private function getCookies () } } - private function getMsg ( $mess ) { - if ( isset( $this->messages[ $mess ] ) ) { - return $this->messages[ $mess ]; - } - else { - $link = GetJsonPage::getLink('messages'); - return '
<' . $mess . '>'; - } - } - /** * groupe auquel se rapporte cette page spéciale */ diff --git a/includes/SpecialAdminGroupTypes.php b/includes/SpecialAdminGroupTypes.php new file mode 100644 index 0000000..f8da125 --- /dev/null +++ b/includes/SpecialAdminGroupTypes.php @@ -0,0 +1,473 @@ +triOptions = ['nom', 'admin_level', 'user_level', 'default_duration', 'update_time', 'updater_id']; + $this->filtreOptions = [ + 'show_archive', + 'sysop', + 'U3', + 'U2', + 'U1', + 'U0' + ]; + $this->filtreOptionsTitles = [ + 'admin_level' => [ 'sysop', 'U3', 'U2', 'U1', 'U0' ], + 'user_level' => [ 'sysop', 'U3', 'U2', 'U1', 'U0' ] + ]; + } + + public function execute( $sub ) { + + // récupération et contrôle des entrées + $reqData = $this->getRequest()->getValues(); + PhpF::empty( $reqData['action'] ); + PhpF::empty( $reqData['id'] ); + PhpF::int( $reqData['id'] ); + + $this->setHeaders(); + $out = $this->getOutput(); + $out->addModules('ext.mgwiki-specialadmingrouptypes'); + $out->enableOOUI(); + + // mode d'affichage + $edit = ( $reqData['action'] == 'edit' ); + + $history = ( $reqData['action'] == 'history' && !is_null( $reqData['id'] )); + + $show = ( $reqData['action'] != 'edit' && $reqData['action'] != 'history' ); + + if ( isset( $reqData['submit'] ) ) { + $status = $this->formCallback( $reqData, $edit, $show ); + $done = ( $status->done() ) ? 'true' : 'false'; + $out->addHTML( HtmlF::alertMessage( $status ) ); + } + + // undo + if ( $reqData['action'] == 'undo' && isset( $reqData['id'] ) && isset( $reqData['archive'] ) ) { + $undo = $this->undo( $reqData['id'], $reqData['archive'] ); + $out->addHTML( HtmlF::alertMessage( $undo ) ); + $history = true; + $show = false; + } + + // delete + if ( $reqData['action'] == 'delete' && isset( $reqData['id'] ) ) { + $delete = $this->delete( $reqData['id'] ); + $out->addHTML( HtmlF::alertMessage( $delete ) ); + $history = false; + $show = true; + } + + // historique + if ( $history ) { + $groupe_types = $this->getGroupTypeHistory( $reqData['id'] ); + $out->setPageTitle( Msg::get('specialadmingrouptypes-title-history', [ $groupe_types[0]['nom'] ] ) ); + $out->addHTML( $this->makeView( $groupe_types ) ); + $out->addHTML( '
' . + (string)new \OOUI\ButtonWidget( [ 'href' => $this->selfURL(), 'label' => 'retour' ] ) + ); + } + + // affichage général + if ( $show ) { + + $out->setPageTitle( Msg::get('specialadmingrouptypes-title') ); + + $groupe_types = $this->getGroupTypes( '', [ 'ORDER BY' => 'nom' ] ); + $out->addHTML( $this->makeView( $groupe_types ) ); + $out->addHTML( '
' . + (string)new \OOUI\ButtonWidget( [ + 'href' => $this->selfURL( [ 'action' => 'edit' ] ), + 'label' => 'nouveau' + ] ) + ); + } + + // formulaire d'édition + if ( $edit ) { + $this->setData( $reqData ); + if ( !empty( $this->nom ) ) { + $out->setPageTitle( Msg::get('specialadmingrouptypes-title-edit', [ $this->nom ] ) ); + } + else { + $out->setPageTitle( Msg::get('specialadmingrouptypes-title-edit-new' ) ); + } + $out->addHTML( $this->makeOouiForm( $reqData ) ); + } + } + + public static function formCallback( &$reqData, &$edit, &$show ) { + + global $wgUser; + + PhpF::html( $reqData['nom'] ); + PhpF::int( $reqData['admin_level'] ); + PhpF::int( $reqData['user_level'] ); + PhpF::int( $reqData['duration'] ); + + $check = DbF::select_clean( 'groupe_type', [ 'id', 'nom' ] ); + foreach ( $check as $row ) { + if ( strtolower( $row['nom'] ) == strtolower( $reqData['nom'] ) + && $row['id'] != $reqData['id'] ) { + $edit = true; // on ré-affiche le formulaire + $show = false; + return Status::newFailed( 'Le nom demandé existe déjà. Vous pouvez: + ' ); + } + } + + if ( is_null( $reqData['id'] ) ) { + $select = [ 'nom' => $reqData['nom'] ]; + $data = [ + 'admin_level' => $reqData['admin_level'], + 'user_level' => $reqData['user_level'], + 'default_duration' => $reqData['duration'] + ]; + } + else { + $select = [ 'id' => $reqData['id'] ]; + $data = [ + 'nom' => $reqData['nom'], + 'admin_level' => $reqData['admin_level'], + 'user_level' => $reqData['user_level'], + 'default_duration' => $reqData['duration'] + ]; + } + + $write = DbF::update_or_insert( 'groupe_type', $select, $data, $wgUser->getId() ); + + if ( $write->done() ) { + $edit = false; + return Status::newDone( 'Les modifications de ' . $reqData['nom'] . ' ont été enregistrées.' ); + } + else { + $edit = true; + return $write; + } + } + + private function undo( $id, $archive_id ) { + global $wgUser; + $old_type = DbF::select_clean( + 'archive_groupe_type', + [ 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration' ], + 'archive_id = ' . $archive_id + )[0]; + $actual_types = DbF::select_clean( + 'groupe_type', + [ 'id', 'nom' ] + ); + foreach ( $actual_types as $type ) { + if ( strtolower( $type['nom'] ) == strtolower( $old_type['nom'] ) ) { + return Status::newFailed( 'Le nom '.$old_type['nom'] . + ' correspond à un autre type de groupe en cours d\'utilisation.' + .' Veuillez rétablir les modifications manuellement avec un autre nom.' ); + } + } + $status = DbF::update( 'groupe_type', [ 'id' => $id ], $old_type, $wgUser->getId() ); + if ( $status->done() ) { + return Status::newDone( 'L\'ancienne version de '.$old_type['nom'].' a été rétablie.'); + } + else { + return $status; + } + } + + private function delete( $id ) { + global $wgUser; + $status = DbF::delete( 'groupe_type', [ 'id' => $id ], $wgUser->getId() ); + if ( $status->done() ) { + return Status::newDone( ''.$status->extra()['groupe_type_nom'].' a été supprimé.'); + } + else { + return $status; + } + } + + private function getGroupTypes( $select = '', $opts = [] ) { + return DbF::select_clean( + 'groupe_type', + [ 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration', 'update_time', 'updater_id' ], + $select, + $opts + ); + } + + private function getGroupTypeHistory( $id ) { + + $actu = DbF::select_clean( + 'groupe_type', + [ 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration', 'update_time', 'updater_id' ], + 'id = ' . $id + ); + + $past = DbF::select_clean( + 'archive_groupe_type', + [ 'archive_id', 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration', + 'update_time', 'updater_id', 'drop_time', 'drop_updater_id' ], + 'id = ' . $id, + [ 'ORDER BY' => 'update_time DESC' ] + ); + if ( !is_null($past) ) { + $actu = array_merge( $actu, $past ); + } + return $actu; + } + + //////////////////////////////////// + // FONCTIONS RELATIVES A L'AFFICHAGE + + private function makeView( $groupe_types ) { + global $wgMgwLevels; + $print = '
'; + # préparation des variables + foreach ( $groupe_types as $type ) { + switch ( $type['default_duration'] ) { + case 0: + $duration = 'indéfini'; + break; + case 1: + $duration = '6 mois'; + break; + case 2: + $duration = '1 an'; + break; + default: + $duration = ( $type['default_duration'] / 2 ) . ' ans'; + break; + } + + $buttons = ( isset( $type['archive_id'] ) ) + ? HtmlF::onclickButton( + 'rétablir', + $this->selfURL( [ 'action'=>'undo', 'id'=>$type['id'], 'archive'=>$type['archive_id'] ] ) ) + : HtmlF::onclickButton( + 'modifier', + $this->selfURL( [ 'action'=>'edit', 'id'=>$type['id'] ] ) ) . ' + historique + supprimer' ; + + $class = ( isset( $type['archive_id'] ) ) + ? 'mgw-archive' + : 'mgw-actif'; + + if ( isset( $type['drop_time'] ) && $type['drop_time'] != 0 ) { + $update_time = wfTimestamp( TS_DB, $type['drop_time'] ); + $updater = \User::newFromId ( $type['drop_updater_id'] ); + $updater_link = '' . $updater->getName() . ''; + $maj = 'Supprimé le ' . $update_time . ' par ' . $updater_link; + } + else { + $update_time = wfTimestamp( TS_DB, $type['update_time'] ); + $updater = \User::newFromId ( $type['updater_id'] ); + $updater_link = '' . $updater->getName() . ''; + $maj = 'Màj le ' . $update_time . ' par ' . $updater_link ; + } + + #intégration + $print .= ' + + + + + + + + + + + + + + + +
' . $type['nom'] . 'Page: ' . $type['page_id'] . '' . $buttons . '
Admin : ' . $wgMgwLevels[ $type['admin_level'] ] . 'User : ' . $wgMgwLevels[ $type['user_level'] ] . 'Durée : ' . $duration . '
' . $maj . '
'; + } + $print .= '
'; + return $print; + } + + private function setData( $reqData ){ + // valeurs par défaut + if ( isset( $reqData['submit'] ) ) { + $this->id = $reqData['id']; + $this->nom = $reqData['nom']; + $this->admin_level = $reqData['admin_level']; + $this->user_level = $reqData['user_level']; + $this->duration = $reqData['duration']; + } + elseif ( !empty( $reqData['id'] ) ) { + $row = $this->getGroupTypes( + 'id = ' . $reqData['id'], + [ 'ORDER BY' => 'nom' ] + )[0]; + + $this->id = $row['id']; + $this->nom = $row['nom']; + $this->admin_level = $row['admin_level']; + $this->user_level = $row['user_level']; + $this->duration = $row['default_duration']; + } + else { + $this->id = ''; + $this->nom = ''; + $this->admin_level = 2; + $this->user_level = 1; + $this->duration = 0; + } + } + + + private function makeOouiForm( $reqData ) { + $html = new \OOUI\FormLayout( [ + 'method' => 'POST', + 'action' => $this->selfURL(), + 'items' => [ + new \OOUI\FieldsetLayout( [ + 'items' => [ + new \OOUI\FieldLayout( + new \OOUI\HiddenInputWidget( [ + 'name' => 'id', + 'value' => $this->id + ] ) + ), + new \OOUI\FieldLayout( + new \OOUI\TextInputWidget( [ + 'name' => 'nom', + 'type' => 'text', + 'value' => $this->nom, + 'required' => true + ] ), + [ + 'label' => 'Nom du type de groupe', + 'align' => 'top', + ] + ), + new \OOUI\FieldLayout( + new \OOUI\DropdownInputWidget( [ + 'name' => 'admin_level', + 'value' => $this->admin_level, + 'options' => [ + [ 'data' => 1, 'label' => 'U1' ], + [ 'data' => 2, 'label' => 'U2' ], + [ 'data' => 3, 'label' => 'U3' ], + [ 'data' => 4, 'label' => 'sysop' ], + ] + ] ), + [ + 'label' => 'Niveau des responsables', + 'align' => 'top', + ] + ), + new \OOUI\FieldLayout( + new \OOUI\DropdownInputWidget( [ + 'name' => 'user_level', + 'value' => $this->user_level, + 'options' => [ + [ 'data' => 1, 'label' => 'U1' ], + [ 'data' => 2, 'label' => 'U2' ], + [ 'data' => 3, 'label' => 'U3' ], + [ 'data' => 4, 'label' => 'sysop' ], + ] + ] ), + [ + 'label' => 'Niveau des utilisateurs', + 'align' => 'top', + ] + ), + new \OOUI\FieldLayout( + new \OOUI\DropdownInputWidget( [ + 'name' => 'duration', + 'value' => $this->duration, + 'options' => [ + [ 'data' => 0, 'label' => 'indéfini' ], + [ 'data' => 1, 'label' => '6 mois' ], + [ 'data' => 2, 'label' => '1 an' ], + [ 'data' => 4, 'label' => '2 ans' ], + [ 'data' => 6, 'label' => '3 ans' ], + [ 'data' => 8, 'label' => '4 ans' ], + [ 'data' => 10, 'label' => '5 ans' ] + ] + ] ), + [ + 'label' => 'Durée par défaut', + 'align' => 'top', + ] + ), + new \OOUI\FieldLayout( + new \OOUI\ButtonInputWidget( [ + 'name' => 'submit', + 'label' => 'Valider', + 'type' => 'submit' + ] ), + [ + 'label' => null, + 'align' => 'top', + ] + ) + ] // items [...] + ] ) // FieldsetLayout( [...] ) + ] // items [...] + ] ); // FormLayout( [...] ) + $html .= '
' . (string)new \OOUI\ButtonWidget( [ + 'href' => $this->selfURL(), + 'label' => 'annuler' + ] ); + return $html; + } + + /** + * @param array $get associative array of GET request parameters + * [key => value] + */ + private function selfURL( array $get = [] ) { + return SpecialPage::getTitleFor( 'specialadmingrouptypes' )->getLinkURL( $get ); + } + + protected function getGroupName() { + return 'mgwiki'; + } +} diff --git a/includes/SpecialCheckAccounts.php b/includes/SpecialCheckAccounts.php index 7119255..a1920ec 100644 --- a/includes/SpecialCheckAccounts.php +++ b/includes/SpecialCheckAccounts.php @@ -2,47 +2,41 @@ namespace MediaWiki\Extension\MGWikiDev; +/* mediawiki */ use SpecialPage; -use MediaWiki\Extension\MGWikiDev\Utilities\GetJsonPage; +use WikitextContent; use MediaWiki\MediaWikiServices; use Wikimedia\Rdbms\Database; -use MediaWiki\Extension\MGWikiDev\Foreign\MGWRenameuser; +/* foreign */ use MediaWiki\Extension\MGWikiDev\Foreign\MGWUserMerge; +use MediaWiki\Extension\MGWikiDev\Foreign\MGWReplaceText; +/* MGWiki */ +use MediaWiki\Extension\MGWikiDev\Utilities\GetMessage as Msg; use MediaWiki\Extension\MGWikiDev\Utilities\PagesFunctions as PageF; use MediaWiki\Extension\MGWikiDev\Utilities\UsersFunctions as UserF; -use WikitextContent; +use MediaWiki\Extension\MGWikiDev\Utilities\PhpFunctions as PhpF; +use MediaWiki\Extension\MGWikiDev\Classes\MGWUser; /** - * Page spéciale demande de création de compte - * Accessible du public (whitelisted) + * Page spéciale maintenance des utilisateurs */ class SpecialCheckAccounts extends SpecialPage { /** - * Array of messages values - * - * @var array + * @var array sort values */ - private $messages = []; + private $select = []; /** - * @var array + * @var array filter values */ - private $triOptions, $filtreOptions, $filtreOptionsTitles; + private $check = []; /** - * Array of sort values - * * @var array */ - private $select = []; + private $triOptions, $filtreOptions, $filtreOptionsTitles; - /** - * Array of filter values - * - * @var array - */ - private $check = []; /** * Initialize the special page. @@ -51,190 +45,306 @@ public function __construct() { parent::__construct( 'SpecialCheckAccounts', 'editinterface' ); // restrict to sysops - # messages d'interface - $this->messages = GetJsonPage::getData('messages'); - # définition des options - $this->triOptions = ['id', 'username', 'nom', 'prenom', 'email']; + $this->triOptions = ['user_id', 'user_name', 'page_template_nom', 'page_template_prenom', 'user_email']; $this->filtreOptions = [ - 'nouserpage', - 'nousertemplate', - 'redirect', - 'nouseremail', - 'differentname-real', - 'differentname-page', + 'page', + 'page_redirect', + 'page_template', + 'same_names', + 'user_email', + 'same_email', + 'user_real_name', + 'same_real_name', + 'mgw_exists', 'sysop', 'bureaucrat', - 'U2']; + 'U3', + 'U2', + 'U1', + 'U0' + ]; $this->filtreOptionsTitles = [ - 'usergroup' => [ 'sysop', 'bureaucrat', 'U2' ] + 'user_groups' => [ 'sysop', 'bureaucrat', 'U3', 'U2', 'U1', 'U0' ] ]; } /** - * Shows the page to the user. * @param string $sub The subpage string argument (if any). */ - public function execute( $sub ) { - global $tri; + global $wgUser; + global $tri; + $postData = $this->getRequest()->getPostValues(); - // définition de $select[] et $check[] - $this->setOptions( $postData ); + // définition de $tri et $select[] + $tri = $this->set_select( $postData ); + + // définition de $check[] + $this->set_check( $postData ); // affichage du formulaire d'entête $this->setHeaders(); $out = $this->getOutput(); $out->addModules('ext.mgwiki-specialcheckaccounts'); - $out->setPageTitle( $this->getMsg('specialcheckaccounts-title') ); + $out->setPageTitle( Msg::get('specialcheckaccounts-title') ); $out->addHTML( $this->makeHeadForm() ); // ACTIONS $empty = [ - 'id' => '', - 'username' => '', - 'userpagelink' => '' + 'user_id' => '', + 'user_name' => '', + 'page_url' => '' ]; - # affichage brut - if ( isset( $postData['afficher'] ) ) { - $users = self::getUsers( false ); + # affichage brut + if ( isset( $postData['action'] ) && $postData['action'] == 'show' ) { + $users = $this->getUsers( ); } - # emails en doublon uniquement - if ( isset( $postData['emailduplicates'] ) ) { - $emailduplicates = self::getEmailDuplicates(); - $users = []; - foreach ( $emailduplicates as $mail ) { - if ( $mail != '' ) { - $users = array_merge( $users, self::getUsers( false, 'user_email = \'' . $mail . '\'' ) ); - } - } - if ( sizeof( $users ) < 1 ) { - $empty['id'] = '0'; + # emails en doublon uniquement + if ( isset( $postData['action'] ) && $postData['action'] == 'emailduplicates' ) { + + $user_ids = $this->getEmailDuplicates(); + $users = $this->getUsers( implode( ',', $user_ids ) ); + + if ( count( $users ) == 0 ) { + $empty['user_id'] = '0'; $empty['message'] = 'Aucun mail en doublon n\'a été trouvé.'; $actionInfo[] = $empty; } } - # utilisateurs cochés uniquement - if ( isset( $postData['select'] ) ) { - $users = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); + # utilisateurs cochés uniquement + if ( isset( $postData['action'] ) && $postData['action'] == 'select' ) { + $users = $this->getUsers( $postData['addusers'] ); } - # harmonisation de la casse 'Prénom NOM' sur les pages utilisateurs - if ( isset( $postData['sanitize'] ) ) { - $actionInfo = $this->sanitizeNomPrenom( explode( ',', $postData['addusers'] ) ); - $users = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); + # harmonisation de la casse 'Prénom NOM' sur les pages utilisateurs + if ( isset( $postData['action'] ) && $postData['action'] == 'sanitize' ) { + + $actionInfo = []; + $list = explode( ',', $postData['addusers'] ); + + foreach ( $list as $user_id ) { + + $MGWuser = MGWUser::newFromUserId( (int)$user_id ); + if ( $MGWuser->sanitize_nom() && $MGWuser->sanitize_prenom() ) { + $status = $MGWuser->update_page_template_names( + Msg::get('specialcheckaccount-sanitize-summary') + ); + } + else + $status = Status::newFailed( 'Nom ou Prénom non défini' ); + + $mess = ( $status->done() ) ? 'SUCCES : ' : 'ECHEC : '; + $mess .= $status->mess(); + + $return = array( + 'user_id' => $user_id, + 'user_name' => $MGWuser->get_data()['user_name'], + 'page_url' => $MGWuser->get_data()['page_url'], + 'message' => $mess + ); + + $actionInfo[] = $return; + } + $users = $this->getUsers( $postData['addusers'] ); } - # renommer les utilisateurs sur la base du formulaire Personne (pages utilisateurs) - if ( isset( $postData['rename'] ) ) { - $reason = $this->getMsg('specialcheckaccount-rename-summary'); - $users = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); - foreach ( $users as $user ) { - $renameInfo = MGWRenameuser::execute( - $user['username'], // username actuel - $user['prenom'] . ' ' . $user['nom'], // username de destination - true, // renommer toutes les pages - false, // supprimer les redirections - $reason - ); - $message = ($renameInfo['done']) ? 'SUCCES' : 'ECHEC'; - $actionInfo[] = [ - 'id' => $user['id'], - 'username' => $user['username'], - 'userpagelink' => $user['userpagelink'], - 'message' => $message . ' : ' . $renameInfo['message'] - ]; - } + # harmoniser les comptes utilisateurs sur la base du formulaire Personne + if ( isset( $postData['action'] ) && $postData['action'] == 'harmonize' ) { + if ( $postData['harmonize-replace'] == 'oui' ) { + $replace = true; + } else { + $replace = false; + } + $list = explode( ',', $postData['addusers'] ); + $actionInfo = []; + foreach ( $list as $user_id ) { + $MGWuser = MGWUser::newFromUserId( $user_id ); + if ( !$MGWuser->same_names() ){ + $status = $MGWuser->rename( + Msg::get('specialcheckaccount-rename-summary'), + $replace, + $nsAll = true, + $ns = null, + $wgUser + ); + $details = ''; + if ( is_array( $status->extra() ) && count( $status->extra() ) > 0 ) { + foreach ( $status->extra() as $detail ) { + $details .= ' / ' . $detail['message']; + } + } + $mess = ( $status->done() ) ? 'SUCCES : ' : 'ECHEC : '; + $mess .= $status->mess() . $details ; + } else { + $mess = '"user_name" est à jour.'; + } + if ( !$MGWuser->same_real_name() ) { + $MGWuser->update_real_name(); + $mess .= '
"user_real_name" a été mis à jour'; + } + if ( !$MGWuser->same_email() ) { + $realname = $MGWuser->update_email(); + $mess .= '
"user_email" a été mis à jour'; + } + $return = array( + 'user_id' => $user_id, + 'user_name' => $MGWuser->get_data()['user_name'], + 'page_url' => $MGWuser->get_data()['page_url'], + 'message' => $mess + ); + $actionInfo[] = $return; + } + $users = $this->getUsers( $postData['addusers'] ); } - # fusionner ou effacer les utilisateurs - ## contrôle des entrées - $checkMerge = true; - $checkDelete = true; - if ( isset( $postData['merge'] ) && count( explode( ',', $postData['addusers'] ) ) > 1 ) { + # vider le compte utilisateur de ses infos (user_email, user_real_name et groupes ) + if ( isset( $postData['action'] ) && $postData['action'] == 'empty' ) { + $list = explode( ',', $postData['deleteusers'] ); + $actionInfo = []; + foreach ( $list as $user_id ) { + $MGWuser = MGWUser::newFromUserId( $user_id ); + $info = $MGWuser->delete_all_groups(); + if ( !is_null( $MGWuser->get_email() ) ) { + $MGWuser->delete_email(); + $info .= '
E-mail effacé.'; + } + if ( is_string( $MGWuser->get_data()['user_real_name'] ) ) { + $MGWuser->delete_real_name(); + $info .= '
real_name effacé.'; + } + $page_url = ( is_string( $MGWuser->get_data()['page_url'] ) ) ? $MGWuser->get_data()['page_url'] : null; + $return = array( + 'user_id' => $user_id, + 'user_name' => $MGWuser->get_data()['user_name'], + 'page_url' => $page_url, + 'message' => $info + ); + $actionInfo[] = $return; + } + $users = $this->getUsers( $postData['deleteusers'] ); + } + + # effacer les utilisateurs + ## contrôle des entrées + $deleteAsked = isset( $postData['action'] ) && $postData['action'] == 'delete'; + $checkDelete = true; + if ( $deleteAsked && count( explode( ',', $postData['addusers'] ) ) > 1 ) { $empty['message'] = '"in" :: un seul utilisateur de destination doit être coché.'; $actionInfo[] = $empty; - if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) + if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) { $postData['addusers'] .= ',' . $postData['deleteusers']; - $users = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); - $checkMerge = false; + } + $users = $this->getUsers( $postData['addusers'] ); + $checkDelete = false; } - if ( isset( $postData['merge'] ) && ( !isset( $postData['addusers'] ) || $postData['addusers'] == '' ) ) { + if ( $deleteAsked && ( !isset( $postData['addusers'] ) || $postData['addusers'] == '' ) ) { $empty['message'] = '"in" :: un utilisateur de destination doit être coché.'; $actionInfo[] = $empty; - if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) { - $users = self::getUsers( false, 'user_id IN(' . $postData['deleteusers'] . ')' ); + if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) { + $users = $this->getUsers( $postData['deleteusers'] ); } - $checkMerge = false; + $checkDelete = false; } - if ( ( isset( $postData['merge'] ) || isset( $postData['delete'] ) ) && - ( !isset( $postData['deleteusers'] ) || $postData['deleteusers'] == '' ) ) { - $empty['message'] = 'Au moins un utilisateur doit être coché :: "out"'; + if ( $deleteAsked && ( !isset( $postData['deleteusers'] ) || $postData['deleteusers'] == '' ) ) { + $empty['message'] = 'Au moins un utilisateur à supprimer doit être coché :: "out"'; $actionInfo[] = $empty; - if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) { - $users = self::getUsers( false, 'user_id IN(' . $postData['deleteusers'] . ')' ); + if ( isset( $postData['deleteusers'] ) && $postData['deleteusers'] != '' ) { + $users = $this->getUsers( $postData['deleteusers'] ); } - $checkMerge = false; $checkDelete = false; } - ## fusion ou délétion en tant que telle - if ( isset( $postData['merge'] ) && $checkMerge ) { - $delete = ( $postData['merge'] == 'delete' ); - $targetUser = self::getUsers( false, 'user_id IN(' . $postData['addusers'] . ')' ); - $usersToMerge = self::getUsers( false, 'user_id IN(' . $postData['deleteusers'] . ')' ); - foreach ( $usersToMerge as $key => $userToMerge ) { - $r = MGWUserMerge::execute( $userToMerge['username'], $targetUser[0]['username'], $delete, [ $this, 'msg' ] ); - - if ( !$delete && $r['done'] ) { - $s = self::oldUserClean( $userToMerge['username'], $targetUser[0]['username'] ); - } - else - $s = true; - - if ( $r['done'] && $s ) - $m = 'SUCCES'; - elseif ( $r['done'] && !$s ) { - $m = 'ECHEC'; - $r['message'] .= '
La fusion a réussi mais l\'ancien utilisateur n\'a pas été nettoyé.'; - } - else - $m = 'ECHEC'; - - $userToMerge['message'] = $m . ' : ' . $r['message']; - $actionInfo[] = $userToMerge; - - $uDisplay = $postData['addusers']; - if ( !$delete ) $uDisplay .= ',' . $postData['deleteusers']; - $users = self::getUsers( false, 'user_id IN(' . $uDisplay . ')' ); + ## délétion en tant que telle + if ( $deleteAsked && $checkDelete ) { + $targetUser = $this->getUsers( $postData['addusers'] ); + $deleteUsers = $this->getUsers( $postData['deleteusers'] ); + foreach ( $deleteUsers as $key => $deleteUser ) { + // merge en effançant l'ancien utilisateur + // implique suppression de compte et de page + $merge = MGWUserMerge::execute( + $deleteUser['user_id'], + $targetUser[0]['user_id'], + true, + [ $this, 'msg' ] + ); + if ( $merge->done() && $postData['delete-replace'] == 'oui' ) { + // on remplace l'ancien nom par le nouveau dans le contenu de toutes les pages + $replace = new MGWReplaceText( [ + "target" => $deleteUser['user_name'], + "replace" => $targetUser[0]['user_name'], + "regex" => false, + "nsall" => true, + "ns" => null, + "summary" => 'Tache automatisée lors de la fusion des utilisateurs.', + "user" => $wgUser->getName() + ] ); + $replace = $replace->execute(); + } + if ( !$merge->done() ) { + $message = 'ECHEC :' . $deleteUser['user_name'] . + ' n\'a pas pu être supprimé (' . $merge->mess() . ')'; + } + elseif ( $merge->done() || !$replace->done() ) { + $details = ''; + if ( is_array( $replace->extra() ) && count( $replace->extra() ) > 0 ) { + foreach ( $replace->extra() as $detail ) { + $details .= ' / ' . $detail['message']; + } + } + $message = 'SUCCES PARTIEL :
    ' . + '
  • ' . $deleteUser['user_name'] . ' a bien été supprimé (' . $merge->mess() . ')
  • ' . + '
  • Son nom n\'a pas été supprimé dans les pages (' . $replace->mess() . ')' . $details . '
  • '; + } + else { + $message = 'SUCCES :
      ' . + '
    • ' . $deleteUser['user_name'] . 'a bien été supprimé (' . $merge->mess() . ')
    • '; + if ( isset( $replace ) ) { + $details = ''; + foreach ( $replace->extra() as $detail ) { + $details .= ' / ' . $detail['message']; + } + $message .= '
    • Son nom a bien été supprimé dans les pages (' . $replace->mess() . ')' . $details . '
    • '; + } + $message .= '
    '; + } + $deleteUser['message'] = $message; + $actionInfo[] = $deleteUser; } + $users = $this->getUsers( $postData['addusers'] ); } - # affichage des utilisateurs valides - if ( isset( $postData['validusers'] ) ) { - $users = self::getUsers( true ); - } - - # mise à jour de la table mgw_utilisateurs - if ( isset( $postData['populate'] ) ) { - $users = self::getUsers( true, 'user_id IN(' . $postData['addusers'] . ')' ); - foreach ( $users as $validUser ) { - $r = self::updateUtilisateurs( $validUser ); - $mess = ( $r['done'] ) ? 'SUCCES : ' : 'ECHEC : '; - $validUser['message'] = $mess . $r['message']; - $actionInfo[] = $validUser; - } + # mise à jour de la table mgw_utilisateurs + if ( isset( $postData['action'] ) && $postData['action'] == 'db_update' ) { + $list = explode( ',', $postData['addusers'] ); + $actionInfo = []; + foreach ( $list as $user_id ) { + $MGWuser = MGWUser::newFromUserId( (int)$user_id ); + $status = $MGWuser->db_update(); + if ( $status->done() ) { + $info = 'SUCCES: ' . $status->mess(); + } else { + $info = 'ECHEC: ' . $status->mess(); + } + $actionInfo[] = [ + 'user_id' => $user_id, + 'user_name' => $MGWuser->get_data()['user_name'], + 'page_url' => $MGWuser->get_data()['page_url'], + 'message' => $info + ]; + } + $users = $this->getUsers( $postData['addusers'] ); } // TRI - if ( isset($users) ) { + if ( isset( $users ) ) { // tri d'un tableau à 2 dimentions uasort ( $users , function ($a, $b) { - global $tri; + global $tri; if ( $a[$tri[1]] == $b[$tri[1]] ) { if ( $tri[2] != '' ) { if ( $a[$tri[2]] == $b[$tri[2]] ) return 0; @@ -246,59 +356,24 @@ public function execute( $sub ) }); } else $users = []; + if ( isset( $actionInfo ) ) { $users = array_merge( $actionInfo, $users ); } + if ( sizeof( $users ) < 1 ) { $users[] = array( - 'id' => '0', - 'username' => '', - 'userpagelink' => '', + 'user_id' => '0', + 'user_name' => '', + 'page_url' => '', 'message' => 'Aucun utilisateur ne correspond à la requête.' ); } // AFFICHAGE DES UTILISATEURS $out->addHTML('
    '); - $select = &$this->select; - $check = &$this->check; foreach( $users as $row ) { - $test = true; - //'nouserpage', 'nousertemplate', 'sysop', 'bureaucrat', 'U2', 'nouseremail', 'emailduplicate' - if ( ( $check['nouserpage']['hide'] == 'checked' && is_null( $row['userpagelink']) ) || - ( $check['nouserpage']['only'] == 'checked' && !is_null( $row['userpagelink']) ) - ) $test = false; - if ( ( $check['nousertemplate']['hide'] == 'checked' && is_null($row['nom']) ) || - ( $check['nousertemplate']['only'] == 'checked' && !is_null($row['nom']) ) - ) $test = false; - if ( ( $check['redirect']['hide'] == 'checked' && !is_null($row['redirect']) ) || - ( $check['redirect']['only'] == 'checked' && is_null($row['redirect']) ) - ) $test = false; - if ( ( $check['sysop']['hide'] == 'checked' && in_array('sysop', $row['groups']) ) || - ( $check['sysop']['only'] == 'checked' && !in_array('sysop', $row['groups']) ) - ) $test = false; - if ( ( $check['bureaucrat']['hide'] == 'checked' && in_array('bureaucrat', $row['groups']) ) || - ( $check['bureaucrat']['only'] == 'checked' && !in_array('bureaucrat', $row['groups']) ) - ) $test = false; - if ( ( $check['U2']['hide'] == 'checked' && in_array('U2', $row['groups']) ) || - ( $check['U2']['only'] == 'checked' && !in_array('U2', $row['groups']) ) - ) $test = false; - if ( ( $check['nouseremail']['hide'] == 'checked' && $row['email'] == '' ) || - ( $check['nouseremail']['only'] == 'checked' && $row['email'] != '' ) - ) $test = false; - if ( ( $check['differentname-real']['hide'] == 'checked' && $row['username'] != $row['realname'] ) || - ( $check['differentname-real']['only'] == 'checked' && $row['username'] == $row['realname'] ) - ) $test = false; - if ( ( $check['differentname-page']['hide'] == 'checked' && - $row['username'] != $row['prenom'].' '.$row['nom'] && !is_null($row['nom']) ) || - ( $check['differentname-page']['only'] == 'checked' && - $row['username'] == $row['prenom'].' '.$row['nom'] && !is_null($row['nom']) ) - ) $test = false; - if ( isset( $row['message'] ) ) $test = true; - - if ( $test ) { - $out->addHTML( $this->displayUser( $row ) ); - } + $out->addHTML( $this->displayUser( $row ) ); } $out->addHTML('
    '); $out->addHTML( $this->displayActions() ); @@ -308,34 +383,74 @@ public function execute( $sub ) /** * @param array $postData */ - private function setOptions( &$postData ) + private function set_select( &$postData ) { - global $tri; - $select = &$this->select; - $check = &$this->check; + $select = &$this->select; + + $tri[1] = ( isset( $postData['tri1'] ) ) + ? $postData['tri1'] : 'user_id'; - $tri[1] = ( isset( $postData['tri1'] ) ) ? $postData['tri1'] : 'id'; - $tri[2] = ( isset( $postData['tri2'] ) ) ? $postData['tri2'] : ''; + $tri[2] = ( isset( $postData['tri2'] ) ) + ? $postData['tri2'] : ''; foreach ( $this->triOptions as $value ) { - if ( isset($postData['tri1'] ) && $postData['tri1'] == $value ) $select[1][$value] = 'selected'; - else $select[1][$value] = ''; - if ( isset($postData['tri2'] ) && $postData['tri2'] == $value ) $select[2][$value] = 'selected'; - else $select[2][$value] = ''; - if ( !in_array( 'selected', $select[2] ) ) $select[2]['...'] = 'selected'; - else $select[2]['...'] = ''; + + if ( isset($postData['tri1'] ) && $postData['tri1'] == $value ) { + $select[1][$value] = 'selected'; + } else { + $select[1][$value] = ''; + } + + if ( isset($postData['tri2'] ) && $postData['tri2'] == $value ) { + $select[2][$value] = 'selected'; + } else { + $select[2][$value] = ''; + } + + if ( !in_array( 'selected', $select[2] ) ) { + $select[2]['...'] = 'selected'; + } else { + $select[2]['...'] = ''; + } } + return $tri; + } + + /** + * @param array $postData + */ + private function set_check( &$postData ) + { + $check = &$this->check; + foreach ( $this->filtreOptions as $value ) { - $check[$value]['view'] = ( isset( $postData[$value] ) && $postData[$value] == 'view' ) ? 'checked' : ''; - $check[$value]['hide'] = ( isset( $postData[$value] ) && $postData[$value] == 'hide' ) ? 'checked' : ''; - $check[$value]['only'] = ( isset( $postData[$value] ) && $postData[$value] == 'only' ) ? 'checked' : ''; - if ( !in_array('checked', [ $check[$value]['view'], $check[$value]['hide'], $check[$value]['only'] ] ) ) + + $check[$value]['view'] = + ( isset( $postData[$value] ) && $postData[$value] == 'view' ) + ? 'checked' : ''; + + $check[$value]['hide'] = + ( isset( $postData[$value] ) && $postData[$value] == 'hide' ) + ? 'checked' : ''; + + $check[$value]['only'] = + ( isset( $postData[$value] ) && $postData[$value] == 'only' ) + ? 'checked' : ''; + + if ( !in_array( + 'checked', + [ + $check[$value]['view'], + $check[$value]['hide'], + $check[$value]['only'] + ] ) + ) { $check[$value]['view'] = 'checked'; + } } } - /** * @param string $option { 'tri' | 'filtres' } * @return mixed (string ou false) @@ -346,7 +461,9 @@ private function makeHeadForm ( ) $select = &$this->select; $check = &$this->check; - $return = '
    '; + $return = ' + '; + // TRI $return .= '
    Tri @@ -376,7 +493,7 @@ private function makeHeadForm ( ) - + '; @@ -384,7 +501,7 @@ private function makeHeadForm ( ) foreach ( $this->filtreOptions as $filtreOption ) { foreach ( $this->filtreOptionsTitles as $title => $titleOptions ) { if ( in_array( $filtreOption, $titleOptions ) && !$writeTitle ){ - $return .= ''; + $return .= ''; $writeTitle = true; } elseif ( !in_array( $filtreOption, $titleOptions ) ) @@ -392,7 +509,7 @@ private function makeHeadForm ( ) } $return .= ' - +
    seul
    '.$this->getMsg( 'specialcheckaccounts-'.$title ).'
    '.Msg::get( 'specialcheckaccounts-'.$title ).'
    '.$this->getMsg( 'specialcheckaccounts-'.$filtreOption ).''.Msg::get( 'specialcheckaccounts-'.$filtreOption ).' + + + + + + + + + + + + + + + + + + +
    USERPAGE
    '; return $return; } @@ -416,68 +555,68 @@ private function makeHeadForm ( ) * @return mixed (string HTML ou false) */ private function displayUser ( &$row ) { - // si l'entrée correspond à un d'erreur on l'affiche... + // si l'entrée correspond à un message on l'affiche... if ( isset( $row['message'] ) ) { - return '
       '.$row['id'].' ' . $row['username'] . ' : -    ' . $row['message'] . '
    '; + $userName = ( !isset( $row['page_url'] ) || is_null( $row['page_url'] ) ) + ? $row['user_name'] : '' . $row['user_name'] . ''; + return ' + + + + +
    '.$row['user_id'].'
    ' . $userName . '
    ' . $row['message'] . '
    '; } - // ... sinon on affiche l'utilisateur - $return = ' + + // sinon on affiche l'utilisateur: + $class = ( $row['mgw_exists'] ) ? 'mgw-specialcheckaccount-mgw-in' : 'mgw-specialcheckaccount-mgw-out'; + $userName = $row['user_name']; + $pageInfo = 'page utilisateur inexistante'; + + if ( $row['page'] ) { + $userName = '' . $row['user_name'] . ''; + + if ( $row['page_redirect'] ) { + $pageInfo = '#redirection: ' + . $row['page_redirect_title'] . ''; + } + elseif ( !$row['page_template'] ) { + $pageInfo = '! {{Personne}}'; + } + else { + $pageInfo = ''; + } + } + + $return = '
    - - - - + + + + + '; $return .= ' - - - - + + + + + '; $return .= ' - + + '; $return .= ' - - - '; - if ( is_null( $row['userpagelink'] ) ) { - $return .= ' - - - - - - '; - } - else { - $return .= ' - - - '; - if ( !is_null($row['redirect'] ) ) - $return .= ' - - - '; - elseif ( is_null($row['nom'] ) ) - $return .= ' - - - '; - else { - $return .= ' - + '; - } - } $return .= '
    '.$row['id'].'' . $row['username'] . ' + '.$row['user_id'].'' . $userName . '' . $pageInfo . ' +
    realname' . $row['realname'] . 'real_name' . $row['user_real_name'] . '' . $row['page_template_prenom'] . ' ' . $row['page_template_nom'] . '
    email' . $row['email'] . '' . $row['user_email'] . '' . $row['page_template_email'] . '
    groups' . implode( ', ', $row['groups'] ) . '
    User pagepage utilisateur inexistante
    User page#redirection: ' . $row['redirect'] . '
    Modèle {{Personne}} inexistant et redirection absence ou invalide
    ' . $row['prenom'] . ' ' . $row['nom'] . '' . implode( ', ', $row['user_groups'] ) . '
    '; return $return; } @@ -487,148 +626,143 @@ private function displayUser ( &$row ) { */ private function displayActions() { global $_SERVER; - return ' + return '
    - - - - + + + + - - - - + + + + - - - + + + - + + + + + + + + - - -
    inoutadd + corrige la casse "Prénom NOM" dans le modèle {{Personne}}
    add +
    + +
    modifie user_name, user_email et user_real_name selon les données du modèle {{Personne}}
    -> + delvide les champs real_name, email et groups du compte utilisateur
    add +
    + +
    delsupprime les comptes utilisateur "delete" en les fusionnant avec "add"
    add + <- - -
    - -
    - - - - - - - - -
    '; +
    '; } /** * Requête la base et les pages utilisateur * - * @param bool $isValid + * @param string $list user_id values in comma-separated string + * @param string $filter ( 'valid'|'page_template'|'page'|'page_redirect' ) * @param mixed $where string ou false (ex. : 'cat_pages > 0') * @param mixed $options array ou false (ex. : array( 'ORDER BY' => 'cat_title ASC' )) * @return array|null */ - private function getUsers ( $isValid, $where = false, $options = false ) { + private function getUsers ( $list = '', $where = '', $options = false ) { + + // Construction de la liste des utilisateurs + if ( empty( $list ) ) { + + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $colnames = array( 'user_id' ); + + if ( !$options ) { + $res = $dbr->select( 'user', $colnames, $where ); + } + elseif ( is_array($options) ) { + $res = $dbr->select( 'user', $colnames, $where, __METHOD__, $options ); + } + else throw new \Exception("Erreur SpecialCheckAccounts::getUsers() : arguments invalides", 1); + + foreach( $res as $row ) { + if ( empty( $list ) ) { + $list = $row->user_id; + } + else { + $list .= ',' . $row->user_id; + } + } + } + // construction des données + $list = explode( ',', $list ); $return = []; + foreach( $list as $user_id ) { + $user_id = (int)$user_id; + + $MGWuser = MGWUser::newFromUserId( $user_id ); + $MWuser = UserF::getUserFromId( $user_id ); + + $data = $MGWuser->get_data(); + PhpF::false( $data['user_real_name'] ); + PhpF::false( $data['user_email'] ); + $data['user_groups'] = $MWuser->getGroups(); + $data['user_registration'] = $MWuser->getRegistration(); + PhpF::empty( $data['user_real_name'] ); + PhpF::empty( $data['user_email'] ); + $data['mgw_exists'] = $MGWuser->get_mgw_exists(); + $data['page'] = $MGWuser->get_page_exists(); + PhpF::false( $data['page_redirect'] ); + PhpF::false( $data['page_template'] ); + PhpF::empty( $data['user_'] ); + PhpF::empty( $data['page_template_prenom'] ); + PhpF::empty( $data['page_template_nom'] ); + PhpF::empty( $data['page_template_email'] ); + $data['same_email'] = $MGWuser->same_email(); + $data['same_names'] = $MGWuser->same_names(); + + foreach ( $data['user_groups'] as $group ) { + $data[ $group ] = true; + } + foreach ( $this->filtreOptionsTitles['user_groups'] as $group ) { + PhpF::false( $data[ $group ] ); + } - /* Construction de la liste des utilisateurs depuis la db */ - $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); - $dbr = $lb->getConnectionRef( DB_REPLICA ); - $colnames = array( - 'user_id', - 'user_name', - 'user_real_name', - 'user_email'); - if ( !$where && !$options ) { - $res = $dbr->select( 'user', $colnames ); - } - elseif ( is_string($where) && !$options ) { - $res = $dbr->select( 'user', $colnames, $where ); - } - elseif ( is_string($where) && is_array($options) ) { - $res = $dbr->select( 'user', $colnames, $where, __METHOD__, $options ); - } - elseif ( !$where && is_array($options) ) { - $res = $dbr->select( 'user', $colnames, '', __METHOD__, $options ); - } - else throw new \Exception("Erreur SpecialCheckAccounts::getUsers() : arguments invalides", 1); + // contrôle de la sortie selon les filtres + $display = true; + foreach ( $this->check as $item => $control ) { - ///////// PATCH TEMPORAIRE ///////////// - foreach( $res as $row ) { - $user = UserF::getUserFromId( $row->user_id ); - $userpage = PageF::getPageFromTitleText( $user->getName(), NS_USER , true ); - if ( !is_null( $userpage ) ) { - $userpagelink = $userpage->getTitle()->getFullURL(); - $infos = PageF::getPageTemplateInfos( $userpage, 'Personne', [ 'Nom', 'Prénom' ] ); - if ( !is_null( $infos ) ) { - $nom = $infos['Nom']; - $prenom = $infos['Prénom']; - $redirect = null; - $redirectURL = null; - $valid = true; + if ( array_key_exists( 'hide', $control ) + && $control[ 'hide' ] == 'checked' + && ( !is_bool( $data[ $item ] ) || $data[ $item ] ) + ) { + $display = false; } - else { - $nom = null; - $prenom = null; - $valid = false; - $redirect = PageF::getPageRedirect( $userpage ); - if ( !is_null( $redirect ) ) { - // vérification de la validité de la redirection - $screen = preg_match( '/^Utilisateur:(.*)$/', $redirect, $matches ); - if ( $screen < 1 ) { - $redirect = null; - $redirectURL = null; - } - else { - $title = PageF::getTitleFromText( $matches[1], NS_USER, true ); - if ( is_null($title) ) $redirect = null; - else $redirectURL = $title->getFullURL(); - } - } + + if ( array_key_exists( 'only', $control ) + && $control[ 'only' ] == 'checked' + && !$data[ $item ] + ) { + $display = false; } } - else { - $userpagelink = null; - $redirect = null; - $nom = null; - $prenom = null; - $valid = false; - } - if ( !isset( $redirectURL ) ) - $redirectURL = null; - - if ( !isset($row->user_email) || is_null($row->user_email) || $row->user_email == '' ) - $valid = false; - - if ( !$isValid ) - $valid = true; - - ///////////////////////////////////////////// - if ( $valid ) { - $return[] = array( - 'id' => $row->user_id, - 'username' => $row->user_name, - 'realname' => $row->user_real_name, - 'email' => $row->user_email, - 'groups' => $user->getGroups(), - 'registration' => $user->getRegistration(), - 'nom' => $nom, - 'prenom' => $prenom, - 'userpagelink' => $userpagelink, - 'redirect' => $redirect, - 'redirectURL' => $redirectURL - ); - } + // sortie + if ( $display ) { + $return[] = $data; + } } return $return; } @@ -639,13 +773,13 @@ private function getUsers ( $isValid, $where = false, $options = false ) { * @return mixed (array ou null) */ private function getEmailDuplicates ( ) { - $return = []; + $mails = []; - /* Requête */ + // Requête sur les mails $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); $dbr = $lb->getConnectionRef( DB_REPLICA ); $colnames = array( 'user_email' ); - $where = ''; + $where = 'user_email IS NOT NULL'; $options = array( "GROUP BY" => "user_email", "HAVING" => "COUNT(user_email) > 1" @@ -653,9 +787,20 @@ private function getEmailDuplicates ( ) { $res = $dbr->select( 'user', $colnames, $where, __METHOD__, $options ); foreach( $res as $row ) { - $return[] = $row->user_email; + $mails[] = $row->user_email; } - return $return; + + // on récupère les utilisateurs correspondants + $user_ids = []; + foreach ( $mails as $mail ) { + $colnames = array( 'user_id' ); + $where = 'user_email = \'' . $mail . '\''; + $res = $dbr->select( 'user', $colnames, $where ); + foreach( $res as $row ) { + $user_ids[] = $row->user_id; + } + } + return $user_ids; } /** @@ -698,15 +843,15 @@ function ($text) { // If there's at least one replacement, modify the page if ( $newtext != $oldtext ) { - $edit_summary = $this->getMsg('specialcheckaccount-sanitize-summary'); + $edit_summary = Msg::get('specialcheckaccount-sanitize-summary'); $newcontent = new WikitextContent( $newtext ); $userpage->doEditContent( $newcontent, $edit_summary, EDIT_AUTOSUMMARY ); } else { $return[] = array( - 'id' => $id, - 'username' => $userpage->getTitle()->getFullText(), - 'userpagelink' => $userpage->getTitle()->getFullURL(), + 'user_id' => $id, + 'user_name' => $userpage->getTitle()->getFullText(), + 'page_url' => $userpage->getTitle()->getFullURL(), 'message' => 'Aucune modification n\'a été faite.' ); } @@ -717,116 +862,41 @@ function ($text) { /** * @param string $oldName * @param string $newName - * @return array $return : utilisateurs et messages + * @return true|string $e */ - private function oldUserClean( $oldName, $newName ) { + private function userPageRedirect( $oldName, $newName ) { - $oldUser = UserF::getUserFromNames( $oldName, null, true ); - if ( is_null($oldUser) ) { - throw new \Exception('oldUserClean: $oldUser doit être un utilisateur valide.', 1); - return false; - } + $oldUserPage = PageF::getPageFromTitleText( $oldName, NS_USER ); - # nettoyage de l'utilisateur dans la base - $oldUser->setEmail(''); - foreach ( $oldUser->getGroups() as $group ) { - $oldUser->removeGroup( $group ); - } - $oldUser->saveSettings(); + if ( is_null( $oldUserPage ) ) { + return Status::newFailed( "Redirection impossible: 'Utilisateur:$oldName' doit être une page existante." ); + } - # nettoyage de la page utilisateur - $oldUserPage = PageF::getPageFromTitleText( $oldName, NS_USER ); $newtext = '#REDIRECTION [[Utilisateur:' . $newName . ']]'; - $edit_summary = $this->getMsg('specialcheckaccount-merge-summary', [ $newName ] ); + $edit_summary = Msg::get('specialcheckaccount-merge-summary', [ $newName ] ); $flags = EDIT_AUTOSUMMARY; $newcontent = new WikitextContent( $newtext ); + $oldUserPage->doEditContent( $newcontent, $edit_summary, $flags ); - /* ne marche pas ... ?? - if ( PageF::writeContent( $oldUserPage, $newtext, $edit_summary, $flags ) ) - return true; - else return false; - */ + + return Status::newDone( "La page 'Utilisateur:$oldName' a bien été redirigée vers 'Utilisateur:$newName'."); } - /** - * @param array $arrUser : une entrée du tableau retourné par $this->getUsers() - */ - public function updateUtilisateurs( $userArray ) { + private function userEmptyAccount( $userName ) { - global $wgUser; + $oldUser = UserF::getUserFromNames( $oldName, null, true ); - $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); - $dbw = $lb->getConnectionRef( DB_MASTER ); - - $res = $dbw->select( 'mgw_utilisateurs', [ 'utilisateur_user_id', 'utilisateur_nom', 'utilisateur_prenom' ], - 'utilisateur_id = ' . $userArray['id'] ); - - if ( sizeof( $res < 1 ) ) { - $dbw->insert( - 'mgw_utilisateurs', - [ - 'utilisateur_user_id' => $userArray['id'], - 'utilisateur_nom' => $userArray['nom'], - 'utilisateur_prenom' => $userArray['prenom'], - 'utilisateur_updater_user_id' => $wgUser->getId(), - 'utilisateur_update_time' => date('Y-m-d H:i:s') - ] - ); - $done = true; - $message = $userArray['prenom'] . ' ' . $userArray['nom'] . ' a été ajouté à la table mgw_utilisateurs.'; - } - elseif ( sizeof( $res < 1 ) ) { - $done = false; - $message = 'Erreur : l\'utilisateur ' . $userArray['id'] . ' figure ' . - sizeof( $res ) . ' fois dans la table mgw_utilisateurs'; - } - elseif ( $res[0]->utilisateur_nom != $userArray['nom'] ) { - $dbw->update( - 'mgw_utilisateurs', [ - 'utilisateur_nom' => $userArray['nom'], - 'utilisateur_updater_user_id' => $wgUser->getId(), - 'utilisateur_update_time' => date('Y-m-d H:i:s') - ], - 'utilisateur_id = ' . $userArray['id'] ); - $done = true; - $message = $userArray['prenom'] . ' ' . $userArray['nom'] . ' a été mis à jour.'; - } - elseif ( $res[0]->utilisateur_prenom != $userArray['prenom'] ) { - $dbw->update( - 'mgw_utilisateurs', - [ - 'utilisateur_prenom' => $userArray['prenom'], - 'utilisateur_updater_user_id' => $wgUser->getId(), - 'utilisateur_update_time' => date('Y-m-d H:i:s') - ], - 'utilisateur_id = ' . $userArray['id'] - ); - $done = true; - $message = $userArray['prenom'] . ' ' . $userArray['nom'] . ' a été mis à jour.'; - } - return [ 'done' => $done, 'message' => $message ]; - } - - /** - * @param string $mess : clé du message - * @param array $args : liste des arguments à passer dans le message ( $1, $2, etc. ) - * @return array $return : utilisateurs et messages - */ - private function getMsg ( $mess, $args = [] ) { - if ( isset( $this->messages[ $mess ] ) ) { - $out = $this->messages[ $mess ]; - if ( sizeof($args) > 0 ) { - foreach ( $args as $key => $arg ) { - $needle = '$' . ($key + 1); - $out = str_replace( $needle, $arg, $out ); - } - } - return $out; + if ( is_null($oldUser) ) { + return Status::newFailed( 'Nettoyage du compte utilisateur: $oldUser doit être un utilisateur valide.' ); } - else { - $link = GetJsonPage::getLink('messages'); - return '<' . $mess . '>'; + + $oldUser->setEmail(''); + foreach ( $oldUser->getGroups() as $group ) { + $oldUser->removeGroup( $group ); } + $oldUser->saveSettings(); + + return Status::newDone( "L'e-mail et les groupes de l'utilisateur $userName ont été effacés."); } protected function getGroupName() { diff --git a/includes/SpecialCheckGroups.php b/includes/SpecialCheckGroups.php new file mode 100644 index 0000000..678a93a --- /dev/null +++ b/includes/SpecialCheckGroups.php @@ -0,0 +1,487 @@ +triOptions = ['page_id', 'page_title', 'referent', 'year']; + $this->filtreOptions = [ + 'archive', + 'nogrouptemplate', + 'redirect' + ]; + $this->filtreOptionsTitles = [ + ]; + } + + /** + * @param string $sub The subpage string argument (if any). + */ + + public function execute( $sub ) + { + global $tri; + $postData = $this->getRequest()->getPostValues(); + + // définition de $select[] et $check[] + $this->setOptions( $postData ); + + // affichage du formulaire d'entête + $this->setHeaders(); + $out = $this->getOutput(); + $out->addModules('ext.mgwiki-specialcheckgroups'); + $out->setPageTitle( Msg::get('specialcheckgroups-title') ); + $out->addHTML( $this->makeHeadForm() ); + + // ACTIONS + + # affichage brut + if ( isset( $postData['afficher'] ) ) { + $groups = $this->getGroups( false ); + } + + # pages groupe cochés uniquement + if ( isset( $postData['select'] ) ) { + $groups = self::getGroups( false, 'page_id IN(' . $postData['addgroups'] . ')' ); + } + + # affichage des groupes valides uniquement + if ( isset( $postData['validgroups'] ) ) { + $groups = self::getGroups( true ); + } + + # mise à jour des tables mgw_group et mgw_membres + if ( isset( $postData['populate'] ) ) { + $groups = self::getGroups( true, 'user_id IN(' . $postData['addgroups'] . ')' ); + foreach ( $groups as $validGroup ) { + /* + $r = UserF::setMGWUser( $validUser['id'], $validUser['nom'], $validUser['prenom'] ); + $mess = ( $r['done'] ) ? 'SUCCES : ' : 'ECHEC : '; + $validUser['message'] = $mess . $r['message']; + $actionInfo[] = $validUser; + */ + } + } + + // TRI + if ( isset($groups) ) { + // tri d'un tableau à 2 dimentions + uasort ( $groups , function ($a, $b) { + global $tri; + if ( $a[$tri[1]] == $b[$tri[1]] ) { + if ( $tri[2] != '' ) { + if ( $a[$tri[2]] == $b[$tri[2]] ) return 0; + return ( $a[$tri[2]] < $b[$tri[2]] ) ? -1 : 1; + } + return 0; + } + return ( $a[$tri[1]] < $b[$tri[1]] ) ? -1 : 1; + }); + } + else $groups = []; + if ( isset( $actionInfo ) ) { + $groups = array_merge( $actionInfo, $groups ); + } + if ( sizeof( $groups ) < 1 ) { + $groups[] = array( + 'page_id' => '0', + 'page_title' => '', + 'page_url' => '', + 'message' => 'Aucun utilisateur ne correspond à la requête.' + ); + } + + // AFFICHAGE DES GROUPES + $out->addHTML('
    '); + $select = &$this->select; + $check = &$this->check; + foreach( $groups as $row ) { + /* + filtres: + 'archive', + 'nogrouptemplate', + 'redirect' + champs: + 'archive' + 'template' + 'page_id' + 'page_title' + 'page_url' + 'type' + 'institution' + 'referent' + 'membres' + 'year' + 'redirect' + 'redirect_url' + */ + $test = true; + //'nouserpage', 'nousertemplate', 'sysop', 'bureaucrat', 'U2', 'nouseremail', 'emailduplicate' + if ( ( $check['archive']['hide'] == 'checked' && $row['archive'] == 'Oui' ) || + ( $check['archive']['only'] == 'checked' && $row['archive'] != 'Oui' ) + ) $test = false; + if ( ( $check['nogrouptemplate']['hide'] == 'checked' && !$row['template'] ) || + ( $check['nogrouptemplate']['only'] == 'checked' && $row['template'] ) + ) $test = false; + if ( ( $check['redirect']['hide'] == 'checked' && !is_null($row['redirect']) ) || + ( $check['redirect']['only'] == 'checked' && is_null($row['redirect']) ) + ) $test = false; + + if ( isset( $row['message'] ) ) $test = true; + + if ( $test ) { + $out->addHTML( $this->displayGroup( $row ) ); + } + } + $out->addHTML('
    '); + $out->addHTML( $this->displayActions() ); + } + + + /** + * @param array $postData + */ + private function setOptions( &$postData ) + { + global $tri; + $select = &$this->select; + $check = &$this->check; + + $tri[1] = ( isset( $postData['tri1'] ) ) ? $postData['tri1'] : 'id'; + $tri[2] = ( isset( $postData['tri2'] ) ) ? $postData['tri2'] : ''; + + foreach ( $this->triOptions as $value ) { + if ( isset($postData['tri1'] ) && $postData['tri1'] == $value ) $select[1][$value] = 'selected'; + else $select[1][$value] = ''; + if ( isset($postData['tri2'] ) && $postData['tri2'] == $value ) $select[2][$value] = 'selected'; + else $select[2][$value] = ''; + if ( !in_array( 'selected', $select[2] ) ) $select[2]['...'] = 'selected'; + else $select[2]['...'] = ''; + } + + foreach ( $this->filtreOptions as $value ) { + $check[$value]['view'] = ( isset( $postData[$value] ) && $postData[$value] == 'view' ) ? 'checked' : ''; + $check[$value]['hide'] = ( isset( $postData[$value] ) && $postData[$value] == 'hide' ) ? 'checked' : ''; + $check[$value]['only'] = ( isset( $postData[$value] ) && $postData[$value] == 'only' ) ? 'checked' : ''; + if ( !in_array('checked', [ $check[$value]['view'], $check[$value]['hide'], $check[$value]['only'] ] ) ) + $check[$value]['view'] = 'checked'; + } + } + + + /** + * @param string $option { 'tri' | 'filtres' } + * @return mixed (string ou false) + */ + private function makeHeadForm ( ) + { + global $_SERVER; + $select = &$this->select; + $check = &$this->check; + + $return = '
    '; + // TRI + $return .= ' +
    Tri + + + + + +
    + + + + + +
    +
    '; + // FILTRES + $return .= ' +
    Filtres + + + + + + + '; + $writeTitle = false; + foreach ( $this->filtreOptions as $filtreOption ) { + foreach ( $this->filtreOptionsTitles as $title => $titleOptions ) { + if ( in_array( $filtreOption, $titleOptions ) && !$writeTitle ){ + $return .= ''; + $writeTitle = true; + } + elseif ( !in_array( $filtreOption, $titleOptions ) ) + $writeTitle = false; + } + $return .= ' + + + + + + '; + } + $return .= ' +
    seul
    '.Msg::get( 'specialcheckgroups-'.$title ).'
    '.Msg::get( 'specialcheckgroups-'.$filtreOption ).'
    +
    + +
    '; + return $return; + } + + + /** + * @param array $row + * @return mixed (string HTML ou false) + */ + private function displayGroup ( &$row ) { + // si l'entrée correspond à un d'erreur on l'affiche... + if ( isset( $row['message'] ) ) { + return '
       '.$row['page_id'].' ' . $row['page_title'] . ' : +    ' . $row['message'] . '
    '; + } + // ... sinon on affiche l'utilisateur + $return = ' + + + + + + '; + if ( $row['template'] ) { + $return .= ' + + + + + '; + $return .= ' + + + + + '; + $return .= ' + + + + + '; + $return .= ' + + + + + '; + $return .= ' + + + + + '; + } + else { + $return .= ' + + + + + + '; + } + if ( !is_null($row['redirect'] ) ) { + $return .= ' + + + + + + '; + } + $return .= '
    '.$row['page_id'].'' . $row['page_title'] . '
    archive' . $row['archive'] . '
    institution' . $row['institution'] . '
    type' . $row['type'] . '
    referent' . $row['referent'] . '
    membres' . $row['membres'] . '
    modèle {{Groupe}} inexistant
    #redirection: ' . $row['redirect'] . '
    '; + return $return; + } + + /** + * @return string HTML + */ + private function displayActions() { + global $_SERVER; + return ' + + + + + + + + + + + + + + + + + + + +
    inout
    <- - -
    + +
    + + + + + +
    '; + } + + /** + * Requête la base et les pages groupe + * + * @param bool $isValid + * @param mixed $where string ou null + * @param mixed $options array ou false (ex. : array( 'ORDER BY' => 'cat_title ASC' )) + * @return array|null + */ + private function getGroups ( $isValid, $where = null, $options = false ) { + + $return = []; + + /* Construction de la liste des groupes depuis les pages */ + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $colnames = array( 'page_id', 'page_title' ); + $cond = 'page_namespace = 730'; + if ( !is_null( $where ) ) $cond .= ' AND ' . $where; + + if ( !$options ) { + $res = $dbr->select( 'page', $colnames, $cond ); + } + elseif ( is_array($options) ) { + $res = $dbr->select( 'page', $colnames, $cond, __METHOD__, $options ); + } + else throw new \Exception("Erreur SpecialCheckGroups::getGroups() : arguments invalides", 1); + + ///////// PATCH TEMPORAIRE ///////////// + foreach( $res as $row ) { + $group = [ + 'page_id' => $row->page_id, + 'page_title' => $row->page_title, + 'page_url' => null, + 'template' => false, + 'archive' => null, + 'type' => null, + 'institution' => null, + 'referent' => null, + 'membres' => null, + 'year' => null, + 'redirect' => null, + 'redirect_url' => null, + ]; + $valid = false; + + $page = PageF::getPageFromId( $row->page_id ); + if ( !is_null( $page ) ) { + $group['page_url'] = $page->getTitle()->getFullURL(); + $infos = PageF::getPageTemplateInfos( + $page, + 'Groupe', + [ + 'Archivé', + 'Type de groupe', + 'Institution de rattachement', + 'Tuteur ou modérateur', + 'Membres', + 'Année' + ] + ); + if ( !is_null( $infos ) ) { + $group['archive'] = $infos['Archivé']; + $group['type'] = $infos['Type de groupe']; + $group['institution'] = $infos['Institution de rattachement']; + $group['referent'] = $infos['Tuteur ou modérateur']; + $group['membres'] = $infos['Membres']; + $group['year'] = $infos['Année']; + $group['template'] = true; + $valid = true; + } + else { + $group['redirect'] = PageF::getPageRedirect( $page ); + if ( !is_null( $group['redirect'] ) ) { + // vérification de la validité de la redirection + $screen = preg_match( '/^Groupe:(.*)$/', $group['redirect'], $matches ); + if ( $screen > 0 ) { + $title = PageF::getTitleFromText( $matches[1], NS_GROUP, true ); + if ( !is_null( $title ) ) $group['redirect_url'] = $title->getFullURL(); + } + } + } + } + if ( !$isValid ) $valid = true; + ///////////////////////////////////////////// + if ( $valid ) { + $return[] = $group; + } + } + return $return; + } + + protected function getGroupName() { + return 'mgwiki'; + } +} diff --git a/includes/SpecialMgwikiTest.php b/includes/SpecialMgwikiTest.php new file mode 100644 index 0000000..2f0a195 --- /dev/null +++ b/includes/SpecialMgwikiTest.php @@ -0,0 +1,188 @@ +setHeaders(); + $postData = $this->getRequest()->getPostValues(); + $select = ''; + $opts = [ + 'ORDER BY' => 'nom', + ]; + +$rep = DbF::select( 'groupe_type', ['id', 'nom', 'admin_level' ] ); + + +var_dump($rep); + +/* + $out = $this->getOutput(); + $out->enableOOUI(); + $out->addHTML( new \OOUI\FormLayout( [ + 'method' => 'POST', + 'action' => $_SERVER['PHP_SELF'], + 'items' => [ + new \OOUI\FieldsetLayout( [ + 'label' => 'Form layout', + 'items' => [ + new \OOUI\FieldLayout( + new \OOUI\DropdownInputWidget( + [ + 'name' => 'choix', + 'value' => 2, + 'options' => [ + [ 'data' => 1, 'label' => 'choix1' ], + [ 'data' => 2, 'label' => 'choix2' ], + [ 'data' => 3, 'label' => 'choix3' ], + [ 'data' => 4, 'label' => 'choix4' ], + ], + ] ), + [ + 'label' => 'Choisissez', + 'align' => 'top', + ] + ), + new \OOUI\FieldLayout( + new \OOUI\TextInputWidget( [ + 'name' => 'password', + 'type' => 'text', + 'value' => 'blablabla' + ] ), + [ + 'label' => 'Password', + 'align' => 'top', + ] + ), + new \OOUI\FieldLayout( + new \OOUI\CheckboxInputWidget( [ + 'name' => 'rememberme', + 'selected' => true, + ] ), + [ + 'label' => 'Remember me', + 'align' => 'inline', + ] + ), + new \OOUI\FieldLayout( + new \OOUI\ButtonInputWidget( [ + 'name' => 'login', + 'label' => 'Log in', + 'type' => 'submit', + 'flags' => [ 'primary', 'progressive' ], + 'icon' => 'check', + ] ), + [ + 'label' => null, + 'align' => 'top', + ] + ), + ] + ] ) + ] + ] ) ); + */ +/* + // formDescriptor Array to tell HTMLForm what to build + $formDescriptor = [ + 'user_id' => [ + 'type' => 'int', + 'label' => 'user_id', + 'required' => true, + //'filter-callback' => function ( $val, $array ) { return 'hahahaha'; }, + ], + 'nom' => [ + 'type' => 'text', + 'label' => 'nom', + 'required' => true, + //'filter-callback' => function ( $val, $array ) { return 'hahahaha'; }, + ], + 'prenom' => [ + 'type' => 'text', + 'label' => 'prenom', + 'required' => true, + //'filter-callback' => function ( $val, $array ) { return 'hahahaha'; }, + ], + ]; + + // Build the HTMLForm object + $htmlForm = HTMLForm::factory( 'vform', $formDescriptor, $this->getContext() ); + $htmlForm->setSubmitText( 'Soumettre' ); + $htmlForm->setSubmitCallback( [ $this, 'processInput' ] ); + $htmlForm->show(); // Display the form +*/ + + /* + if ( isset($postData['wpkey'] ) ){ + + ///////// TIMESTAMP & DB: + + $user = UserF::getUserFromId((int)$postData['wpkey']); + var_dump( wfTimestamp( TS_DB, $user->getRegistration() ) ); + var_dump( wfTimestamp(TS_MW) ); + + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + var_dump( $dbw->timestamp( ) ); // + + var_dump( 'MGW_DB_ROW_DROPED : ' . MGW_DB_DROPED ); + + ///////// MGWuser : + $r = MGWUser::newFromUserId( (int)$postData['wpkey'] ); + var_dump($r); */ + /* + $replace = new MGWReplaceText( [ + "target" => "interne TEST2", + "replace" => "anonyme", + "regex" => false, + "nsall" => true, + "summary" => "MGW replacetext test.", + "user" => "Webmaster" + ] ); + $status = $replace->execute(); + var_dump($status); + */ + } + + // Callback function + // OnSubmit Callback, here we do all the logic we want to do… + public static function processInput( $formData ) { + + global $wgUser; + $table = 'utilisateur'; + $select = [ 'user_id' => $formData['user_id'] ]; + $data = [ + 'nom' => $formData['nom'], + 'prenom' => $formData['prenom'] + ]; + + $updater_id = $wgUser->getId(); + + $dbUpdate = DbF::update_or_insert( $table, $select, $data, $updater_id ); + + return $dbUpdate->mess(); + } + + protected function getGroupName() { + return 'mgwiki'; + } +} diff --git a/includes/Utilities/GetMessage.php b/includes/Utilities/GetMessage.php new file mode 100644 index 0000000..aa06841 --- /dev/null +++ b/includes/Utilities/GetMessage.php @@ -0,0 +1,41 @@ + 0 ) { + foreach ( $args as $key => $arg ) { + $needle = '$' . ($key + 1); + $out = str_replace( $needle, $arg, $out ); + } + } + return $out; + } + + // le message n'existe pas + if ( $html ) { + $link = GetJsonPage::getLink('messages'); + return '<' . $mess . '>'; + } + else return '<' . $mess . '>'; + } +} diff --git a/includes/Utilities/HtmlFunctions.php b/includes/Utilities/HtmlFunctions.php new file mode 100644 index 0000000..6969bb0 --- /dev/null +++ b/includes/Utilities/HtmlFunctions.php @@ -0,0 +1,31 @@ +done() ) + ? '' + : '' ; + $backgroundColor = ( $status->done() ) ? '#d5fdf4' : '#fef6e7' ; + $borderColor = ( $status->done() ) ? '#14866d' : '#ffcc33' ; + $outcome = ( $status->done() ) ? 'done' : 'failed' ; + return ' +
    ' . $icon . '' . $status->mess() . '
    ' ; + } + + public static function onclickButton( $value, $href ) { + return ''; + } +} diff --git a/includes/Utilities/JsonToForm.php b/includes/Utilities/JsonToForm.php index 79240f0..7f63f66 100644 --- a/includes/Utilities/JsonToForm.php +++ b/includes/Utilities/JsonToForm.php @@ -4,6 +4,7 @@ use MediaWiki\Extension\MGWikiDev\Utilities\Captcha; use MediaWiki\Extension\MGWikiDev\Utilities\GetJsonPage; +use MediaWiki\Extension\MGWikiDev\Utilities\GetMessage as Msg; /** * Class to set forms directly from a .json wiki page. @@ -54,11 +55,6 @@ class JsonToForm */ private $postData; - /** - * @var array - */ - private $messages; - /** * @var array [ ['type' => 'html'/'wiki', 'value' => (string) ] ] */ @@ -67,9 +63,8 @@ class JsonToForm /** * @param string $jsonService: doit correspondre à une clé de $wgMGWikiJsonPages (LocalSettings.php) * @return array $this->output - * @param array $messages */ - public function __construct( $jsonService, &$postData, &$messages ) + public function __construct( $jsonService, &$postData ) { global $wgMGWikiJsonPages; @@ -86,8 +81,6 @@ public function __construct( $jsonService, &$postData, &$messages ) self::hydratePostData($postData); $this->Captcha = new Captcha(); - - $this->messages = $messages; } private function hydratePostData( &$postData ) @@ -280,7 +273,7 @@ private function makeCaptchaElement( $hide ) $captchaKey = $this->Captcha->getRandomKey(); $this->htmlOut( '
    ' . $captchaKey . '
    - ' . $this->getMsg( $this->formName . '-label-captcha' ) . ' + ' . Msg::get( $this->formName . '-label-captcha' ) . '
    ' ); @@ -316,7 +309,7 @@ public function sendEmail() $mailer->send( array($mail_to, $mail_from), //to $mail_to, //from - $this->getMsg( $this->formName . '-email-subject' ), //subject + Msg::get( $this->formName . '-email-subject' ), //subject $body, //body array( //options 'replyTo' => $mail_from, @@ -348,7 +341,7 @@ private function composeEmail() { $body = ' -

    ' . $this->getMsg( $this->formName . '-email-intro') . '

    +

    ' . Msg::get( $this->formName . '-email-intro') . '

    Votre message :

    ' ; @@ -377,7 +370,7 @@ private function composeEmail() $body .= '
    Date: ' . date('Y-m-d H:i:s') . '

    -

    ' . $this->getMsg( $this->formName . '-email-end' ) . '

    +

    ' . Msg::get( $this->formName . '-email-end' ) . '

    '; return $body; @@ -410,13 +403,4 @@ public function getHash() } return md5( $string ); } - - private function getMsg ( $mess ) { - if ( isset( $this->messages[ $mess ] ) ) { - return $this->messages[ $mess ]; - } - else { - return '<' . $mess . '>'; - } - } } diff --git a/includes/Utilities/MgwDataFunctions.php b/includes/Utilities/MgwDataFunctions.php new file mode 100644 index 0000000..592cebf --- /dev/null +++ b/includes/Utilities/MgwDataFunctions.php @@ -0,0 +1,335 @@ + 'valeur', 'page_id' => 5, etc. ] + * @param int $updater_id + * @return MGWStatus + */ + public static function update_or_insert( $table, $select, $data, $updater_id ) { + + $update = self::update( $table, $select, $data, $updater_id ); + + if ( $update->done() ) { + return $update; + } + + if ( $update->extra == MGW_DB_UNSET ) { + $data = array_merge( $data, $select ); + return self::insert( $table, $data, $updater_id ); + } + + else { + return $update; + } + } + + /** + * @param string $table // nom de la table sans préfixe ('utilisateur', 'groupe_membre', etc.) + * @param array $data // sous la forme: [ 'champs SANS PREFIXE' => 'valeur', 'page_id' => 5, etc. ] + * @param int $updater_id + * @return MGWStatus + */ + public static function insert( $table, $data, $updater_id ) { + + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + + $insert = []; + foreach ( $data as $field => $value ) { + $insert[ $table . '_' . $field ] = $value; + } + $insert[ $table . '_updater_id' ] = $updater_id; + $insert[ $table . '_update_time' ] = $dbw->timestamp( date("Y-m-d H:i:s") ); + + $dbw->insert( + 'mgw_' . $table, + $insert + ); + } catch (\Exception $e) { + return Status::newFailed( $e, MGW_DB_ERROR ); + } + return Status::newDone( 'Entrée ajoutée à la table mgw_' . $table, MGW_DB_INSERTED ); + } + + /** + * @param string $table // nom de la table sans préfixe ('utilisateur', 'groupe_membre', etc.) + * @param array $select // sous la forme: [ 'champs SANS PREFIXE' => 'valeur' ] + * @param array $data // sous la forme: [ 'champs SANS PREFIXE' => 'valeur', 'page_id' => 5, etc. ] + * @param int $updater_id + * @return MGWStatus + */ + public static function update( $table, $select, $data, $updater_id ) { + + global $wgMgwStringVars; + + $sel = array_keys( $select )[0]; + if ( in_array( $table . '_' . $sel, $wgMgwStringVars ) ) { + $select[$sel] = "'" . $select[$sel] . "'"; + } + + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + $update = []; + + // $data = [] -> màj update_time et updater_id seulement + if ( count( $data ) > 0 ) { + + // 1. on récupère les données actuelles + $res = $dbw->select( + 'mgw_' . $table, + [ + '*' + ], + $table . '_' . $sel .' = ' . $select[$sel] + ); + if ( $res->result->num_rows < 1 ) { + return Status::newFailed( + 'Aucune ligne ne correspond à la requête ' . $table . '_' . $sel . ' = ' . $select[$sel] . + ' dans la table mgw_' . $table, + MGW_DB_UNSET + ); + } + + // 2. on vérifie la nouveauté des valeurs proposées + $row = self::extractResData( $res ); + $row = $row[0]; + $same_data = true; + + foreach ( $data as $field => $value ) { + if ( $row[ $table . '_' . $field ] != $value ) { + $same_data = false; + break; + } + } + if ( $same_data ) { + return Status::newDone( + 'Table mgw_' . $table . ' : ' . $sel . ' = ' . $select[$sel] . ' déjà à jour.', + MGW_DB_UNCHANGED + ); + } + + // 3. on archive les données actuelles + $archive = self::archive( $table, $row, false, $updater_id ); + if ( ! $archive->done() ) { + return $archive; + } + + // 4. on met à jour la table + $update = []; + foreach ( $data as $field => $value ) { + $update[ $table . '_' . $field ] = $value; + } + } + $update[ $table . '_updater_id' ] = $updater_id; + $update[ $table . '_update_time' ] = $dbw->timestamp( date("Y-m-d H:i:s") ); + + foreach ( $update as $field => $value ) { + if ( is_string( $value ) ) { + $value = "'" . $value . "'"; + } + $array[] = $field . ' = ' . $value; + } + $array = implode( ',', $array ); + + $sql = 'UPDATE MGW_mgw_' . $table . ' SET ' . $array . + ' WHERE ' . $table . '_' . $sel . ' = ' . $select[$sel] . ';'; + + $dbw->query( $sql ); + + } catch (\Exception $e) { + return Status::newFailed( $e, MGW_DB_ERROR ); + } + return Status::newDone( 'La table mgw_' . $table . ' a été mise à jour.', MGW_DB_UPDATED ); + } + + /** + * @param string $table // nom de la table sans préfixe ('utilisateur', 'groupe_membre', etc.) + * @param array $select // sous la forme: [ 'champs SANS PREFIXE' => 'valeur' ] + * @param int $updater_id + * @return MGWStatus + */ + public static function delete( $table, $select, $updater_id ) { + + global $wgMgwStringVars; + + $sel = array_keys( $select )[0]; + if ( in_array( $table . '_' . $sel, $wgMgwStringVars ) ) { + $select[$sel] = "'" . $select[$sel] . "'"; + } + + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + $update = []; + + // 1. on récupère les données actuelles + $res = $dbw->select( + 'mgw_' . $table, + [ + '*' + ], + $table . '_' . $sel .' = ' . $select[$sel] + ); + if ( $res->result->num_rows < 1 ) { + return Status::newFailed( + 'Aucune ligne ne correspond à la requête ' . $table . '_' . $sel .' = ' . $select[$sel] . + ' dans la table mgw_' . $table, + MGW_DB_UNSET + ); + } + + // 2. on archive les données actuelles + $row = self::extractResData( $res ); + $row = $row[0]; + $archive = self::archive( $table, $row, true, $updater_id ); + if ( ! $archive->done() ) { + return $archive; + } + + // 4. on supprime la ligne de la table + $dbw->delete( 'mgw_' . $table, $table . '_' . $sel . ' = ' . $select[$sel] ); + + } catch (\Exception $e) { + return Status::newFailed( $e, MGW_DB_ERROR ); + } + return Status::newDone( 'La suppression a été effectuée dans la table mgw_' . $table, $row ); + } + + + /** + * @param string $table // nom de la table sans préfixe ('utilisateur', 'groupe_membre', etc.) + * @param array $rowData // résultat d'une ligne de la requête SELECT * FROM mgw_$table + * @param bool $droped // si l'archive conçerne une délétion + * @param int $updater_id // obligatoire si l'archive conçerne une délétion + * @return MGWStatus + */ + public static function archive( $table, $rowData, $droped = false, $updater_id ) { + + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + + if ( $droped && is_null( $updater_id ) ) { + throw new \Exception('Erreur: $updater_id = null alors que $droped = true', 1); + } + + if ( $droped ) { + $rowData[ $table . '_drop_updater_id' ] = $updater_id; + $rowData[ $table . '_drop_time' ] = $dbw->timestamp( date("Y-m-d H:i:s") ); + } + + $dbw->insert( + 'mgw_archive_' . $table, + $rowData + ); + } catch (\Exception $e) { + return Status::newFailed( $e, MGW_DB_ERROR ); + } + return Status::newDone( 'Table mgw_archive' . $table . 'mise à jour.', MGW_DB_INSERTED ); + } + + /** + * Extraire la liste des résultats d'un objet IResultWrapper + * Normaliser le format (string|int) + * @param IResultWrapper $res + * @return array + */ + public static function extractResData( &$res, $prefix = '' ) { + + if ( $res->result->num_rows == 0 ) { + return null; + } + + global $wgMgwStringVars; + $ret = []; + $i = 0; + while ( $i < $res->result->num_rows ) { + $row = $res->fetchRow(); + $row = array_filter( + $row, + function( $k ) { + return is_string( $k ); + }, + ARRAY_FILTER_USE_KEY + ); + # on rétablit string vs/int + foreach ( $row as $field => $value ) { + if ( !in_array( $field, $wgMgwStringVars ) ) { + $row[$field] = (int)$value; + } + } + + # on supprime le préfixe si demandé + if ( !empty( $prefix ) ) { + $prefix = str_replace( 'archive_', '', $prefix ); + foreach ( $row as $field => $value ) { + if ( preg_match( '/'.$prefix.'/', $field ) > 0 ){ + $newfield = str_replace( $prefix . '_', '', $field ); + $row[$newfield] = $value; + unset( $row[$field] ); + } + } + } + + $ret[] = $row; + $i++; + } + return $ret; + } + + /** + * Requêtes dans les tables mgw. + * !!! les noms sont donnés sans les préfixes. + * @param string $table ex.: 'utilisateurs' + * @param array $columns ex.: [ 'id', 'user_id', etc. ] + * @param string $select ex.: 'nom = "Foo"' + * @param array $opts ex.: [ 'ORDER BY' => 'nom DESC' ] + * + * @return array + */ + public static function select_clean( $table, $columns, $select = '', $opts = [] ) { + + $prefix = str_replace( 'archive_', '', $table ); + + // réimplémentation des préfixes + foreach ( $columns as $key => $value ) { + if ( $value != 'archive_id' ) { + if ( preg_match( '/archive_id/', $select ) < 1 ) { + $select = str_replace( $value, $prefix . '_' . $value, $select ); + } + foreach ( $opts as $kkey => $vvalue ) { + $opts[$kkey] = str_replace( $value, $prefix . '_' . $value, $vvalue ); + } + $columns[$key] = $prefix . '_' . $value; + } + } + $table_full = 'mgw_' . $table; + + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $res = $dbr->select( + $table_full, + $columns, + $select, + __METHOD__, + $opts + ); + + return self::extractResData( $res, $table ); + } +} diff --git a/includes/Utilities/PagesFunctions.php b/includes/Utilities/PagesFunctions.php index 54aa07c..1129006 100644 --- a/includes/Utilities/PagesFunctions.php +++ b/includes/Utilities/PagesFunctions.php @@ -8,7 +8,7 @@ use WikitextContent; /** - * Fonctions sur les pages + * Ensemble de fonctions statiques sur les pages */ class PagesFunctions { @@ -17,9 +17,9 @@ class PagesFunctions * @param int $namespace : constante de l'espace de nom de la page (ex.: 'NS_MAIN') * @param bool $check = false (return null if title does not exist) * - * @return mixed Title ou null + * @return Title|null */ - public function getTitleFromText ( $pagename, $namespace, $check = false ) { + public function getTitleFromText ( $pagename, $namespace = NS_MAIN, $check = false ) { $title = Title::newFromText( $pagename, $namespace ); if ( $check && $title->getArticleID() <= 0 ) { return null; @@ -30,7 +30,7 @@ public function getTitleFromText ( $pagename, $namespace, $check = false ) { * @param Title $title * @param bool $check = false (return null if page does not exist) * - * @return mixed WikiPage ou null + * @return WikiPage|null */ public function getPageFromTitle ( Title $title, $check = false ) { if ( $check ) { @@ -39,14 +39,23 @@ public function getPageFromTitle ( Title $title, $check = false ) { return WikiPage::factory( $title ); } + /** + * @param int $id + * + * @return WikiPage|null + */ + public function getPageFromId ( $id ) { + return WikiPage::newFromID( $id ); + } + /** * @param string $pagename : titre de la page * @param int $namespace : constante de l'espace de nom de la page (ex.: 'NS_MAIN') * @param bool $check = false (return null if title or page does not exist) * - * @return mixed WikiPage ou null + * @return WikiPage|null */ - public function getPageFromTitleText ( $pagename, $namespace, $check = false ) { + public function getPageFromTitleText ( $pagename, $namespace = NS_MAIN, $check = false ) { $title = self::getTitleFromText( $pagename, $namespace, $check ); if ( is_null( $title ) ) { return null; @@ -58,7 +67,7 @@ public function getPageFromTitleText ( $pagename, $namespace, $check = false ) { * @param string $pagename : titre de la page * @param int $namespace : constante de l'espace de nom de la page (ex.: 'NS_MAIN') * - * @return mixed string ou null + * @return string|null */ public function getPageContentFromTitleText ( $pagename, $namespace ) { $page = self::getPageFromTitleText( $pagename, $namespace, true ); @@ -73,13 +82,24 @@ public function getPageContentFromTitleText ( $pagename, $namespace ) { * renvoie le texte du titre de la page redirigée * * @param WikiPage $page : titre de la page - * @return mixed string ou false + * @param string $output 'title'|'string' + * @return Title|string|false */ - public function getPageRedirect ( $page ) { + public function getPageRedirect ( $page, $output = 'string' ) { $return = []; $content = $page->getContent()->getNativeData(); $screen = preg_match( '/^\#REDIRECTION \[\[(.*)\]\]/', $content, $matches ); - if ( $screen > 0 ) return $matches[1]; + if ( $screen > 0 ) { + switch ( $output ) { + case 'title': + return Title::newFromText( $matches[1] ); + break; + + default: + return $matches[1]; + break; + } + } return null; } @@ -90,25 +110,27 @@ public function getPageRedirect ( $page ) { * @param string $template : nom du modèle * @param array $fields : champs recherchés * - * @return mixed array( field => data, ... ) ou null + * @return array( "field" => data, ... )|null */ public function getPageTemplateInfos ( $page, $template, $fields ) { $return = []; $content = $page->getContent()->getNativeData(); - $content = str_replace( '}}','',$content ); + //$content = str_replace( '}}','',$content ); $content = explode('{{', $content ); foreach ( $content as $key => $string ) { - $screen = preg_match( '/^' . $template . '/', $string); + $screen = preg_match( '/^' . $template . '[\s\|]/', $string); if ( $screen > 0 ) { $data = explode( '|', $string ); foreach ( $fields as $kkey => $field ) { foreach ( $data as $kkkey => $dat ) { - $screen = preg_match('/^'.$field.'/', $dat, $matches ); - if ( $screen > 0 ) { - $dat = trim(str_replace( $field.'=', '', $dat )); - $return[$field] = $dat; - } + $screen = preg_match('/^'.$field.'[ ]*=(.+)[\s\|]/', $dat, $matches ); + if ( isset( $matches[1] ) ) { + $return[$field] = $matches[1]; + } } + if ( !isset($return[$field]) ) { + $return[$field] = null; + } } } } diff --git a/includes/Utilities/PhpFunctions.php b/includes/Utilities/PhpFunctions.php index b686828..bebee7f 100644 --- a/includes/Utilities/PhpFunctions.php +++ b/includes/Utilities/PhpFunctions.php @@ -22,6 +22,21 @@ public function recursiveArrayKey ( $needle, $array ) return null; } + /** + * recherche récursivement une paire 'clé' => 'valeur' + * @return bool + */ + public function recursiveArrayKeyValue ( $key, $value, $array ) + { + $recursive = self::recursiveIterator( $array ); + foreach ( $recursive as $kkey => $vvalue ) { + if ( $key === $kkey && $value === $vvalue ) { + return true; + } + } + return false; + } + /** * recherche récursivement une clé, fusionne le résultat si plusieurs occurences * @return mixed (valeur, array ou array_merge) @@ -88,4 +103,62 @@ public function array_doublons( $array ) { } return $r_valeur; } + + /** + * sets $var to false if null, empty or undefined + * @param mixed &$var + */ + public function false ( &$var ) { + if ( !isset( $var ) || is_null( $var ) || empty( $var ) ) { + $var = false; + } + } + + /** + * sets $var to null if empty, false or undefined + * @param mixed &$var + */ + public function null ( &$var ) { + if ( !isset( $var ) || empty( $var ) || !$var ) { + $var = null; + } + } + + /** + * sets $var to empty if null, undefined or false + * @param mixed &$var + */ + public function empty ( &$var ) { + if ( !isset( $var ) || is_null( $var ) || !$var ) { + $var = ''; + } + } + + /** + * sets $var to empty if null, undefined or false + * @param mixed &$var + */ + public function int ( &$var ) { + $int = (int)$var; + if ( 'test'.$int == 'test'.$var ) { + $var = $int; + } + else { + $var = null; + } + } + + /** + * @param mixed &$var + * @param bool $decode + * @return void + */ + public function html ( &$var, $decode = false ) { + if ( $decode ) { + $var = htmlspecialchars_decode( $var ); + } + else { + $var = htmlspecialchars( $var ); + } + } } diff --git a/includes/Utilities/UsersFunctions.php b/includes/Utilities/UsersFunctions.php index 5287409..570b38f 100644 --- a/includes/Utilities/UsersFunctions.php +++ b/includes/Utilities/UsersFunctions.php @@ -3,9 +3,11 @@ namespace MediaWiki\Extension\MGWikiDev\Utilities; use User; +use MediaWiki\MediaWikiServices; +use MediaWiki\Extension\MGWikiDev\Classes\MGWStatus as Status; /** - * Fonctions sur les pages + * Fonctions sur les utilisateurs */ class UsersFunctions { @@ -34,4 +36,401 @@ public function getUserFromNames ( $prenom, $nom = null, $check = false ) { public function getUserFromId ( $id ) { return User::newFromId ( $id ); } + + + /** + * Vérification de l'existence de l'utilisateur dans la base MediaWiki + * retourne le nom d'utilisateur s'il existe + * + * @param string|int $val user_name|user_id + * @param bool $case_sensitive + * @return string|bool user_name|false + */ + public function userExists( $val, $case_sensitive = false ) { + + if ( is_int( $val ) || preg_match( '/^[0-9]+$/', $val ) > 0 ) { + if ( !is_int( $val ) ) $val = (int)$val; + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $res = $dbr->select( 'user', [ 'user_name' ], 'user_id = ' . $val ); + if ( $res->numRows() > 0 ) { + $row = $res->fetchRow(); + return $row['user_name']; + } + return false; + } + + if ( $case_sensitive ) { + $user = User::newFromName ( $val ); + if ( $user->getId() > 0 ) return $val; + else return false; + } + + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $res = $dbr->select( 'user', [ 'user_name' ] ); + if ( $res->numRows() > 0 ) { + foreach ( $res as $row ) { + if ( strtolower( $val ) == strtolower( $row->user_name ) ) + return $row->user_name; + } + } + + return false; + } + + + /** + * Vérification de l'existence de l'utilisateur dans les tables mgw + * retourne 'Prénom NOM' s'il existe + * + * @param array|string|int $val nom,prenom|user_name|user_id + * @param bool $case_sensitive + * @return string|bool user_name|false + */ + public function mgwUserExists( $mode, $val, $case_sensitive = false ) { + + switch ( $mode ) { + case 'user_id': + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbr = $lb->getConnectionRef( DB_REPLICA ); + $res = $dbr->select( 'mgw_utilisateur', [ 'utilisateur_id' ], 'utilisateur_user_id = ' . $val ); + if ( $res->numRows() > 0 ) { + return true; + } + return false; + break; + + default : + return false; + break; + } + } + + + /** + * Vérification de l'existence du mail + * + * @param string $email + * @return string|bool $user_name|false + */ + public function emailExists( $email ) { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + $res = $dbw->select( 'user', ['user_name', 'user_email'] ); + foreach ( $res as $row ) { + if ( $row->user_email == $email ) { + return $row->user_name ; + } + } + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + // FONCTIONS A SUPPRIMER (-> MgwDataFonctions ) + // ... + + /** + * Cas d'usage: demande de suppression de compte. + * $fullDeletion = true implique un effaçement complet de l'utilisateur + * y compris dans les tables d'archive. + * + * @param int $user_id + * @param bool $fullDeletion + * @param int updater_id + * @return array [ 'done' => bool, 'message' => string ] + */ + public function deleteMGWUser( $user_id, $fullDeletion = false, $updater_id = null ) { + + if ( is_null($updater_id) ) { + global $wgUser; + $updater_id = $wgUser->getId(); + } + + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + $res = $dbw->select( + 'mgw_utilisateur', + [ + 'utilisateur_user_id' + ], + 'utilisateur_user_id = ' . $user_id + ); + + if ( sizeof( $res < 1 ) ) { + return [ + 'done' => false, + 'message' => 'Suppression impossible: l\'utilisateur ' . $user_id . + ' n\'existe pas dans la table utilisateurs de mgwiki.' + ]; + } + elseif ( sizeof( $res > 1 ) ) { + return [ + 'done' => false, + 'message' => 'Suppression impossible: : l\'utilisateur ' . $user_id . ' figure ' . + sizeof( $res ) . ' fois dans la table mgw_utilisateur.
    Veuillez contacter le responsable technique.' + ]; + } + + # suppression simple: on archive avec les champs + # utilisateur_drop_updater_id et utilisateur_drop_time renseignés + if ( !$fullDeletion ) { + $archive = self::archiveMGWUser( + $res[0]->utilisateur_id, + $res[0]->utilisateur_user_id, + $res[0]->utilisateur_nom, + $res[0]->utilisateur_prenom, + $res[0]->utilisateur_level, + $res[0]->utilisateur_updater_id, + $res[0]->utilisateur_update_time, + $updater_id, + date('Y-m-d H:i:s') + ); + } + # suppression complète: on vide les archives de toutes les lignes mentionnant l'utilisateur + else $archive = self::dropUserFromArchive( $res[0]->utilisateur_id, $user_id, $updater_id ); + + $drop = self::dropMGWUser( $user_id ); + + $username = $res[0]->utilisateur_prenom . ' ' . $res[0]->utilisateur_nom; + + if ( is_bool( $drop ) && is_bool( $archive ) && !$fullDeletion ) { + $done = $drop; + $message = $username . 'a été retiré des utilisateurs. Les archives n\'ont pas été effaçées.'; + } + elseif ( is_bool( $drop ) && is_bool( $archive ) && $fullDeletion ) { + $done = $drop; + $message = $username . 'a été retiré des utilisateurs ainsi que des archives.'; + } + else { + $done = false; + $message = 'Erreur à la suppression de ' . $username . ' :'; + if ( !is_bool( $archive ) ) $message .= '
    ' . $archive; + if ( !is_bool( $update ) ) $message .= '
    ' . $archive; + } + return [ 'done' => $done, 'message' => $message ]; + } + + /** + * @param int $id + * @param string $user_nom + * @param string $user_prenom + * @param int $updater_id + * @return MGWStatus + */ + public static function insertMGWUser( $user_id, $user_nom, $user_prenom, $updater_id ) { + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + $dbw->insert( + 'mgw_utilisateur', + [ + 'utilisateur_user_id' => $user_id, + 'utilisateur_nom' => $user_nom, + 'utilisateur_prenom' => $user_prenom, + 'utilisateur_updater_id' => $updater_id, + 'utilisateur_update_time' => $dbw->timestamp( date("Y-m-d H:i:s") ) + ] + ); + } catch (\Exception $e) { + return Status::newFailed( $e ); + } + return Status::newDone( $user_prenom . ' ' . $user_nom . ' a été ajouté à la table mgw_utilisateur.'); + } + + /** + * @param int $id + * @param array $data + * @return bool + */ + public static function updateMGWUser( $user_id, $user_nom, $user_prenom, $updater_id ) { + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + + $res = $dbw->select( + 'mgw_utilisateur', + [ + '*' + ], + 'utilisateur_user_id = ' . $user_id + ); + + if ( $res->result->num_rows < 1 ) { + return Status::newFailed( 'Utilisateur inconnu dans la table mgw_utilisateur'); + } + + $row = $res->fetchRow(); + + if ( $row['utilisateur_nom'] == $user_nom && $row['utilisateur_prenom'] == $user_prenom ) { + return Status::newFailed( 'Table mgw_utilisateur déjà à jour.'); + } + + $archive = self::archiveMGWUser( + $row['utilisateur_id'], + $row['utilisateur_user_id'], + $row['utilisateur_nom'], + $row['utilisateur_prenom'], + $row['utilisateur_level'], + $row['utilisateur_updater_id'], + $row['utilisateur_update_time'] + ); + + if ( ! $archive->done() ) { + return $archive; + } + + $dbw->update( + 'mgw_utilisateur', + [ + 'utilisateur_nom' => $user_nom, + 'utilisateur_prenom' => $user_prenom, + 'utilisateur_updater_id' => $updater_id, + 'utilisateur_update_time' => $dbw->timestamp( date("Y-m-d H:i:s") ) + ], + 'utilisateur_user_id = ' . $user_id + ); + } catch (\Exception $e) { + return Status::newFailed( $e ); + } + return Status::newDone( $user_prenom . ' ' . $user_nom . ' a été mis à jour dans la table mgw_utilisateur.'); + } + + + /** + * @param int $id + * @param string $user_nom + * @param string $user_prenom + * @param int $updater_id + * @return bool|string + */ + public static function archiveMGWUser( + $utilisateur_id, + $utilisateur_user_id, + $utilisateur_nom, + $utilisateur_prenom, + $utilisateur_level, + $utilisateur_updater_id, + $utilisateur_update_time, + $utilisateur_drop_updater_id = null + ) { + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + + if ( !is_null( $utilisateur_drop_updater_id ) ) { + $utilisateur_drop_time = $dbw->timestamp( date("Y-m-d H:i:s") ); + } else { + $utilisateur_drop_time = null; + } + $dbw->insert( + 'mgw_archive_utilisateurs', + [ + 'utilisateur_id' => $utilisateur_id, + 'utilisateur_user_id' => $utilisateur_user_id, + 'utilisateur_nom' => $utilisateur_nom, + 'utilisateur_prenom' => $utilisateur_prenom, + 'utilisateur_level' => $utilisateur_level, + 'utilisateur_updater_id' => $utilisateur_updater_id, + 'utilisateur_update_time' => $utilisateur_update_time, + 'utilisateur_drop_updater_id' => $utilisateur_drop_updater_id, + 'utilisateur_drop_time' => $utilisateur_drop_time + ] + ); + } catch (\Exception $e) { + return Status::newFailed( $e ); + } + return Status::newDone( 'Archive: ok' ); + } + + + /** + * @param int $id + * @return bool + */ + public static function dropMGWUser( $user_id, $updater_id ) { + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + + $res = $dbw->select( + 'mgw_utilisateur', + [ + '*' + ], + 'utilisateur_user_id = ' . $user_id + ); + + if ( $res->result->num_rows < 1 ) { + return Status::newFailed( 'Utilisateur inconnu dans la table mgw_utilisateur'); + } + + $row = $res->fetchRow(); + $archive = self::archiveMGWUser( + $row['utilisateur_id'], + $row['utilisateur_user_id'], + $row['utilisateur_nom'], + $row['utilisateur_prenom'], + $row['utilisateur_level'], + $row['utilisateur_updater_id'], + $row['utilisateur_update_time'], + $updater_id + ); + + if ( ! $archive->done() ) { + return $archive; + } + + $dbw->delete( + 'mgw_utilisateur', + 'utilisateur_user_id = ' . $user_id + ); + } catch (\Exception $e) { + return Status::newFailed( $e ); + } + return Status::newDone( 'l\'utilisateur' . $user_id . ' a été supprimé.' ); + } + + /** + * Suppression complète: on ne laisse que la trace de la suppression elle-même. + * + * @param int $utilisateur_id, + * @param int $utilisateur_user_id, + * @param int $utilisateur_updater_id + * @return bool + */ + private function dropUserFromArchive( $utilisateur_id, $utilisateur_user_id, $utilisateur_updater_id ) { + try { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + $dbw = $lb->getConnectionRef( DB_MASTER ); + $dbw->delete( + 'mgw_archive_utilisateurs', + 'utilisateur_user_id = ' . $utilisateur_user_id + ); + $dbw->insert( + 'mgw_archive_utilisateurs', + [ + 'utilisateur_id' => $utilisateur_id, + 'utilisateur_user_id' => $utilisateur_user_id, + 'utilisateur_nom' => 'compte supprimé', + 'utilisateur_prenom' => 'comte supprimé', + 'utilisateur_level' => null, + 'utilisateur_updater_id' => $utilisateur_updater_id, + 'utilisateur_update_time' => date('Y-m-d H:i:s'), + 'utilisateur_drop_updater_id' => $utilisateur_updater_id, + 'utilisateur_drop_time' => date('Y-m-d H:i:s') + ] + ); + } catch (\Exception $e) { + return $e; + } + return true; + } + + /** + * @param int $id + * @return int + */ + public function getLevel( $id ) { + } } diff --git a/maintenance/Tests/bacasable.php b/maintenance/Tests/bacasable.php new file mode 100644 index 0000000..ea5196e --- /dev/null +++ b/maintenance/Tests/bacasable.php @@ -0,0 +1,4 @@ + 0 ) { + $file = '../sql/addTable-' . $matches[2] . '.sql'; + file_put_contents( $file, $matches[1] ); + $tables = $matches[3]; + } + else break; +} + +$indexes = $main; +while ( $continue ) { + $test = preg_match( '/(CREATE INDEX \/\*i\*\/([a-z_]+) [^;]+;)(.*)/', $indexes, $matches ); + if ( $test > 0 ) { + $file = '../sql/addIndex-' . $matches[2] . '.sql'; + file_put_contents( $file, $matches[1] ); + $indexes = $matches[3]; + } + else break; +} + +echo 'OK +'; diff --git a/mgwiki.sql b/mgwiki.sql index c4cdd05..4af131c 100644 --- a/mgwiki.sql +++ b/mgwiki.sql @@ -1,144 +1,153 @@ -- Database schema for MGWiki (only for MYSQL database) +-- +-- en cas de modif: màj des fichiers sql/*.sql avec la commande 'php UpdateSQLfiles.php' +-- ! pas de commentaires dans les lignes de commande +-- booléens traités en int: 1|0 -CREATE TABLE /*_*/mgw_utilisateurs ( - utilisateur_id int not null auto_increment, +CREATE TABLE IF NOT EXISTS /*_*/mgw_utilisateur ( + utilisateur_id int unsigned not null auto_increment, utilisateur_user_id int not null, utilisateur_nom varchar(64) not null, utilisateur_prenom varchar(64) not null, - utilisateur_level smallint, - utilisateur_updater_user_id int not null, - utilisateur_update_time datetime not null, + utilisateur_level smallint default 0, + utilisateur_update_time varbinary(14) not null, + utilisateur_updater_id int not null, PRIMARY KEY (utilisateur_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_utilisateurs_lookup ON /*_*/mgw_utilisateurs (utilisateur_user_id, utilisateur_nom, utilisateur_prenom); -CREATE TABLE /*_*/mgw_archive_utilisateurs ( - utilisateur_id int not null, +CREATE INDEX /*i*/mgw_utilisateur_lookup ON /*_*/mgw_utilisateur (utilisateur_user_id, utilisateur_nom, utilisateur_prenom, utilisateur_level); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_utilisateur ( + archive_id int unsigned not null auto_increment, + utilisateur_id int unsigned not null, utilisateur_user_id int not null, utilisateur_nom varchar(64) not null, utilisateur_prenom varchar(64) not null, utilisateur_level smallint not null, - utilisateur_updater_user_id int not null, - utilisateur_update_time datetime not null, - PRIMARY KEY (utilisateur_id, utilisateur_update_time) + utilisateur_update_time varbinary(14) not null, + utilisateur_updater_id int not null, + utilisateur_drop_time varbinary(14) default null, + utilisateur_drop_updater_id int default null, + PRIMARY KEY (archive_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_archive_utilisateurs_lookup ON /*_*/mgw_archive_utilisateurs (utilisateur_user_id, utilisateur_nom, utilisateur_prenom); - --- Triggers -DELIMITER | -CREATE TRIGGER mgw_after_update_utilisateur AFTER UPDATE - ON /*_*/mgw_utilisateurs FOR EACH ROW - BEGIN - INSERT INTO mgw_archive_utilisateurs ( - utilisateur_id, - utilisateur_user_id, - utilisateur_nom, - utilisateur_prenom, - utilisateur_level, - utilisateur_updater_user_id, - utilisateur_update_time - ) - VALUES ( - OLD.utilisateur_id, - OLD.utilisateur_user_id, - OLD.utilisateur_nom, - OLD.utilisateur_prenom, - OLD.utilisateur_level, - OLD.utilisateur_updater_user_id, - OLD.utilisateur_update_time - ); - END | -CREATE TRIGGER mgw_after_delete_utilisateur AFTER DELETE - ON /*_*/mgw_utilisateurs FOR EACH ROW - BEGIN - INSERT INTO mgw_archive_utilisateurs ( - utilisateur_id, - utilisateur_user_id, - utilisateur_nom, - utilisateur_prenom, - utilisateur_level, - utilisateur_updater_user_id, - utilisateur_update_time - ) - VALUES ( - OLD.utilisateur_id, - OLD.utilisateur_user_id, - OLD.utilisateur_nom, - OLD.utilisateur_prenom, - OLD.utilisateur_level, - OLD.utilisateur_updater_user_id, - OLD.utilisateur_update_time - ); - END | -DELIMITER ; - -CREATE TABLE IF NOT EXISTS /*_*/mgw_institutions ( + +CREATE INDEX /*i*/mgw_archive_utilisateur_lookup ON /*_*/mgw_archive_utilisateur (utilisateur_user_id, utilisateur_nom, utilisateur_prenom, utilisateur_level); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_institution ( institution_id int unsigned auto_increment not null, institution_page_id int not null, institution_nom varchar(64) not null, - institution_update_utilisateur_id int not null, - institution_update_time datetime not null, + institution_update_time varbinary(14) not null, + institution_updater_id int not null, PRIMARY KEY (institution_id) ) /*$wgDBTableOptions*/; -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_institutions ( +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_institution ( + archive_id int unsigned not null auto_increment, institution_id int unsigned not null, institution_page_id int not null, institution_nom varchar(64) not null, - institution_update_utilisateur_id int not null, - institution_update_time datetime not null, - PRIMARY KEY (institution_id, institution_update_time) + institution_update_time varbinary(14) not null, + institution_updater_id int not null, + institution_drop_time varbinary(14) default null, + institution_drop_updater_id int default null, + PRIMARY KEY (archive_id) +) /*$wgDBTableOptions*/; + +-- table ne nécessitant pas d'archive +CREATE TABLE IF NOT EXISTS /*_*/mgw_institution_groupe ( + institution_groupe_id int unsigned not null auto_increment, + institution_groupe_type_id int unsigned not null, + institution_groupe_institution_id int unsigned not null, + PRIMARY KEY (institution_groupe_id) ) /*$wgDBTableOptions*/; -CREATE TABLE IF NOT EXISTS /*_*/mgw_groupes ( +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe ( groupe_id int unsigned auto_increment not null, - groupe_level smallint not null, - groupe_institution_id int not null, - groupe_page_id int not null, - groupe_nom varchar(64) not null, - groupe_start_time datetime, - groupe_end_time datetime, - groupe_actif boolean default true, - groupe_update_utilisateur_id int not null, - groupe_update_time datetime not null, + groupe_institution_id int unsigned not null, + groupe_page_id int, + groupe_type_id int unsigned, + groupe_start_time varbinary(14), + groupe_end_time varbinary(14), + groupe_actif smallint default 1, + groupe_updater_id int not null, + groupe_update_time varbinary(14) not null, PRIMARY KEY (groupe_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_groupes_lookup ON /*_*/mgw_groupes (groupe_page_id); - -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupes ( - groupe_id int not null, - groupe_level smallint not null, - groupe_institution_id int not null, - groupe_page_id int not null, - groupe_nom varchar(64) not null, - groupe_start_time datetime, - groupe_end_time datetime, - groupe_actif boolean not null, - groupe_update_time datetime not null, - groupe_update_utilisateur_id int not null, - PRIMARY KEY (groupe_id, groupe_update_time) + +CREATE INDEX /*i*/mgw_groupe_lookup ON /*_*/mgw_groupe (groupe_institution_id, groupe_page_id, groupe_type_id, groupe_actif); + + -- pas de cas d'usage drop pour les groupes +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe ( + archive_id int unsigned not null auto_increment, + groupe_id int unsigned not null, + groupe_institution_id int unsigned not null, + groupe_page_id int, + groupe_type_id int unsigned, + groupe_start_time varbinary(14), + groupe_end_time varbinary(14), + groupe_actif smallint, + groupe_update_time varbinary(14) not null, + groupe_updater_id int not null, + PRIMARY KEY (archive_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_archive_groupes_lookup ON /*_*/mgw_archive_groupes (groupe_page_id); -CREATE TABLE IF NOT EXISTS /*_*/mgw_groupes_membres ( +CREATE INDEX /*i*/mgw_archive_groupe_lookup ON /*_*/mgw_archive_groupe (groupe_institution_id, groupe_page_id, groupe_type_id, groupe_actif); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe_membre ( groupe_membre_id int unsigned auto_increment not null, groupe_membre_groupe_id int unsigned not null, - groupe_membre_utilisateur_id int unsigned not null, - groupe_membre_isadmin boolean default false, - groupe_membre_update_time datetime not null, - groupe_membre_update_utilisateur_id int not null, + groupe_membre_user_id int not null, + groupe_membre_isadmin smallint default 0, + groupe_membre_update_time varbinary(14) not null, + groupe_membre_updater_id int not null, PRIMARY KEY (groupe_membre_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_groupes_membres_lookup ON /*_*/mgw_groupes_membres (groupe_membre_groupe_id, groupe_membre_utilisateur_id); -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupes_membres ( +CREATE INDEX /*i*/mgw_groupe_membre_lookup ON /*_*/mgw_groupe_membre (groupe_membre_groupe_id, groupe_membre_user_id, groupe_membre_isadmin); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe_membre ( + archive_id int unsigned not null auto_increment, groupe_membre_id int unsigned not null, groupe_membre_groupe_id int unsigned not null, - groupe_membre_utilisateur_id int unsigned not null, - groupe_membre_isadmin boolean default false, - groupe_membre_update_time datetime not null, - groupe_membre_drop_time datetime, -- uniquement lors de la suppression - groupe_membre_update_utilisateur_id int not null, - PRIMARY KEY (groupe_membre_id, groupe_membre_update_time) + groupe_membre_user_id int not null, + groupe_membre_isadmin smallint, + groupe_membre_update_time varbinary(14) not null, + groupe_membre_updater_id int not null, + groupe_membre_drop_time varbinary(14) default null, + groupe_membre_drop_updater_id int default null, + PRIMARY KEY (archive_id) +) /*$wgDBTableOptions*/; + +CREATE INDEX /*i*/mgw_archive_groupe_membre_lookup ON /*_*/mgw_archive_groupe_membre (groupe_membre_groupe_id, groupe_membre_user_id, groupe_membre_isadmin); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe_type ( + groupe_type_id int unsigned auto_increment not null, + groupe_type_nom varchar(64) not null, + groupe_type_page_id int, + groupe_type_admin_level smallint not null, + groupe_type_user_level smallint not null, + groupe_type_default_duration int default null, + groupe_type_update_time varbinary(14) not null, + groupe_type_updater_id int not null, + PRIMARY KEY (groupe_type_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_archive_groupes_membres_lookup ON /*_*/mgw_archive_groupes_membres (groupe_membre_groupe_id, groupe_membre_utilisateur_id); + +CREATE INDEX /*i*/mgw_groupe_type_lookup ON /*_*/mgw_groupe_type (groupe_type_nom, groupe_type_page_id, groupe_type_user_level, groupe_type_admin_level); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe_type ( + archive_id int unsigned not null auto_increment, + groupe_type_id int unsigned not null, + groupe_type_nom varchar(64) not null, + groupe_type_page_id int, + groupe_type_admin_level smallint not null, + groupe_type_user_level smallint not null, + groupe_type_default_duration int, + groupe_type_update_time varbinary(14) not null, + groupe_type_updater_id int not null, + groupe_type_drop_time varbinary(14) default null, + groupe_type_drop_updater_id int default null, + PRIMARY KEY (archive_id) +) /*$wgDBTableOptions*/; + +CREATE INDEX /*i*/mgw_archive_groupe_type_lookup ON /*_*/mgw_archive_groupe_type (groupe_type_nom, groupe_type_page_id, groupe_type_user_level, groupe_type_admin_level); diff --git a/resources/ext.mgwiki-dev.css b/resources/ext.mgwiki-dev.css index 5f66389..b931ef4 100644 --- a/resources/ext.mgwiki-dev.css +++ b/resources/ext.mgwiki-dev.css @@ -62,3 +62,8 @@ visibility: visible; opacity: 1; } + +.mgw-delete-link{ + color:red; + cursor:pointer; +} diff --git a/resources/ext.mgwiki-dev.js b/resources/ext.mgwiki-dev.js index bba8a2f..b9166d6 100644 --- a/resources/ext.mgwiki-dev.js +++ b/resources/ext.mgwiki-dev.js @@ -64,12 +64,27 @@ } } + // faire disparare les messages de succès après 10 secondes + mw.mgwAlertMessage = function () { + setTimeout(() => { $('.mgw-alert[status=done]').hide(); }, 2000); + } + $( function () { + mw.mgwAlertMessage(); mw.mgwBorderColor(); mw.mgwImgTooltip(); mw.mgwLink(); $('.mgw-tooltiptext').css('display',''); $("#mgw-toggle-createUserSubPage-icon").html(' ▼ '); $("#mgw-toggle-createUserSubPage").attr("onclick","mw.mgwToggleCreateUserSubPage()"); + $("#wpName1, #wpPassword1").attr("placeholder",""); + $( ".mgw-delete-link" ).click( function() { + let link = $(this).attr('href'); + let elmt = $(this).attr('elmt'); + if ( confirm( 'Vous êtes sur le point de supprimer: ' + elmt + + '\nConfirmez-vous ?' ) ) { + document.location.href= link; + } + }); }); }( mediaWiki, jQuery ) ); diff --git a/resources/ext.mgwiki-specialadmin.css b/resources/ext.mgwiki-specialadmin.css new file mode 100644 index 0000000..8835d12 --- /dev/null +++ b/resources/ext.mgwiki-specialadmin.css @@ -0,0 +1,34 @@ +.mgw-radio{ + display:block; + margin:auto; +} + +.mgw-action-button{ + margin-left:50px; + margin-bottom:20px; +} + +.mgw-show-array{ + border: 1px solid black; + height:500px; + overflow: auto; +} + +.mgw-row-table{ + border-radius:10px; + margin:15px; + width:95%; + line-height:1.3; +} + +.mgw-active{ + background:#fadfaa; +} + +.mgw-inactive{ + background:#f6f6f6; +} + +.mgw-deleted{ + background:#ffcece; +} diff --git a/resources/ext.mgwiki-specialadmin.js b/resources/ext.mgwiki-specialadmin.js new file mode 100644 index 0000000..390092e --- /dev/null +++ b/resources/ext.mgwiki-specialadmin.js @@ -0,0 +1,95 @@ +( function ( mw, $ ) { + + mw.mgwFiltresShow = function() { + $('.mgw-view').attr('checked', true ); + } + + mw.mgwFiltresHide = function() { + $('.mgw-hide').attr('checked', true ); + } + + mw.mgwAddAll = function() { + let inputs = document.getElementById('mgw-check-add-all'); + if ( inputs.checked ) { + $('.mgw-check-add').each( function(){ + let id = $(this).val(); + if ( ! $('.mgw-check-del[value='+id+']').is(':checked') ) { + $(this).attr('checked', true ); + } + }); + } + if ( ! inputs.checked ) { + $('.mgw-check-add').attr('checked', false ); + } + } + + mw.mgwDelAll = function() { + let inputs = document.getElementById('mgw-check-del-all'); + if ( inputs.checked ) { + $('.mgw-check-del').each( function(){ + let id = $(this).val(); + if ( ! $('.mgw-check-add[value='+id+']').is(':checked') ) { + $(this).attr('checked', true ); + } + }); + } + if ( ! inputs.checked ) { + $('.mgw-check-del').attr('checked', false ); + } + } + + mw.mgwCheckAddConcat = function(){ + var arr = []; + $('.mgw-check-add:checked').each( function(){ arr.push( $(this).val() ); }); + $('#mgw-hidden-field-add').val( arr.join(',') ); + } + + mw.mgwCheckDelConcat = function(){ + var arr = []; + $('.mgw-check-del:checked').each( function(){ arr.push( $(this).val() ); }); + $('#mgw-hidden-field-del').val( arr.join(',') ); + } + + $( function () { + + // valider le formulaire en définissant l'action + $('.mgw-action-button').click( function(){ + let action = $(this).attr('action'); + if ( $(this).attr('checkadd') == 'true' ) { + mw.mgwCheckAddConcat(); + } + if ( $(this).attr('checkdel') == 'true' ) { + mw.mgwCheckDelConcat(); + } + let hidden = JSON.parse( $(this).attr('hidden') ); + Object.entries( hidden ).forEach(entry => { + const [ field, value ] = entry; + $('input[name='+field+']').val( value ); + }); + if ( $(this).attr('control') != '' ) { + $(this).attr('control'); + } + $('input[name=action]').val( action ) + }); + + // si check-add décocher check-del + $('.mgw-check-add').click( function(){ + let id = $(this).val(); + if( $(this).is(':checked') ){ + if ( $('.mgw-check-del[value='+id+']').is(':checked') ) { + $('.mgw-check-del[value='+id+']').removeAttr('checked'); + } + } + }); + + $('.mgw-check-del').click( function(){ + let id = $(this).val(); + if ( $(this).is(':checked') ){ + if ( $('.mgw-check-add[value='+id+']').is(':checked') ){ + $('.mgw-check-add[value='+id+']').removeAttr('checked'); + } + } + }); + }); + +}( mediaWiki, jQuery ) ); diff --git a/resources/specialadmingrouptypes.css b/resources/specialadmingrouptypes.css new file mode 100644 index 0000000..1649497 --- /dev/null +++ b/resources/specialadmingrouptypes.css @@ -0,0 +1,39 @@ +.mgw-display-group-types{ + border: 1px solid black; + height:400px; + overflow: auto; +} + +.mgw-admin-group-types{ + border-radius:10px; + margin:15px; + width:95%; + line-height:1.3; + background:#f6f6f6; +} + + +.mgw-admin-group-types.mgw-actif{ + background:#fff8e1; +} + +.mgw-level{ + width:150px; + padding-left:10px; +} + +.mgw-title{ + padding-left:10px; +} + +.mgw-edit-button{ + text-align:right; + padding-right:10px; +} + +.mgw-maj{ + font-size:x-small; + font-style:italic; + text-align:right; + padding-right:10px; +} diff --git a/resources/specialadmingrouptypes.js b/resources/specialadmingrouptypes.js new file mode 100644 index 0000000..27b9abd --- /dev/null +++ b/resources/specialadmingrouptypes.js @@ -0,0 +1,3 @@ +( function ( mw, $ ) { + +}( mediaWiki, jQuery ) ); diff --git a/resources/specialcheckaccounts.css b/resources/specialcheckaccounts.css index acbbdec..0253a0e 100644 --- a/resources/specialcheckaccounts.css +++ b/resources/specialcheckaccounts.css @@ -14,21 +14,50 @@ } .mgw-specialcheckaccount-user{ - background:#f6f6f6; border-radius:10px; margin:15px; width:85%; line-height:1.3; } -.mgw-specialcheckaccount-user td:nth-child(1){ - width:30px; +.mgw-specialcheckaccount-mgw-out{ + background:#f6f6f6; +} + +.mgw-specialcheckaccount-mgw-in{ + background:#fadfaa; +} + +.mgw-td-first{ + width:20px; + font-size:small; +} + +.mgw-td-label{ + width:80px; +} + +.mgw-td-button{ + width:80px; + text-align:center; + padding-top:10px; + padding-bottom:10px; } -.mgw-specialcheckaccount-user td:nth-child(2){ - width:100px; + +.mgw-td-legend{ + font-size:small; + font-style:italic; + padding-left:20px; } -.mgw-specialcheckaccount-user td:nth-child(3){ + +.mgw-td-1stlabel-top{ + width:250px; +} + +.mgw-td-page{ + width:250px; + margin-left:20px; } -.mgw-specialcheckaccount-user td:nth-child(4){ - width:30px; +.mgw-td-last{ + width:20px; } diff --git a/resources/specialcheckaccounts.js b/resources/specialcheckaccounts.js index 27b8b7f..4e00c73 100644 --- a/resources/specialcheckaccounts.js +++ b/resources/specialcheckaccounts.js @@ -1,59 +1,82 @@ ( function ( mw, $ ) { - mw.mgwFiltresShow = function() { - $('.mgw-view').attr('checked', true ); + mw.mgwShowUsers = function() { + $('#mgw-select-action').val('show'); + $('#mgw-select-action').click(); } - mw.mgwFiltresHide = function() { - $('.mgw-hide').attr('checked', true ); + mw.mgwEmailduplicates = function() { + $('#mgw-select-action').val('emailduplicates'); + $('#mgw-select-action').click(); } - mw.mgwAddAll = function() { - let inputs = document.getElementById('mgw-users-addall'); - if ( inputs.checked ) { - $('.mgw-user-add').each( function(){ - let id = $(this).val(); - if ( ! $('.mgw-user-delete[value='+id+']').is(':checked') ) { - $(this).attr('checked', true ); - } - }); - } - if ( ! inputs.checked ) { - $('.mgw-user-add').attr('checked', false ); - } + mw.mgwSelectUsers = function(){ + mw.mgwChecksConcat(); + $('#mgw-select-action').val('select'); + $('#mgw-select-action').click(); } - mw.mgwDeleteAll = function() { - let inputs = document.getElementById('mgw-users-deleteall'); - if ( inputs.checked ) { - $('.mgw-user-delete').each( function(){ - let id = $(this).val(); - if ( ! $('.mgw-user-add[value='+id+']').is(':checked') ) { - $(this).attr('checked', true ); - } - }); + mw.mgwSanitize = function(){ + mw.mgwChecksConcat(); + $('#mgw-select-action').val('sanitize'); + $('#mgw-select-action').click(); + } + + mw.mgwEmpty = function(){ + mw.mgwChecksConcat(); + let del = ''; + let m = 0; + let add = ''; + let t = 0; + $('.mgw-user-delete:checked').each( function(){ + m += 1; + let id = $(this).val(); + del += $('.mgw-username[userid='+id+']').text() + ', '; + }); + if ( m < 1 ) { + alert( 'Vous devez sélectionner au moins un utilisateur à vider (cases "delete").'); + return; } - if ( ! inputs.checked ) { - $('.mgw-user-delete').attr('checked', false ); + $('.mgw-user-add:checked').each( function(){ + t += 1; + let id = $(this).val(); + add += $('.mgw-username[userid='+id+']').text() + ' '; + }); + if ( t > 0 ) { + alert( 'Vous ne devez pas sélectionner d\'utilisateur dans les cases "add".'); + return; + } + let message = 'ATTENTION vous êtes sur le point de supprimer les variable \ + user_email, user_groups et user_real_name aux utilisateurs suivants :\n\n' + del; + if ( confirm( message ) ) { + $('#mgw-select-action').val('empty'); + $('#mgw-select-action').click(); } } - mw.mgwSelectUsers = function(){ + mw.mgwHarmonize = function(){ mw.mgwChecksConcat(); - $('#mgw-action-select').click(); + $('#mgw-select-action').val('harmonize'); + $('#mgw-select-action').click(); } - mw.mgwSanitize = function(){ - mw.mgwChecksConcat(); - $('#mgw-action-sanitize').click(); + mw.mgwHarmonizeReplace = function(){ + if ( $('#mgw-action-harmonize-replace:checked') ) { + $('#mgw-select-harmonize-replace').val('oui'); + } else { + $('#mgw-select-harmonize-replace').val('non'); + } } - mw.mgwRename = function(){ - mw.mgwChecksConcat(); - $('#mgw-action-rename').click(); + mw.mgwDeleteReplace = function(){ + if ( $('#mgw-action-delete-replace:checked') ) { + $('#mgw-select-delete-replace').val('oui'); + } else { + $('#mgw-select-delete-replace').val('non'); + } } - mw.mgwUserMerge = function( opt ){ + mw.mgwDelete = function( ){ mw.mgwChecksConcat(); let merged = ''; let m = 0; @@ -65,7 +88,7 @@ merged += $('.mgw-username[userid='+id+']').text() + ', '; }); if ( m < 1 ) { - alert( 'Vous devez sélectionner au moins un utilisateur à '+opt+' (cases "out").'); + alert( 'Vous devez sélectionner au moins un utilisateur à supprimer (cases "out").'); return; } $('.mgw-user-add:checked').each( function(){ @@ -74,36 +97,62 @@ targeted += $('.mgw-username[userid='+id+']').text() + ' '; }); if ( t != 1 ) { - alert( 'Vous devez sélectionner un (et un seul) utilisateur à '+opt+' (cases "in").'); + alert( 'Vous devez sélectionner un (et un seul) utilisateur à supprimer (cases "in").'); return; } - let message = 'ATTENTION vous êtes sur le point de '+ opt +' :\n\n' + merged + let message = 'ATTENTION vous êtes sur le point de supprimer :\n\n' + merged + '\n\n vers : \n\n' + targeted + '\n\nCette opération est irréversible.'; if ( confirm( message ) ) { - if ( opt == 'supprimer') { - $('#mgw-action-merge').val('delete'); - } - $('#mgw-action-merge').click(); + $('#mgw-select-action').val('delete'); + $('#mgw-select-action').click(); } } - mw.mgwMerge = function(){ - mw.mgwUserMerge('fusionner') + mw.mgwDBupdate = function(){ + mw.mgwChecksConcat(); + $('#mgw-select-action').val('db_update'); + $('#mgw-select-action').click(); } - mw.mgwDelete = function(){ - mw.mgwUserMerge('supprimer') + mw.mgwFiltresShow = function() { + $('.mgw-view').attr('checked', true ); } - mw.mgwPopulate = function(){ - mw.mgwChecksConcat(); - $('#mgw-action-populate').click(); + mw.mgwFiltresHide = function() { + $('.mgw-hide').attr('checked', true ); + } + + mw.mgwAddAll = function() { + let inputs = document.getElementById('mgw-users-addall'); + if ( inputs.checked ) { + $('.mgw-user-add').each( function(){ + let id = $(this).val(); + if ( ! $('.mgw-user-delete[value='+id+']').is(':checked') ) { + $(this).attr('checked', true ); + } + }); + } + if ( ! inputs.checked ) { + $('.mgw-user-add').attr('checked', false ); + } } - mw.mgwValidUsers = function(){ - $('#mgw-action-validusers').click(); + mw.mgwDeleteAll = function() { + let inputs = document.getElementById('mgw-users-deleteall'); + if ( inputs.checked ) { + $('.mgw-user-delete').each( function(){ + let id = $(this).val(); + if ( ! $('.mgw-user-add[value='+id+']').is(':checked') ) { + $(this).attr('checked', true ); + } + }); + } + if ( ! inputs.checked ) { + $('.mgw-user-delete').attr('checked', false ); + } } + // cumuler toutes les id cochée en 1 valeur "multiple" puis submit mw.mgwChecksConcat = function(){ var arr = []; diff --git a/resources/specialcheckgroups.css b/resources/specialcheckgroups.css new file mode 100644 index 0000000..3792b00 --- /dev/null +++ b/resources/specialcheckgroups.css @@ -0,0 +1,34 @@ +.mgw-radio{ + display:block; + margin:auto; +} + +.mgw-select-submit{ + margin-left:50px; + margin-bottom:20px; +} +.mgw-displaygroups{ + border: 1px solid black; + height:600px; + overflow: auto; +} + +.mgw-specialcheckgroups-group{ + background:#f6f6f6; + border-radius:10px; + margin:15px; + width:85%; + line-height:1.3; +} + +.mgw-specialcheckgroups-group td:nth-child(1){ + width:30px; +} +.mgw-specialcheckgroups-group td:nth-child(2){ + width:100px; +} +.mgw-specialcheckgroups-group td:nth-child(3){ +} +.mgw-specialcheckgroups-group td:nth-child(4){ + width:30px; +} diff --git a/resources/specialcheckgroups.js b/resources/specialcheckgroups.js new file mode 100644 index 0000000..6009f7d --- /dev/null +++ b/resources/specialcheckgroups.js @@ -0,0 +1,86 @@ +( function ( mw, $ ) { + + mw.mgwFiltresShow = function() { + $('.mgw-view').attr('checked', true ); + } + + mw.mgwFiltresHide = function() { + $('.mgw-hide').attr('checked', true ); + } + + mw.mgwAddAll = function() { + let inputs = document.getElementById('mgw-users-addall'); + if ( inputs.checked ) { + $('.mgw-group-add').each( function(){ + let id = $(this).val(); + if ( ! $('.mgw-group-delete[value='+id+']').is(':checked') ) { + $(this).attr('checked', true ); + } + }); + } + if ( ! inputs.checked ) { + $('.mgw-group-add').attr('checked', false ); + } + } + + mw.mgwDeleteAll = function() { + let inputs = document.getElementById('mgw-groups-deleteall'); + if ( inputs.checked ) { + $('.mgw-group-delete').each( function(){ + let id = $(this).val(); + if ( ! $('.mgw-group-add[value='+id+']').is(':checked') ) { + $(this).attr('checked', true ); + } + }); + } + if ( ! inputs.checked ) { + $('.mgw-group-delete').attr('checked', false ); + } + } + + mw.mgwSelectGroups = function(){ + mw.mgwChecksConcat(); + $('#mgw-action-select').click(); + } + + mw.mgwPopulate = function(){ + mw.mgwChecksConcat(); + $('#mgw-action-populate').click(); + } + + mw.mgwValidGroups = function(){ + $('#mgw-action-validgroups').click(); + } + +// cumuler toutes les id cochée en 1 valeur "multiple" puis submit + mw.mgwChecksConcat = function(){ + var arr = []; + $('.mgw-group-add:checked').each( function(){ arr.push( $(this).val() ); }); + $('#mgw-select-addgroups').val( arr.join(',') ); + arr=[]; + $('.mgw-group-delete:checked').each( function(){ arr.push( $(this).val() ); }); + $('#mgw-select-deletegroups').val( arr.join(',') ); + } + + $( function () { + + $('.mgw-group-add').click( function(){ + let id = $(this).val(); + if( $(this).is(':checked') ){ + if ( $('.mgw-group-delete[value='+id+']').is(':checked') ) { + $('.mgw-group-delete[value='+id+']').removeAttr('checked'); + } + } + }); + + $('.mgw-group-delete').click( function(){ + let id = $(this).val(); + if ( $(this).is(':checked') ){ + if ( $('.mgw-group-add[value='+id+']').is(':checked') ){ + $('.mgw-group-add[value='+id+']').removeAttr('checked'); + } + } + }); + }); + +}( mediaWiki, jQuery ) ); diff --git a/sql/addIndex-archive_groupe_lookup.sql b/sql/addIndex-archive_groupe_lookup.sql deleted file mode 100644 index 86c9483..0000000 --- a/sql/addIndex-archive_groupe_lookup.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX /*i*/mgw_archive_groupes_lookup ON /*_*/mgw_archive_groupes (groupe_page_id); diff --git a/sql/addIndex-archive_groupes_membres_lookup.sql b/sql/addIndex-archive_groupes_membres_lookup.sql deleted file mode 100644 index 519865f..0000000 --- a/sql/addIndex-archive_groupes_membres_lookup.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX /*i*/mgw_archive_groupes_membres_lookup ON /*_*/mgw_archive_groupes_membres (groupe_membre_groupe_id, groupe_membre_utilisateur_id); diff --git a/sql/addIndex-archive_utilisateurs_lookup.sql b/sql/addIndex-archive_utilisateurs_lookup.sql deleted file mode 100644 index 0053ed7..0000000 --- a/sql/addIndex-archive_utilisateurs_lookup.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX /*i*/mgw_archive_utilisateurs_lookup ON /*_*/mgw_archive_utilisateurs (utilisateur_nom, utilisateur_prenom); diff --git a/sql/addIndex-groupes_lookup.sql b/sql/addIndex-groupes_lookup.sql deleted file mode 100644 index 34a3228..0000000 --- a/sql/addIndex-groupes_lookup.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX /*i*/mgw_groupes_lookup ON /*_*/mgw_groupes (groupe_page_id); diff --git a/sql/addIndex-groupes_membres_lookup.sql b/sql/addIndex-groupes_membres_lookup.sql deleted file mode 100644 index 381380a..0000000 --- a/sql/addIndex-groupes_membres_lookup.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX /*i*/mgw_groupes_membres_lookup ON /*_*/mgw_groupes_membres (groupe_membre_groupe_id, groupe_membre_utilisateur_id); diff --git a/sql/addIndex-mgw_archive_groupe_lookup.sql b/sql/addIndex-mgw_archive_groupe_lookup.sql new file mode 100644 index 0000000..3dc07c2 --- /dev/null +++ b/sql/addIndex-mgw_archive_groupe_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_archive_groupe_lookup ON /*_*/mgw_archive_groupe (groupe_institution_id, groupe_page_id, groupe_type_id, groupe_actif); \ No newline at end of file diff --git a/sql/addIndex-mgw_archive_groupe_membre_lookup.sql b/sql/addIndex-mgw_archive_groupe_membre_lookup.sql new file mode 100644 index 0000000..afabbfd --- /dev/null +++ b/sql/addIndex-mgw_archive_groupe_membre_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_archive_groupe_membre_lookup ON /*_*/mgw_archive_groupe_membre (groupe_membre_groupe_id, groupe_membre_user_id, groupe_membre_isadmin); \ No newline at end of file diff --git a/sql/addIndex-mgw_archive_groupe_type_lookup.sql b/sql/addIndex-mgw_archive_groupe_type_lookup.sql new file mode 100644 index 0000000..24d8a40 --- /dev/null +++ b/sql/addIndex-mgw_archive_groupe_type_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_archive_groupe_type_lookup ON /*_*/mgw_archive_groupe_type (groupe_type_nom, groupe_type_page_id, groupe_type_user_level, groupe_type_admin_level); \ No newline at end of file diff --git a/sql/addIndex-mgw_archive_utilisateur_lookup.sql b/sql/addIndex-mgw_archive_utilisateur_lookup.sql new file mode 100644 index 0000000..addf836 --- /dev/null +++ b/sql/addIndex-mgw_archive_utilisateur_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_archive_utilisateur_lookup ON /*_*/mgw_archive_utilisateur (utilisateur_user_id, utilisateur_nom, utilisateur_prenom, utilisateur_level); \ No newline at end of file diff --git a/sql/addIndex-mgw_groupe_lookup.sql b/sql/addIndex-mgw_groupe_lookup.sql new file mode 100644 index 0000000..54f7881 --- /dev/null +++ b/sql/addIndex-mgw_groupe_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_groupe_lookup ON /*_*/mgw_groupe (groupe_institution_id, groupe_page_id, groupe_type_id, groupe_actif); \ No newline at end of file diff --git a/sql/addIndex-mgw_groupe_membre_lookup.sql b/sql/addIndex-mgw_groupe_membre_lookup.sql new file mode 100644 index 0000000..83f7b88 --- /dev/null +++ b/sql/addIndex-mgw_groupe_membre_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_groupe_membre_lookup ON /*_*/mgw_groupe_membre (groupe_membre_groupe_id, groupe_membre_user_id, groupe_membre_isadmin); \ No newline at end of file diff --git a/sql/addIndex-mgw_groupe_type_lookup.sql b/sql/addIndex-mgw_groupe_type_lookup.sql new file mode 100644 index 0000000..ac22933 --- /dev/null +++ b/sql/addIndex-mgw_groupe_type_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_groupe_type_lookup ON /*_*/mgw_groupe_type (groupe_type_nom, groupe_type_page_id, groupe_type_user_level, groupe_type_admin_level); \ No newline at end of file diff --git a/sql/addIndex-mgw_utilisateur_lookup.sql b/sql/addIndex-mgw_utilisateur_lookup.sql new file mode 100644 index 0000000..5967c08 --- /dev/null +++ b/sql/addIndex-mgw_utilisateur_lookup.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/mgw_utilisateur_lookup ON /*_*/mgw_utilisateur (utilisateur_user_id, utilisateur_nom, utilisateur_prenom, utilisateur_level); \ No newline at end of file diff --git a/sql/addIndex-utilisateurs_lookup.sql b/sql/addIndex-utilisateurs_lookup.sql deleted file mode 100644 index cc7fefd..0000000 --- a/sql/addIndex-utilisateurs_lookup.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX /*i*/mgw_utilisateurs_lookup ON /*_*/mgw_utilisateurs (utilisateur_nom, utilisateur_prenom); diff --git a/sql/addTable-archive_groupes.sql b/sql/addTable-archive_groupes.sql deleted file mode 100644 index faab478..0000000 --- a/sql/addTable-archive_groupes.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupes ( - groupe_id int not null, - groupe_level smallint not null, - groupe_institution_id int not null, - groupe_page_id int not null, - groupe_nom varchar(64) not null, - groupe_start_time varbinary(14), - groupe_end_time varbinary(14), - groupe_actif boolean not null, - groupe_update_time varbinary(14) not null, - groupe_update_utilisateur_id int not null, - PRIMARY KEY (groupe_id, groupe_update_time) -) /*$wgDBTableOptions*/; diff --git a/sql/addTable-archive_groupes_membres.sql b/sql/addTable-archive_groupes_membres.sql deleted file mode 100644 index 9067406..0000000 --- a/sql/addTable-archive_groupes_membres.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupes_membres ( - groupe_membre_id int unsigned not null, - groupe_membre_groupe_id int unsigned not null, - groupe_membre_utilisateur_id int unsigned not null, - groupe_membre_isadmin boolean default false, - groupe_membre_update_time varbinary(14) not null, - groupe_membre_drop_time varbinary(14), -- uniquement lors de la suppression - groupe_membre_update_utilisateur_id int not null, - PRIMARY KEY (groupe_membre_id, groupe_membre_update_time) -) /*$wgDBTableOptions*/; diff --git a/sql/addTable-archive_institutions.sql b/sql/addTable-archive_institutions.sql deleted file mode 100644 index cc5cfa1..0000000 --- a/sql/addTable-archive_institutions.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_institutions ( - institution_id int unsigned not null, - institution_page_id int not null, - institution_nom varchar(64) not null, - institution_update_utilisateur_id int not null, - institution_update_time varbinary(14) not null, - PRIMARY KEY (institution_id, institution_update_time) -) /*$wgDBTableOptions*/; diff --git a/sql/addTable-archive_utilisateurs.sql b/sql/addTable-archive_utilisateurs.sql deleted file mode 100644 index dbcb565..0000000 --- a/sql/addTable-archive_utilisateurs.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE /*_*/mgw_archive_utilisateurs ( - utilisateur_id int not null, - utilisateur_nom varchar(64) not null, - utilisateur_prenom varchar(64) not null, - utilisateur_level smallint not null, - utilisateur_update_utilisateur_id int not null, - utilisateur_update_time varbinary(14) not null, - PRIMARY KEY (utilisateur_id, utilisateur_update_time) -) /*$wgDBTableOptions*/; diff --git a/sql/addTable-groupes.sql b/sql/addTable-groupes.sql deleted file mode 100644 index b69adc8..0000000 --- a/sql/addTable-groupes.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS /*_*/mgw_groupes ( - groupe_id int unsigned auto_increment not null, - groupe_level smallint not null, - groupe_institution_id int not null, - groupe_page_id int not null, - groupe_nom varchar(64) not null, - groupe_start_time varbinary(14), - groupe_end_time varbinary(14), - groupe_actif boolean default true, - groupe_update_utilisateur_id int not null, - groupe_update_time varbinary(14) not null, - PRIMARY KEY (groupe_id) -) /*$wgDBTableOptions*/; diff --git a/sql/addTable-groupes_membres.sql b/sql/addTable-groupes_membres.sql deleted file mode 100644 index 51c4cc1..0000000 --- a/sql/addTable-groupes_membres.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE IF NOT EXISTS /*_*/mgw_groupes_membres ( - groupe_membre_id int unsigned auto_increment not null, - groupe_membre_groupe_id int unsigned not null, - groupe_membre_utilisateur_id int unsigned not null, - groupe_membre_isadmin boolean default false, - groupe_membre_update_time varbinary(14) not null, - groupe_membre_update_utilisateur_id int not null, - PRIMARY KEY (groupe_membre_id) -) /*$wgDBTableOptions*/; diff --git a/sql/addTable-institutions.sql b/sql/addTable-institutions.sql deleted file mode 100644 index bc48317..0000000 --- a/sql/addTable-institutions.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE IF NOT EXISTS /*_*/mgw_institutions ( - institution_id int unsigned auto_increment not null, - institution_page_id int not null, - institution_nom varchar(64) not null, - institution_update_utilisateur_id int not null, - institution_update_time varbinary(14) not null, - PRIMARY KEY (institution_id) -) /*$wgDBTableOptions*/; diff --git a/sql/addTable-mgw_archive_groupe.sql b/sql/addTable-mgw_archive_groupe.sql new file mode 100644 index 0000000..8890fcd --- /dev/null +++ b/sql/addTable-mgw_archive_groupe.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe ( archive_id int unsigned not null auto_increment, groupe_id int unsigned not null, groupe_institution_id int unsigned not null, groupe_page_id int, groupe_type_id int unsigned, groupe_start_time varbinary(14), groupe_end_time varbinary(14), groupe_actif smallint, groupe_update_time varbinary(14) not null, groupe_updater_id int not null, PRIMARY KEY (archive_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_archive_groupe_membre.sql b/sql/addTable-mgw_archive_groupe_membre.sql new file mode 100644 index 0000000..f07deac --- /dev/null +++ b/sql/addTable-mgw_archive_groupe_membre.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe_membre ( archive_id int unsigned not null auto_increment, groupe_membre_id int unsigned not null, groupe_membre_groupe_id int unsigned not null, groupe_membre_user_id int not null, groupe_membre_isadmin smallint, groupe_membre_update_time varbinary(14) not null, groupe_membre_updater_id int not null, groupe_membre_drop_time varbinary(14) default null, groupe_membre_drop_updater_id int default null, PRIMARY KEY (archive_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_archive_groupe_type.sql b/sql/addTable-mgw_archive_groupe_type.sql new file mode 100644 index 0000000..e2d2394 --- /dev/null +++ b/sql/addTable-mgw_archive_groupe_type.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe_type ( archive_id int unsigned not null auto_increment, groupe_type_id int unsigned not null, groupe_type_nom varchar(64) not null, groupe_type_page_id int, groupe_type_admin_level smallint not null, groupe_type_user_level smallint not null, groupe_type_default_duration int, groupe_type_update_time varbinary(14) not null, groupe_type_updater_id int not null, groupe_type_drop_time varbinary(14) default null, groupe_type_drop_updater_id int default null, PRIMARY KEY (archive_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_archive_institution.sql b/sql/addTable-mgw_archive_institution.sql new file mode 100644 index 0000000..a34ddc8 --- /dev/null +++ b/sql/addTable-mgw_archive_institution.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_institution ( archive_id int unsigned not null auto_increment, institution_id int unsigned not null, institution_page_id int not null, institution_nom varchar(64) not null, institution_update_time varbinary(14) not null, institution_updater_id int not null, institution_drop_time varbinary(14) default null, institution_drop_updater_id int default null, PRIMARY KEY (archive_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_archive_utilisateur.sql b/sql/addTable-mgw_archive_utilisateur.sql new file mode 100644 index 0000000..740b948 --- /dev/null +++ b/sql/addTable-mgw_archive_utilisateur.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_utilisateur ( archive_id int unsigned not null auto_increment, utilisateur_id int unsigned not null, utilisateur_user_id int not null, utilisateur_nom varchar(64) not null, utilisateur_prenom varchar(64) not null, utilisateur_level smallint not null, utilisateur_update_time varbinary(14) not null, utilisateur_updater_id int not null, utilisateur_drop_time varbinary(14) default null, utilisateur_drop_updater_id int default null, PRIMARY KEY (archive_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_groupe.sql b/sql/addTable-mgw_groupe.sql new file mode 100644 index 0000000..cb808f9 --- /dev/null +++ b/sql/addTable-mgw_groupe.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe ( groupe_id int unsigned auto_increment not null, groupe_institution_id int unsigned not null, groupe_page_id int, groupe_type_id int unsigned, groupe_start_time varbinary(14), groupe_end_time varbinary(14), groupe_actif smallint default 1, groupe_updater_id int not null, groupe_update_time varbinary(14) not null, PRIMARY KEY (groupe_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_groupe_membre.sql b/sql/addTable-mgw_groupe_membre.sql new file mode 100644 index 0000000..68675cd --- /dev/null +++ b/sql/addTable-mgw_groupe_membre.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe_membre ( groupe_membre_id int unsigned auto_increment not null, groupe_membre_groupe_id int unsigned not null, groupe_membre_user_id int not null, groupe_membre_isadmin smallint default 0, groupe_membre_update_time varbinary(14) not null, groupe_membre_updater_id int not null, PRIMARY KEY (groupe_membre_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_groupe_type.sql b/sql/addTable-mgw_groupe_type.sql new file mode 100644 index 0000000..64fa5e0 --- /dev/null +++ b/sql/addTable-mgw_groupe_type.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe_type ( groupe_type_id int unsigned auto_increment not null, groupe_type_nom varchar(64) not null, groupe_type_page_id int, groupe_type_admin_level smallint not null, groupe_type_user_level smallint not null, groupe_type_default_duration int default null, groupe_type_update_time varbinary(14) not null, groupe_type_updater_id int not null, PRIMARY KEY (groupe_type_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_institution.sql b/sql/addTable-mgw_institution.sql new file mode 100644 index 0000000..c849fc3 --- /dev/null +++ b/sql/addTable-mgw_institution.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_institution ( institution_id int unsigned auto_increment not null, institution_page_id int not null, institution_nom varchar(64) not null, institution_update_time varbinary(14) not null, institution_updater_id int not null, PRIMARY KEY (institution_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_institution_groupe.sql b/sql/addTable-mgw_institution_groupe.sql new file mode 100644 index 0000000..5ad8ec0 --- /dev/null +++ b/sql/addTable-mgw_institution_groupe.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_institution_groupe ( institution_groupe_id int unsigned not null auto_increment, institution_groupe_type_id int unsigned not null, institution_groupe_institution_id int unsigned not null, PRIMARY KEY (institution_groupe_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-mgw_utilisateur.sql b/sql/addTable-mgw_utilisateur.sql new file mode 100644 index 0000000..47403a0 --- /dev/null +++ b/sql/addTable-mgw_utilisateur.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS /*_*/mgw_utilisateur ( utilisateur_id int unsigned not null auto_increment, utilisateur_user_id int not null, utilisateur_nom varchar(64) not null, utilisateur_prenom varchar(64) not null, utilisateur_level smallint default 0, utilisateur_update_time varbinary(14) not null, utilisateur_updater_id int not null, PRIMARY KEY (utilisateur_id)) /*$wgDBTableOptions*/; \ No newline at end of file diff --git a/sql/addTable-utilisateurs.sql b/sql/addTable-utilisateurs.sql deleted file mode 100644 index 7b20054..0000000 --- a/sql/addTable-utilisateurs.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE /*_*/mgw_utilisateurs ( - utilisateur_id int not null, - utilisateur_nom varchar(64) not null, - utilisateur_prenom varchar(64) not null, - utilisateur_level smallint, - utilisateur_update_utilisateur_id int not null, - utilisateur_update_time varbinary(14) not null, - PRIMARY KEY (utilisateur_id) -) /*$wgDBTableOptions*/; From a3dc2173aceba9d6ff6638165afd26df94f9e360 Mon Sep 17 00:00:00 2001 From: Looxloox Date: Wed, 16 Dec 2020 10:16:39 +0100 Subject: [PATCH 14/15] =?UTF-8?q?special:admin=20groupes=20types=20presque?= =?UTF-8?q?=20finalis=C3=A9=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Hooks.php | 56 ++- MGWikiDev.i18n.php | 4 + extension.json | 3 +- i18n/en.json | 3 +- i18n/fr.json | 3 +- i18n/qqq.json | 3 +- includes/Classes/MGWSpecialAdmin.php | 162 +++---- includes/Parsers.php | 69 ++- includes/SpecialAdminGroupTypes.php | 589 +++++++++++++++++------- includes/Utilities/HtmlFunctions.php | 255 ++++++++++ includes/Utilities/MgwDataFunctions.php | 13 +- includes/Utilities/PagesFunctions.php | 100 ++++ includes/Utilities/PhpFunctions.php | 17 + mgwiki.sql | 142 +++--- resources/ext.mgwiki-dev.css | 10 + resources/ext.mgwiki-specialadmin.css | 39 +- 16 files changed, 1147 insertions(+), 321 deletions(-) diff --git a/Hooks.php b/Hooks.php index 44abaf0..7451261 100644 --- a/Hooks.php +++ b/Hooks.php @@ -2,6 +2,7 @@ use MediaWiki\Extension\MGWikiDev\Parsers; use MediaWiki\Extension\MGWikiDev\Utilities\UsersFunctions as UserF; +use MediaWiki\Extension\MGWikiDev\Utilities\MgwDataFunctions as DbF; /** * MGWiki - development version @@ -25,6 +26,7 @@ public static function onExtensionLoad() { define("MGW_DB_UPDATED", 3); define("MGW_DB_DROPED", 4); define("MGW_DB_ERROR", 5); + define("MGW_PAGE_MISSING", 0); // définition des variables de type string global $wgMgwStringVars; @@ -34,7 +36,8 @@ public static function onExtensionLoad() { 'institution_nom', 'groupe_type_nom' ]; - // définition des niveaux de permissions + + // niveaux de permissions global $wgMgwLevels; $wgMgwLevels = [ 0 => 'U0', @@ -43,16 +46,64 @@ public static function onExtensionLoad() { 3 => 'U3', 4 => 'sysop' ]; + + // duration + function wfMgwDuration( int $int ) { + switch ( $int ) { + case 0: + return 'indéfini'; + break; + case 1: + return '6 mois'; + break; + case 2: + return '1 an'; + break; + default: + return ( $int / 2 ) . ' ans'; + break; + } + } } /** * Chargement du module MGWikiDev */ public static function onBeforePageDisplay( OutputPage $out, Skin $skin ) { + //Modules pour toutes les pages $out->addModules('ext.mgwiki-dev'); } + public static function onSpecialPageBeforeExecute ( $specialPage, $sub ) + { + // rediriger dans certains cas d'usage + + # pages ne devant pas être renommées manuellement + if ( $specialPage->getName() == 'Movepage') { + $url = $specialPage->getRequest()->getGlobalRequestURL(); + + // sous-pages MGWiki:Types_de_groupes/Xxx + if ( preg_match( '/MGWiki:(Types_de_groupes\/.*)$/', $url, $matches ) > 0 ) { + $title = Title::newFromText( $matches[1], NS_PROJECT ); + $screen = DbF::select_clean( + 'groupe_type', + [ 'id', 'page_id' ], + 'page_id = ' . $title->getArticleID() + ); + if ( !is_null( $screen ) ) { + $returnto = $specialPage->getTitleFor( 'specialadmingrouptypes' )->getLinkURL( [ + "action" => "edit", + "id" => $screen[0]['id'] + ] ); + $specialPage->getOutput()->redirect( $returnto ); + } + } + + } + + } + /** * ! CUSTOM HOOK ! (cf readme -> ApiMain.php changes) * Autoriser l'API getjson quelque soit l'utilisateur @@ -70,8 +121,9 @@ public static function onApiAllow( $module, $user ) // Register any render callbacks with the parser public static function onParserFirstCallInit( Parser $parser ) { - // Create a function hook associating the "example" magic word with renderExample() $parser->setFunctionHook( 'mgw-onclick', [ Parsers::class, 'onclickSpan' ] ); + $parser->setFunctionHook( 'mgw-groupe-type', [ Parsers::class, 'mgw_groupe_type' ] ); + $parser->setFunctionHook( 'mgw-display', [ Parsers::class, 'mgw_display' ] ); } /** diff --git a/MGWikiDev.i18n.php b/MGWikiDev.i18n.php index d430776..264be61 100644 --- a/MGWikiDev.i18n.php +++ b/MGWikiDev.i18n.php @@ -12,9 +12,13 @@ /** English (English) */ $magicWords['en'] = [ 'mgw-onclick' => [ 0, 'mgw-onclick' ], + 'mgw-groupe-type' => [ 0, 'mgw-groupe-type' ], + 'mgw-display' => [ 0, 'mgw-display' ] ]; /** French (français) */ $magicWords['fr'] = [ 'mgw-onclick' => [ 0, 'mgw-onclick' ], + 'mgw-groupe-type' => [ 0, 'mgw-groupe-type' ], + 'mgw-display' => [ 0, 'mgw-display' ] ]; diff --git a/extension.json b/extension.json index 875f028..1c4f524 100644 --- a/extension.json +++ b/extension.json @@ -36,7 +36,8 @@ "ApiAllow": "MGWikiDevHooks::onApiAllow", "ParserFirstCallInit": "MGWikiDevHooks::onParserFirstCallInit", "LoadExtensionSchemaUpdates": "MGWikiDevHooks::onLoadExtensionSchemaUpdates", - "AuthChangeFormFields": "MGWikiDevHooks::onAuthChangeFormFields" + "AuthChangeFormFields": "MGWikiDevHooks::onAuthChangeFormFields", + "SpecialPageBeforeExecute": "MGWikiDevHooks::onSpecialPageBeforeExecute" }, "callback": "MGWikiDevHooks::onExtensionLoad", "APIModules": { diff --git a/i18n/en.json b/i18n/en.json index fb2f352..e3ada1d 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -12,5 +12,6 @@ "specialcheckgroups": "Maintenance : groupes", "specialadmingrouptypes": "Maintenance: types de groupes", "specialmgwikilogin": "Login", - "specialmgwikilogin-unknownuser": "utilisateur inconnu" + "specialmgwikilogin-unknownuser": "utilisateur inconnu", + "mgw-cannot-move-page": "Vous ne pouvez pas renommer cette page" } diff --git a/i18n/fr.json b/i18n/fr.json index 9a24bf6..c8faed0 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -16,5 +16,6 @@ "usermerge-badoldid": "user_id de l'ancien utilisateur inexistant", "usermerge-badnewuserid": "user_id du nouvel utilisateur inexistant", "specialmgwikilogin": "Login", - "specialmgwikilogin-unknownuser": "utilisateur inconnu" + "specialmgwikilogin-unknownuser": "utilisateur inconnu", + "mgw-cannot-move-page": "Vous ne pouvez pas renommer cette page" } diff --git a/i18n/qqq.json b/i18n/qqq.json index fb2f352..e3ada1d 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -12,5 +12,6 @@ "specialcheckgroups": "Maintenance : groupes", "specialadmingrouptypes": "Maintenance: types de groupes", "specialmgwikilogin": "Login", - "specialmgwikilogin-unknownuser": "utilisateur inconnu" + "specialmgwikilogin-unknownuser": "utilisateur inconnu", + "mgw-cannot-move-page": "Vous ne pouvez pas renommer cette page" } diff --git a/includes/Classes/MGWSpecialAdmin.php b/includes/Classes/MGWSpecialAdmin.php index 0bb2dfd..fcc684e 100644 --- a/includes/Classes/MGWSpecialAdmin.php +++ b/includes/Classes/MGWSpecialAdmin.php @@ -1,9 +1,11 @@ headActions = [ 'action' => 'label' ] * $this->headHiddenFields = [ 'name' => 'value' ] */ - public function __construct( $specialSettings ) + public function __construct( ) { - parent::__construct( ...$specialSettings ); + parent::__construct( 'SpecialAdmin', 'permission' ); // ADAPTER $out = $this->getOutput(); $out->addModules('ext.mgwiki-specialadmin'); @@ -89,6 +91,82 @@ private function set_reqData() { return $this->getRequest()->getPostValues(); } + + /** + * @param array $reqData + */ + private function set_select( &$reqData ) + { + global $tri; + $select = &$this->select; + + $tri[1] = ( isset( $reqData['tri1'] ) ) + ? $reqData['tri1'] : 'user_id'; + + $tri[2] = ( isset( $reqData['tri2'] ) ) + ? $reqData['tri2'] : ''; + + foreach ( $this->triOptions as $value ) { + + if ( isset($reqData['tri1'] ) && $reqData['tri1'] == $value ) { + $select[1][$value] = 'selected'; + } else { + $select[1][$value] = ''; + } + + if ( isset($reqData['tri2'] ) && $reqData['tri2'] == $value ) { + $select[2][$value] = 'selected'; + } else { + $select[2][$value] = ''; + } + + if ( !in_array( 'selected', $select[2] ) ) { + $select[2]['...'] = 'selected'; + } else { + $select[2]['...'] = ''; + } + } + + return $tri; + } + + /** + * @param array $reqData + */ + private function set_check( &$reqData ) + { + $check = &$this->check; + + foreach ( $this->filtreOptions as $value ) { + + PhpF::empty( $reqData[$value] ); + + $check[$value]['view'] = + ( $reqData[$value] == 'view' ) + ? 'checked' : ''; + + $check[$value]['hide'] = + ( $reqData[$value] == 'hide' ) + ? 'checked' : ''; + + $check[$value]['only'] = + ( $reqData[$value] == 'only' ) + ? 'checked' : ''; + + if ( !in_array( + 'checked', + [ + $check[$value]['view'], + $check[$value]['hide'], + $check[$value]['only'] + ] ) + ) { + $check[$value]['view'] = 'checked'; + } + } + } + + /** * TODO OVERRIDE */ @@ -130,6 +208,7 @@ private function displayFooterActions() {} * @param array $hidden [ 'name' => 'value' ] * @param string $ctrlFn 'mw.maFonction()' */ + // => HtmlFunctions.php private function actionButton ( $action, $label, @@ -189,83 +268,6 @@ private function delAllBox ( $label ) { '; } - - /*********************** - ** Native fuctions ** - ***********************/ - - /** - * @param array $reqData - */ - private function set_select( &$reqData ) - { - global $tri; - $select = &$this->select; - - $tri[1] = ( isset( $reqData['tri1'] ) ) - ? $reqData['tri1'] : 'user_id'; - - $tri[2] = ( isset( $reqData['tri2'] ) ) - ? $reqData['tri2'] : ''; - - foreach ( $this->triOptions as $value ) { - - if ( isset($reqData['tri1'] ) && $reqData['tri1'] == $value ) { - $select[1][$value] = 'selected'; - } else { - $select[1][$value] = ''; - } - - if ( isset($reqData['tri2'] ) && $reqData['tri2'] == $value ) { - $select[2][$value] = 'selected'; - } else { - $select[2][$value] = ''; - } - - if ( !in_array( 'selected', $select[2] ) ) { - $select[2]['...'] = 'selected'; - } else { - $select[2]['...'] = ''; - } - } - - return $tri; - } - - /** - * @param array $reqData - */ - private function set_check( &$reqData ) - { - $check = &$this->check; - - foreach ( $this->filtreOptions as $value ) { - - $check[$value]['view'] = - ( isset( $reqData[$value] ) && $reqData[$value] == 'view' ) - ? 'checked' : ''; - - $check[$value]['hide'] = - ( isset( $reqData[$value] ) && $reqData[$value] == 'hide' ) - ? 'checked' : ''; - - $check[$value]['only'] = - ( isset( $reqData[$value] ) && $reqData[$value] == 'only' ) - ? 'checked' : ''; - - if ( !in_array( - 'checked', - [ - $check[$value]['view'], - $check[$value]['hide'], - $check[$value]['only'] - ] ) - ) { - $check[$value]['view'] = 'checked'; - } - } - } - /** * @param string $msg_prefix * le préfixe des messages à récupérer diff --git a/includes/Parsers.php b/includes/Parsers.php index 9dbb49a..0558edc 100644 --- a/includes/Parsers.php +++ b/includes/Parsers.php @@ -2,6 +2,9 @@ namespace MediaWiki\Extension\MGWikiDev; +use MediaWiki\Extension\MGWikiDev\Utilities\HtmlFunctions as HtmlF; +use MediaWiki\Extension\MGWikiDev\Utilities\MgwDataFunctions as DbF; + class Parsers { // Render the output of {{#mgw-onclick:target|tag|include|blank=false}}. @@ -22,6 +25,70 @@ public static function onclickSpan( $parser, $target = '', $tag = 'span', $inclu $output = $include; } - return [ $output, 'isHTML' => true ];//, 'noparse' => true, + return [ $output, 'isHTML' => true ];//, 'noparse' => true, + } + + // retrieve data from mgw_groupe_type table + // must be called from a' MGWiki:Types_de_groupes/' subpage + public static function mgw_groupe_type( $parser, $field = '' ) { + + $title = $parser->getTitle(); + $fields = [ 'admin_level', 'user_level', 'default_duration' ]; + + if ( $title->getNamespace() != NS_PROJECT || + preg_match( '/Types de groupes\/(.*)$/', $title->getText() ) < 1 ) + { + $output = HtmlF::parseError('La balise {{#mgw-groupe-type:[arg]}} doit être employée sur une sous-page '. + '"MGWiki:Types_de_groupes/..."' ); + return [ $output, 'noparse' => true, 'isHTML' => true ]; + } + + if ( empty( $field ) ) { + $output = HtmlF::parseError('La balise {{#mgw-groupe-type:[arg]}} doit comporter un argument' ); + return [ $output, 'noparse' => true, 'isHTML' => true ]; + } + + if ( !in_array( $field, $fields ) ) { + $output = HtmlF::parseError('{{#mgw-groupe-type:[arg]}} : mauvais argument => "admin_level" | "user_level" | "default_duration"' ); + return [ $output, 'noparse' => true, 'isHTML' => true ]; + } + + $screen = DbF::select_clean( 'groupe_type', ['page_id', $field ], 'page_id = ' . $title->getArticleID() ); + if ( is_null( $screen ) ) { + $output = HtmlF::parseError('{{#mgw-groupe-type:' . $field . + '}} : cette page n\'est pas connue dans la table mgw_groupe_type.' ); + return [ $output, 'noparse' => true, 'isHTML' => true ]; + } + else { + return $screen[0][$field]; + } } + + // retrieve data from mgw_groupe_type table + // must be called from a' MGWiki:Types_de_groupes/' subpage + public static function mgw_display( $parser, $value = '', $field = '' ) { + + $fields = [ 'level', 'duration' ]; + + if ( $value === '' ) { + $output = HtmlF::parseError('{{#mgw-display:[value]|[field]}} : [value] absent.' ); + return [ $output, 'noparse' => true, 'isHTML' => true ]; + } + + if ( !in_array( $field, $fields ) ) { + $output = HtmlF::parseError('{{#mgw-display:[value]|[field]}} : Mauvais argument '. + '[field] => "'. implode('" | "', $fields) .'"' ); + return [ $output, 'noparse' => true, 'isHTML' => true ]; + } + + switch ( $field ) { + case 'level': + global $wgMgwLevels; + return $wgMgwLevels[ (int)$value ]; + break; + case 'duration': + return wfMgwDuration( (int)$value ); + break; + } + } } diff --git a/includes/SpecialAdminGroupTypes.php b/includes/SpecialAdminGroupTypes.php index f8da125..a8634e8 100644 --- a/includes/SpecialAdminGroupTypes.php +++ b/includes/SpecialAdminGroupTypes.php @@ -10,6 +10,7 @@ use MediaWiki\Extension\MGWikiDev\Utilities\MgwDataFunctions as DbF; use MediaWiki\Extension\MGWikiDev\Utilities\PhpFunctions as PhpF; use MediaWiki\Extension\MGWikiDev\Utilities\HtmlFunctions as HtmlF; +use MediaWiki\Extension\MGWikiDev\Utilities\PagesFunctions as PageF; use MediaWiki\Extension\MGWikiDev\Classes\MGWStatus as Status; // https://www.mediawiki.org/wiki/HTMLForm @@ -31,54 +32,47 @@ class SpecialAdminGroupTypes extends SpecialPage { /** * @var array */ - private $triOptions, $filtreOptions, $filtreOptionsTitles; + private $tri, $headInfos; public function __construct() { parent::__construct( 'specialadmingrouptypes', 'editinterface' ); // restrict to sysops # définition des options - $this->triOptions = ['nom', 'admin_level', 'user_level', 'default_duration', 'update_time', 'updater_id']; - $this->filtreOptions = [ - 'show_archive', - 'sysop', - 'U3', - 'U2', - 'U1', - 'U0' - ]; - $this->filtreOptionsTitles = [ - 'admin_level' => [ 'sysop', 'U3', 'U2', 'U1', 'U0' ], - 'user_level' => [ 'sysop', 'U3', 'U2', 'U1', 'U0' ] - ]; + $this->headInfos['triOptions'] = ['nom', 'admin_level', 'user_level', 'default_duration', 'update_time', 'updater_id']; + $this->headInfos['filtreOptions'] = [ 'archive' ]; + $this->headInfos['filtreOptionsTitles'] = []; + $this->headInfos['headActions'] = [ 'refresh' => 'rafraîchir' ]; + $this->headInfos['headHiddenFields'] = []; } public function execute( $sub ) { // récupération et contrôle des entrées - $reqData = $this->getRequest()->getValues(); - PhpF::empty( $reqData['action'] ); - PhpF::empty( $reqData['id'] ); - PhpF::int( $reqData['id'] ); + $reqData = $this->set_reqData(); + HtmlF::set_select( $reqData, $this->headInfos['triOptions'], $this->tri, $this->select ); + HtmlF::set_check( $reqData, $this->headInfos['filtreOptions'], $this->check ); + // préparation de l'affichage $this->setHeaders(); $out = $this->getOutput(); + $out->addModules('ext.mgwiki-specialadmin'); $out->addModules('ext.mgwiki-specialadmingrouptypes'); $out->enableOOUI(); - // mode d'affichage + // sélection du mode d'affichage $edit = ( $reqData['action'] == 'edit' ); - $history = ( $reqData['action'] == 'history' && !is_null( $reqData['id'] )); + $show = ( ! $edit && ! $history ); - $show = ( $reqData['action'] != 'edit' && $reqData['action'] != 'history' ); - + // SUBMIT if ( isset( $reqData['submit'] ) ) { $status = $this->formCallback( $reqData, $edit, $show ); $done = ( $status->done() ) ? 'true' : 'false'; $out->addHTML( HtmlF::alertMessage( $status ) ); } - // undo + // ACTIONS + # undo if ( $reqData['action'] == 'undo' && isset( $reqData['id'] ) && isset( $reqData['archive'] ) ) { $undo = $this->undo( $reqData['id'], $reqData['archive'] ); $out->addHTML( HtmlF::alertMessage( $undo ) ); @@ -86,40 +80,60 @@ public function execute( $sub ) { $show = false; } - // delete + # delete if ( $reqData['action'] == 'delete' && isset( $reqData['id'] ) ) { - $delete = $this->delete( $reqData['id'] ); + $delete = $this->delete( $reqData['id'], 'Suppression de type de groupe.' ); $out->addHTML( HtmlF::alertMessage( $delete ) ); $history = false; $show = true; } - // historique + # undelete + if ( $reqData['action'] == 'undelete' && isset( $reqData['archive'] ) ) { + $undelete = $this->undelete( $reqData['archive'] ); + $out->addHTML( HtmlF::alertMessage( $undelete ) ); + $history = true; + $show = false; + } + + // VUES + # history if ( $history ) { - $groupe_types = $this->getGroupTypeHistory( $reqData['id'] ); - $out->setPageTitle( Msg::get('specialadmingrouptypes-title-history', [ $groupe_types[0]['nom'] ] ) ); - $out->addHTML( $this->makeView( $groupe_types ) ); + + $show_array = $this->getHistoryShowArray( $reqData['id'] ); + + $out->setPageTitle( Msg::get('specialadmingrouptypes-title-history', [ $show_array[0]['nom'] ] ) ); + $out->addHTML( $this->makeView( $show_array ) ); $out->addHTML( '
    ' . (string)new \OOUI\ButtonWidget( [ 'href' => $this->selfURL(), 'label' => 'retour' ] ) ); } - // affichage général + # show if ( $show ) { - $out->setPageTitle( Msg::get('specialadmingrouptypes-title') ); + $show_array = $this->getShowArray( '', [ 'ORDER BY' => 'nom' ] ); + + if ( count( $show_array ) > 0 ) { + HtmlF::sort_show_array( $show_array, $this->tri ); + } - $groupe_types = $this->getGroupTypes( '', [ 'ORDER BY' => 'nom' ] ); - $out->addHTML( $this->makeView( $groupe_types ) ); + $out->setPageTitle( Msg::get( 'specialadmingrouptypes-title' ) ); + $out->addHTML( HtmlF::makeHeadForm( $this->selfURL(), $this->select, $this->check, $this->tri, $this->headInfos, 'specialadmingrouptypes' ) ); + $out->addHTML( $this->makeView( $show_array ) ); $out->addHTML( '
    ' . (string)new \OOUI\ButtonWidget( [ 'href' => $this->selfURL( [ 'action' => 'edit' ] ), - 'label' => 'nouveau' + 'label' => 'nouveau', + 'flags' => [ + 'primary', + 'progressive' + ] ] ) ); } - // formulaire d'édition + # edit if ( $edit ) { $this->setData( $reqData ); if ( !empty( $this->nom ) ) { @@ -132,109 +146,235 @@ public function execute( $sub ) { } } - public static function formCallback( &$reqData, &$edit, &$show ) { + private function set_reqData() { + $reqData = $this->getRequest()->getValues(); + PhpF::empty( $reqData['action'] ); + PhpF::empty( $reqData['id'] ); + PhpF::int( $reqData['id'] ); + PhpF::empty( $reqData['reverse-a'] ); + PhpF::empty( $reqData['reverse-b'] ); + return $reqData; + } + + + /** + * @param int $id + * @param int $archive_id + */ + private function undo( $id, $archive_id ) { global $wgUser; - PhpF::html( $reqData['nom'] ); - PhpF::int( $reqData['admin_level'] ); - PhpF::int( $reqData['user_level'] ); - PhpF::int( $reqData['duration'] ); + $old = DbF::select_clean( + 'archive_groupe_type', + [ 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration' ], + 'archive_id = ' . $archive_id + )[0]; - $check = DbF::select_clean( 'groupe_type', [ 'id', 'nom' ] ); - foreach ( $check as $row ) { - if ( strtolower( $row['nom'] ) == strtolower( $reqData['nom'] ) - && $row['id'] != $reqData['id'] ) { - $edit = true; // on ré-affiche le formulaire - $show = false; - return Status::newFailed( 'Le nom demandé existe déjà. Vous pouvez: - ' ); + $actual = DbF::select_clean( + 'groupe_type', + [ 'id', 'nom', 'page_id' ], + 'id = ' . $id + )[0]; + + $currentNames = DbF::select_clean( + 'groupe_type', + [ 'id', 'nom' ] + ); + + // on vérifie la non création de doublon + foreach ( $currentNames as $row ) { + if ( strtolower( $row['nom'] ) == strtolower( $old['nom'] ) ) { + return Status::newFailed( 'Le nom '.$old['nom'] . + ' correspond à un autre type de groupe en cours d\'utilisation.' + .' Veuillez rétablir les modifications manuellement avec un autre nom.' ); } } - if ( is_null( $reqData['id'] ) ) { - $select = [ 'nom' => $reqData['nom'] ]; - $data = [ - 'admin_level' => $reqData['admin_level'], - 'user_level' => $reqData['user_level'], - 'default_duration' => $reqData['duration'] - ]; + // renamePage si besoin + if ( $old['nom'] != $actual['nom'] ) { + $rename = PageF::renamePage( + 'Types de groupes/' . $actual['nom'], + NS_PROJECT, + 'Types de groupes/' . $old['nom'], + NS_PROJECT, + 'Ancienne version restaurée.', + $wgUser + ); + if( !$rename->done() ) { + return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . + $actual['nom'] . ' n\'a pas pu être renomée.' ); + } + $old['page_id'] = $rename->extra(); } - else { - $select = [ 'id' => $reqData['id'] ]; - $data = [ - 'nom' => $reqData['nom'], - 'admin_level' => $reqData['admin_level'], - 'user_level' => $reqData['user_level'], - 'default_duration' => $reqData['duration'] - ]; + + // màj des tables + $update = DbF::update( 'groupe_type', [ 'id' => $id ], $old, $wgUser->getId() ); + if ( ! $update->done() ) { + return $update; } - $write = DbF::update_or_insert( 'groupe_type', $select, $data, $wgUser->getId() ); + // refreshPage + $refresh = PageF::refreshPage( + $old['page_id'], + Msg::get( 'specialadmingrouptypes-update-summary' ), + $wgUser + ); + if ( ! $refresh->done() ) { + return Status::newFailed( 'Les modifications ont été enregistrées mais la page MGWiki:Types de groupes/' + . $old['nom'] . ' n\'a pas pu être réactualisée.' ); + } + + // succès + return Status::newDone( 'L\'ancienne version de '.$old['nom'].' a été rétablie.'); + } - if ( $write->done() ) { - $edit = false; - return Status::newDone( 'Les modifications de ' . $reqData['nom'] . ' ont été enregistrées.' ); + /** + * @param int $id mgw_groupe_type_id + * @param string $summary + */ + private function delete( $id, $summary ) { + global $wgUser; + // suppression dans les tables + $dbDelete = DbF::delete( 'groupe_type', [ 'id' => $id ], $wgUser->getId() ); + if ( !$dbDelete->done() ){ + return $dbDelete; } - else { - $edit = true; - return $write; + // suppression de la page + $pageDelete = PageF::lightDelete( $dbDelete->extra()['groupe_type_page_id'], $summary ); + if ( ! $pageDelete->done() ) { + return Status::newFailed( '' . $dbDelete->extra()['groupe_type_nom'] . ' a été supprimé mais'. + ' la page "MGWiki:Types_de_groupes/' . $dbDelete->extra()['groupe_type_nom'] . + '" existe toujours ( ' . $dbDelete->mess() . ' )' ); } - } + // succès + return Status::newDone( ''.$dbDelete->extra()['groupe_type_nom'].' a été supprimé.'); + } - private function undo( $id, $archive_id ) { + + /** + * @param int $archive_id + */ + private function undelete( $archive_id ) { global $wgUser; - $old_type = DbF::select_clean( + + // requêtes + $old = DbF::select_clean( 'archive_groupe_type', [ 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration' ], 'archive_id = ' . $archive_id )[0]; - $actual_types = DbF::select_clean( + + $names = DbF::select_clean( 'groupe_type', [ 'id', 'nom' ] ); - foreach ( $actual_types as $type ) { - if ( strtolower( $type['nom'] ) == strtolower( $old_type['nom'] ) ) { - return Status::newFailed( 'Le nom '.$old_type['nom'] . - ' correspond à un autre type de groupe en cours d\'utilisation.' - .' Veuillez rétablir les modifications manuellement avec un autre nom.' ); + + // on vérifie l'absence de doublons + foreach ( $names as $row ) { + if ( strtolower( $row['nom'] ) == strtolower( $old['nom'] ) ) { + return Status::newFailed( 'Le nom '.$old['nom'] . + ' correspond à un autre type de groupe en cours d\'utilisation.' + .' Veuillez rétablir le groupe supprimé manuellement avec un autre nom.' ); } } - $status = DbF::update( 'groupe_type', [ 'id' => $id ], $old_type, $wgUser->getId() ); - if ( $status->done() ) { - return Status::newDone( 'L\'ancienne version de '.$old_type['nom'].' a été rétablie.'); + + // on re-crée la page + $newPage = PageF::newPage( + 'Types de groupes/' . $old['nom'], + NS_PROJECT, + '{{Modèle:Types de groupes}}', + Msg::get( 'specialadmingrouptypes-newpage-summary' ), + $wgUser + ); + if( !$newPage->done() ) { + return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . + $old['nom'] . ' n\'a pas pu être créée. (' . $newPage->mess() . ')' ); } - else { + $old['page_id'] = $newPage->extra(); + + // on rétablit l'ancienne version + $status = DbF::insert( 'groupe_type', $old, $wgUser->getId() ); + if ( ! $status->done() ) { return $status; } + + // page refresh + $refresh = PageF::refreshPage( + $old['page_id'], + Msg::get( 'specialadmingrouptypes-update-summary' ), + $wgUser + ); + if ( ! $refresh->done() ) { + return Status::newFailed( 'Les modifications ont été enregistrées mais la page MGWiki:Types de groupes/' + . $old['nom'] . ' n\'a pas pu être réactualisée.' ); + } + + // succès + return Status::newDone( $old['nom'].' a été rétabli.'); } - private function delete( $id ) { - global $wgUser; - $status = DbF::delete( 'groupe_type', [ 'id' => $id ], $wgUser->getId() ); - if ( $status->done() ) { - return Status::newDone( ''.$status->extra()['groupe_type_nom'].' a été supprimé.'); + + private function getShowArray( $select = '', $opts = [] ) { + + $groupTypes = []; + if ( $this->check['archive']['only'] != 'checked' ) { + $groupTypes = DbF::select_clean( + 'groupe_type', + [ 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration', 'update_time', 'updater_id' ], + $select, + $opts + ); } - else { - return $status; + + if ( $this->check['archive']['view'] == 'checked' || $this->check['archive']['only'] == 'checked' ) { + if ( !empty( $select ) ) { + $select = implode( ' AND ', [ $select, 'drop_time IS NOT NULL'] ); + } + else { + $select = 'drop_time IS NOT NULL'; + } + $droped = DbF::select_clean( + 'archive_groupe_type', + [ 'archive_id', 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration', 'update_time', 'updater_id', 'drop_time', 'drop_updater_id' ], + $select, + $opts + ); + $groupTypes = array_merge( $groupTypes, $droped ); } - } - private function getGroupTypes( $select = '', $opts = [] ) { - return DbF::select_clean( - 'groupe_type', - [ 'id', 'nom', 'page_id', 'admin_level', 'user_level', 'default_duration', 'update_time', 'updater_id' ], - $select, - $opts - ); + foreach ( $groupTypes as $key => $row ) { + PhpF::null( $groupTypes[$key]['drop_time'] ); + PhpF::null( $groupTypes[$key]['archive_id'] ); + $groupTypes[$key]['droped'] = !is_null( $groupTypes[$key]['drop_time'] ); + $groupTypes[$key]['undroped'] = false; + if ( $groupTypes[$key]['droped'] ) { + // on le retire s'il a été rétabli depuis + $screen = DbF::select_clean( + 'groupe_type', + [ 'id', 'update_time' ], + 'id = ' . $groupTypes[$key]['id'] . ' AND groupe_type_update_time > ' . $groupTypes[$key]['update_time'] + ); + if ( !is_null( $screen ) ) { + unset( $groupTypes[$key] ); + } + // on n'affiche que la dernière suppression si plusieurs + if ( isset( $groupTypes[$key] ) ) { + $screen = DbF::select_clean( + 'archive_groupe_type', + [ 'id', 'drop_time' ], + 'id = ' . $groupTypes[$key]['id'] . ' AND groupe_type_drop_time > ' . $groupTypes[$key]['drop_time'] + ); + if ( !is_null( $screen ) ) { + unset( $groupTypes[$key] ); + } + } + } + } + return $groupTypes; } - private function getGroupTypeHistory( $id ) { + private function getHistoryShowArray( $id ) { $actu = DbF::select_clean( 'groupe_type', @@ -249,87 +389,110 @@ private function getGroupTypeHistory( $id ) { 'id = ' . $id, [ 'ORDER BY' => 'update_time DESC' ] ); - if ( !is_null($past) ) { - $actu = array_merge( $actu, $past ); + + if ( is_null( $actu ) ) { + $actu = []; } - return $actu; - } + if ( is_null( $past ) ) { + $past = []; + } + + $return = array_merge( $actu, $past ); - //////////////////////////////////// - // FONCTIONS RELATIVES A L'AFFICHAGE + foreach ( $return as $key => $row ) { + PhpF::null( $return[$key]['drop_time'] ); + PhpF::null( $return[$key]['archive_id'] ); + $return[$key]['droped'] = !is_null( $return[$key]['drop_time'] ); + $return[$key]['undroped'] = isset( $return[$key - 1] ); + } - private function makeView( $groupe_types ) { + return $return; + } + + private function makeView( $show_array ) { + if ( $show_array == [] ) { + return HtmlF::tagShowArray('
      Aucun type de groupe ne correspond à la sélection'); + } global $wgMgwLevels; - $print = '
    '; + # préparation des variables - foreach ( $groupe_types as $type ) { - switch ( $type['default_duration'] ) { - case 0: - $duration = 'indéfini'; - break; - case 1: - $duration = '6 mois'; - break; - case 2: - $duration = '1 an'; - break; - default: - $duration = ( $type['default_duration'] / 2 ) . ' ans'; - break; - } + foreach ( $show_array as $row ) { + + $duration = wfMgwDuration( $row['default_duration'] ); - $buttons = ( isset( $type['archive_id'] ) ) - ? HtmlF::onclickButton( + if ( $row['droped'] && !$row['undroped'] ) { + $buttons = HtmlF::onclickButton( 'rétablir', - $this->selfURL( [ 'action'=>'undo', 'id'=>$type['id'], 'archive'=>$type['archive_id'] ] ) ) - : HtmlF::onclickButton( + $this->selfURL( [ 'action' => 'undelete', 'id' => $row['id'], 'archive' => $row['archive_id'] ] ) + ) . 'historique' ; + } + elseif ( !is_null( $row['archive_id'] ) ) { + $buttons = HtmlF::onclickButton( + 'rétablir', + $this->selfURL( [ 'action'=>'undo', 'id'=>$row['id'], 'archive'=>$row['archive_id'] ] ) + ); + } + else { + $buttons = HtmlF::onclickButton( 'modifier', - $this->selfURL( [ 'action'=>'edit', 'id'=>$type['id'] ] ) ) . ' - selfURL( [ 'action'=>'history', 'id'=>$row['id'] ] ) . '" class="mgw-history-link">historique - supprimer' ; + supprimer' ; + } - $class = ( isset( $type['archive_id'] ) ) - ? 'mgw-archive' - : 'mgw-actif'; + if ( $row['droped'] ) { + $class = 'deleted'; + } + elseif ( !is_null( $row['archive_id'] ) ) { + $class = 'inactive'; + } + else { + $class = 'active'; + } - if ( isset( $type['drop_time'] ) && $type['drop_time'] != 0 ) { - $update_time = wfTimestamp( TS_DB, $type['drop_time'] ); - $updater = \User::newFromId ( $type['drop_updater_id'] ); + if ( $row['droped'] ) { + $update_time = wfTimestamp( TS_DB, $row['drop_time'] ); + $updater = \User::newFromId ( $row['drop_updater_id'] ); $updater_link = '' . $updater->getName() . ''; $maj = 'Supprimé le ' . $update_time . ' par ' . $updater_link; } else { - $update_time = wfTimestamp( TS_DB, $type['update_time'] ); - $updater = \User::newFromId ( $type['updater_id'] ); + $update_time = wfTimestamp( TS_DB, $row['update_time'] ); + $updater = \User::newFromId ( $row['updater_id'] ); $updater_link = '' . $updater->getName() . ''; $maj = 'Màj le ' . $update_time . ' par ' . $updater_link ; } #intégration - $print .= ' - + $rowTable = ' - - + + - - + + - -
    ' . $type['nom'] . 'Page: ' . $type['page_id'] . ' ' . HtmlF::linkPageId( $row['nom'], $row['page_id'] ) . ' ' . $buttons . '
    Admin : ' . $wgMgwLevels[ $type['admin_level'] ] . 'User : ' . $wgMgwLevels[ $type['user_level'] ] . 'Admin : ' . $wgMgwLevels[ $row['admin_level'] ] . 'User : ' . $wgMgwLevels[ $row['user_level'] ] . ' Durée : ' . $duration . '
    ' . $maj . '
    '; + '; + + $print[] = HtmlF::tagRowTable( $rowTable, $class ); } - $print .= '
    '; + $print = HtmlF::tagShowArray( implode( '', $print ) ); return $print; } + /////////////// + // FORMULAIRE + private function setData( $reqData ){ // valeurs par défaut if ( isset( $reqData['submit'] ) ) { @@ -340,7 +503,7 @@ private function setData( $reqData ){ $this->duration = $reqData['duration']; } elseif ( !empty( $reqData['id'] ) ) { - $row = $this->getGroupTypes( + $row = $this->getShowArray( 'id = ' . $reqData['id'], [ 'ORDER BY' => 'nom' ] )[0]; @@ -360,7 +523,6 @@ private function setData( $reqData ){ } } - private function makeOouiForm( $reqData ) { $html = new \OOUI\FormLayout( [ 'method' => 'POST', @@ -379,7 +541,8 @@ private function makeOouiForm( $reqData ) { 'name' => 'nom', 'type' => 'text', 'value' => $this->nom, - 'required' => true + 'required' => true, + 'autofocus' => true ] ), [ 'label' => 'Nom du type de groupe', @@ -441,7 +604,11 @@ private function makeOouiForm( $reqData ) { new \OOUI\ButtonInputWidget( [ 'name' => 'submit', 'label' => 'Valider', - 'type' => 'submit' + 'type' => 'submit', + 'flags' => [ + 'primary', + 'progressive' + ] ] ), [ 'label' => null, @@ -459,6 +626,116 @@ private function makeOouiForm( $reqData ) { return $html; } + public static function formCallback( &$reqData, &$edit, &$show ) { + global $wgUser; + PhpF::html( $reqData['nom'] ); + PhpF::int( $reqData['admin_level'] ); + PhpF::int( $reqData['user_level'] ); + PhpF::int( $reqData['duration'] ); + + // on interdit les doublons sur le champs 'nom' + $check = DbF::select_clean( 'groupe_type', [ 'id', 'nom' ] ); + foreach ( $check as $row ) { + if ( strtolower( $row['nom'] ) == strtolower( $reqData['nom'] ) + && $row['id'] != $reqData['id'] ) { + $edit = true; // on ré-affiche le formulaire + $show = false; + return Status::newFailed( 'Le nom demandé existe déjà. Vous pouvez: + ' ); + } + } + + // préparation de la reqête + if ( is_null( $reqData['id'] ) ) { + $select = [ 'nom' => ucfirst( $reqData['nom'] ) ]; + $data = [ + 'admin_level' => $reqData['admin_level'], + 'user_level' => $reqData['user_level'], + 'default_duration' => $reqData['duration'] + ]; + $new = true; + } + else { + $select = [ 'id' => $reqData['id'] ]; + $data = [ + 'nom' => ucfirst( $reqData['nom'] ), + 'admin_level' => $reqData['admin_level'], + 'user_level' => $reqData['user_level'], + 'default_duration' => $reqData['duration'] + ]; + $new = false; + } + + // gestion des pages du wiki + if ( $new ) { + + # on crée une nouvelle page + $status = PageF::newPage( + 'Types de groupes/' . $select['nom'], + NS_PROJECT, + '{{Modèle:Types de groupes}}', + Msg::get( 'specialadmingrouptypes-newpage-summary' ), + $wgUser + ); + if( !$status->done() ) { + return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . + $reqData['nom'] . ' n\'a pas pu être créée. (' . $status->mess() . ')' ); + } + $data['page_id'] = $status->extra(); + } + else { + + # on renomme la page si le nom est modifié + $oldData = DbF::select_clean( 'groupe_type', ['id', 'nom', 'page_id' ], 'id = ' . $reqData['id'] )[0]; + + if ( $oldData['nom'] != $data['nom'] ) { + $status = PageF::renamePage( + 'Types de groupes/' . $oldData['nom'], + NS_PROJECT, + 'Types de groupes/' . $data['nom'], + NS_PROJECT, + 'Mise à jour automatisée suite à modification de nom', + $wgUser + ); + if( !$status->done() ) { + return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . + $reqData['nom'] . ' n\'a pas pu être renomée.' ); + } + $data['page_id'] = $status->extra(); + } + else { + $data['page_id'] = $oldData['page_id']; + } + } + + // écriture des tables + $write = DbF::update_or_insert( 'groupe_type', $select, $data, $wgUser->getId() ); + if ( ! $write->done() ) { + $edit = true; + return $write; + } + $edit = false; + + // réactualisation de la page + $refresh = PageF::refreshPage( + $data['page_id'], + Msg::get( 'specialadmingrouptypes-update-summary' ), + $wgUser + ); + + if ( ! $refresh->done() ) { + return Status::newFailed( 'Les modifications ont été enregistrées mais la page MGWiki:Types de groupes/' + . $reqData['nom'] . ' n\'a pas pu être réactualisée.' ); + } + + return Status::newDone( 'Les modifications de ' . $reqData['nom'] . ' ont été enregistrées.' ); + } + /** * @param array $get associative array of GET request parameters * [key => value] diff --git a/includes/Utilities/HtmlFunctions.php b/includes/Utilities/HtmlFunctions.php index 6969bb0..0d2bc0e 100644 --- a/includes/Utilities/HtmlFunctions.php +++ b/includes/Utilities/HtmlFunctions.php @@ -2,11 +2,20 @@ namespace MediaWiki\Extension\MGWikiDev\Utilities; +use Title; + +use MediaWiki\Extension\MGWikiDev\Utilities\GetMessage as Msg; +use MediaWiki\Extension\MGWikiDev\Utilities\PhpFunctions as PhpF; + /** * Divers outils de mise en forme HTML */ class HtmlFunctions { + public static function parseError( $text ) { + return '' . $text . ''; + } + /** * @param MGWStatus $status */ @@ -28,4 +37,250 @@ public static function onclickButton( $value, $href ) { return ''; } + + /** + * @param array $hidden [ 'name' => 'value' ] + * @param string $ctrlFn 'mw.maFonction()' + */ + public static function actionButton ( + $action, + $label, + $check = [ 'add' => 'false', 'del' => 'false' ], + $ctrlFn = '', + $hidden = [] ) + { + $extra = ' extra="' . json_encode( $hidden ) . '"'; + return ''; + } + + /** + * @param int $a + */ + public static function reverseCheck( $a, $checked = '' ) { + return ' + '; + } + + /** + */ + public static function tagShowArray( $content ) { + return '
    ' . $content . '
    '; + } + + /** + * @param string $content ' (...) ' + * @param string $class 'active'|'inactive'|'deleted' + */ + public static function tagRowTable( $content, $class ) { + return '' . $content . '
    '; + } + + + /** + * @param string $text le texte du lien + * @param int $id page_id + */ + public static function linkPageId( $text, $id ) { + $title = Title::newFromID( $id ); + if ( !is_null( $title ) ) { + return '' . $text . ''; + } + else { + return $text; + } + } + + + /** + * @param array $reqData + */ + public static function set_select( $reqData, $triOptions, &$tri, &$select ) + { + $tri[1]['rev'] = ( empty($reqData['reverse-a']) ) ? '' : 'checked'; + $tri[2]['rev'] = ( empty($reqData['reverse-b']) ) ? '' : 'checked'; + + $tri[1]['val'] = ( isset( $reqData['tri1'] ) ) + ? $reqData['tri1'] : 'nom'; + + $tri[2]['val'] = ( isset( $reqData['tri2'] ) ) + ? $reqData['tri2'] : '' ; + + foreach ( $triOptions as $value ) { + + if ( isset($reqData['tri1'] ) && $reqData['tri1'] == $value ) { + $select[1][$value] = 'selected'; + } else { + $select[1][$value] = ''; + } + + if ( isset($reqData['tri2'] ) && $reqData['tri2'] == $value ) { + $select[2][$value] = 'selected'; + } else { + $select[2][$value] = ''; + } + + if ( !in_array( 'selected', $select[2] ) ) { + $select[2]['...'] = 'selected'; + } else { + $select[2]['...'] = ''; + } + } + } + + /** + * @param array $reqData + */ + public static function set_check( &$reqData, $filtreOptions, &$check ) + { + foreach ( $filtreOptions as $value ) { + + PhpF::empty( $reqData[$value] ); + + $check[$value]['view'] = + ( $reqData[$value] == 'view' ) + ? 'checked' : ''; + + $check[$value]['hide'] = + ( $reqData[$value] == 'hide' ) + ? 'checked' : ''; + + $check[$value]['only'] = + ( $reqData[$value] == 'only' ) + ? 'checked' : ''; + + if ( !in_array( + 'checked', + [ + $check[$value]['view'], + $check[$value]['hide'], + $check[$value]['only'] + ] ) + ) { + if ( $value == 'archive' ) { + $check[$value]['hide'] = 'checked'; + } + else { + $check[$value]['view'] = 'checked'; + } + } + } + } + + /** + * @param string $url + * @param array $select + * @param array $check + * @param string $msg_prefix préfixe des messages à récupérer + * + * @return string + */ + public static function makeHeadForm ( $url, $select, $check, $tri, $headInfos, $msg_prefix ) + { + $return = ' +
    '; + + // TRI + $return .= ' +
    + Tri: + + ' . self::reverseCheck( 'a', $tri[1]['rev'] ) . '
    + + ' . self::reverseCheck( 'b', $tri[2]['rev'] ) . ' +
    '; + // FILTRES + $return .= ' +
    Filtres: + + + + + + + '; + $writeTitle = false; + foreach ( $headInfos['filtreOptions'] as $filtreOption ) { + foreach ( $headInfos['filtreOptionsTitles'] as $title => $titleOptions ) { + if ( in_array( $filtreOption, $titleOptions ) && !$writeTitle ){ + $return .= ''; + $writeTitle = true; + } + elseif ( !in_array( $filtreOption, $titleOptions ) ) + $writeTitle = false; + } + $return .= ' + + + + + + '; + } + $return .= ' +
    seul
    ' . Msg::get( $msg_prefix.'-'.$title ) . '
    ' . Msg::get( $msg_prefix.'-'.$filtreOption ) . '
    +
    +
    '; + + if ( count( $headInfos['headActions'] ) > 0 ) { + foreach ( $headInfos['headActions'] as $action => $label ) { + $return .= self::actionButton( $action, $label ); + } + } + + if ( count( $headInfos['headHiddenFields'] ) > 0 ) { + foreach ( $headInfos['headHiddenFields'] as $field => $value ) { + $return .= ''; + } + } + + $return .= ' + + + + +
    +
    '; + return $return; + } + + /** + * @param array $showArray + * @param array $actionInfo + * @return void + */ + public static function sort_show_array( &$show_array, $triRef ) { + // tri d'un tableau à 2 dimentions + global $tri; + $tri = $triRef; + uasort ( $show_array , function ( $a, $b ) { + global $tri; + $ltA = ( $tri[1]['rev'] == 'checked' ) ? 1 : -1 ; + $gtA = ( $tri[1]['rev'] == 'checked' ) ? -1 : 1 ; + $ltB = ( $tri[2]['rev'] == 'checked' ) ? 1 : -1 ; + $gtB = ( $tri[2]['rev'] == 'checked' ) ? -1 : 1 ; + + if ( $a[$tri[1]['val']] == $b[$tri[1]['val']] ) { + if ( !empty( $tri[2]['val'] ) ) { + if ( $a[$tri[2]['val']] == $b[$tri[2]['val']] ) return 0; + return ( $a[$tri[2]['val']] < $b[$tri[2]['val']] ) ? $ltB : $gtB; + } + return 0; + } + + return ( $a[$tri[1]['val']] < $b[$tri[1]['val']] ) ? $ltA : $gtA; + }); + } } diff --git a/includes/Utilities/MgwDataFunctions.php b/includes/Utilities/MgwDataFunctions.php index 592cebf..1c07542 100644 --- a/includes/Utilities/MgwDataFunctions.php +++ b/includes/Utilities/MgwDataFunctions.php @@ -106,12 +106,12 @@ public static function update( $table, $select, $data, $updater_id ) { } // 2. on vérifie la nouveauté des valeurs proposées - $row = self::extractResData( $res ); - $row = $row[0]; + $oldData = self::extractResData( $res ); + $oldData = $oldData[0]; $same_data = true; foreach ( $data as $field => $value ) { - if ( $row[ $table . '_' . $field ] != $value ) { + if ( $oldData[ $table . '_' . $field ] != $value ) { $same_data = false; break; } @@ -124,7 +124,7 @@ public static function update( $table, $select, $data, $updater_id ) { } // 3. on archive les données actuelles - $archive = self::archive( $table, $row, false, $updater_id ); + $archive = self::archive( $table, $oldData, false, $updater_id ); if ( ! $archive->done() ) { return $archive; } @@ -310,10 +310,10 @@ public static function select_clean( $table, $columns, $select = '', $opts = [] foreach ( $columns as $key => $value ) { if ( $value != 'archive_id' ) { if ( preg_match( '/archive_id/', $select ) < 1 ) { - $select = str_replace( $value, $prefix . '_' . $value, $select ); + $select = preg_replace( '/^' . $value . '/', $prefix . '_' . $value, $select ); } foreach ( $opts as $kkey => $vvalue ) { - $opts[$kkey] = str_replace( $value, $prefix . '_' . $value, $vvalue ); + $opts[$kkey] = preg_replace( '/^' . $value . '/', $prefix . '_' . $value, $vvalue ); } $columns[$key] = $prefix . '_' . $value; } @@ -332,4 +332,5 @@ public static function select_clean( $table, $columns, $select = '', $opts = [] return self::extractResData( $res, $table ); } + } diff --git a/includes/Utilities/PagesFunctions.php b/includes/Utilities/PagesFunctions.php index 1129006..166afc3 100644 --- a/includes/Utilities/PagesFunctions.php +++ b/includes/Utilities/PagesFunctions.php @@ -6,6 +6,8 @@ use WikiPage; use CommentStoreComment; use WikitextContent; +use MovePage; +use MediaWiki\Extension\MGWikiDev\Classes\MGWStatus as Status; /** * Ensemble de fonctions statiques sur les pages @@ -160,4 +162,102 @@ public function writeContent ( $page, $newtext, $edit_summary, $flags = 0 ) { return ( !is_null( $newRev ) && $updater->wasSuccessful() ); } + + /** + * @param string $titletext + * @param int $namespace + * @param string $wikitextContent + * @param string $summary + * @param User $user + * + * @return MGWStatus + */ + public static function newPage( $titletext, $namespace, $wikitext, $summary, $user ) { + + global $wgNamespaceAliases; + + $title = Title::newFromText( $titletext, $namespace ); + $article = WikiPage::factory( $title ); + $content = new WikitextContent( $wikitext ); + $flags = EDIT_NEW; + $status = $article->doEditContent( $content, $summary, $flags, false, $user ); + if ( !$status->isOK() ) { + return Status::newFailed( $status->getMessage() ); + } + else { + return Status::newDone( 'La page "' . array_search ( $namespace, $wgNamespaceAliases ) . + ":" . $titletext . '" a été créée.', $article->getId() ); + } + } + + /** + * @param string $oldTitletext + * @param int $oldNamespace + * @param string $newTitletext + * @param int $newNamespace + * @param string $summary + * @param User $user + * + * @return MGWStatus + */ + public static function renamePage( $oldTitletext, $oldNamespace, $newTitleText, $newNamespace, $summary, $user ) { + + $oldTitle = Title::newFromText( $oldTitletext, $oldNamespace ); + $newTitle = Title::newFromText( $newTitleText, $newNamespace ); + $movePage = new MovePage( $oldTitle, $newTitle ); + if ( $movePage->isValidMove() ) { + $movePage->move( $user, $summary, $createRedirect = true ); + return Status::newDone( 'La page a été renommée', $newTitle->getArticleID() ); + } + else { + return Status::newFailed( 'Impossible de renomer la page' ); + } + } + + /** + * @param int $page_id + * @param string $summary + * @param User $user + * + * @return MGWStatus + */ + public static function refreshPage( $page_id, $summary, $user ) { + + $title = Title::newFromID( $page_id ); + if ( is_null( $title ) ) { + return Status::newFailed( 'La page n\'existe pas.', MGW_PAGE_MISSING ); + } + $article = WikiPage::factory( $title ); + $content = $article->getContent(); // enregistrement sans modification pour actualiser les parsers + $flags = EDIT_MINOR; + $ret = $article->doEditContent( $content, $summary, $flags, false, $user ); + + if ( ! $ret->isOK() ) { + return Status::newFailed( 'La page n\'a pas pu être rafraîchie ( ' . + $ret->getMessage() .' )' ); + } + else { + return Status::newDone( 'La page a été rafraîchie' ); + } + } + + /** + * @param int $page_id + * @param string $summary + * + * @return MGWStatus + */ + public static function lightDelete( $page_id, $reason ) { + + $title = Title::newFromID( $page_id ); + $article = WikiPage::factory( $title ); + $delete = $article->doDeleteArticleReal( $reason ); + if ( ! $delete->isOK() ) { + return Status::newFailed( 'La page n\'a pas pu être supprimée ( ' . + $delete->getMessage() .' )' ); + } + else { + return Status::newDone( 'La page a été supprimée' ); + } + } } diff --git a/includes/Utilities/PhpFunctions.php b/includes/Utilities/PhpFunctions.php index bebee7f..4d17e20 100644 --- a/includes/Utilities/PhpFunctions.php +++ b/includes/Utilities/PhpFunctions.php @@ -77,6 +77,23 @@ public function recursiveArrayKeyMerge ( $needle, $array ) } } + /** + * recherche récursivement une clé, fusionne le résultat si plusieurs occurences + * @return mixed (valeur, array ou array_merge) + */ + public function recursiveArrayKeyValueCount ( $key, $value, $array ) + { + $recursive = self::recursiveIterator( $array ); + $ret = 0; + foreach ($recursive as $kkey => $vvalue) { + if ( $key === $kkey && $value == $vvalue ) { + $ret++; + } + } + return $ret; + } + + private function recursiveIterator( $array ) { $iterator = new \RecursiveArrayIterator( $array ); $recursive = new \RecursiveIteratorIterator( diff --git a/mgwiki.sql b/mgwiki.sql index 4af131c..0a860ac 100644 --- a/mgwiki.sql +++ b/mgwiki.sql @@ -17,8 +17,8 @@ CREATE TABLE IF NOT EXISTS /*_*/mgw_utilisateur ( CREATE INDEX /*i*/mgw_utilisateur_lookup ON /*_*/mgw_utilisateur (utilisateur_user_id, utilisateur_nom, utilisateur_prenom, utilisateur_level); -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_utilisateur ( - archive_id int unsigned not null auto_increment, +CREATE TABLE IF NOT EXISTS /*_*/mgw_utilisateur_archive ( + utilisateur_archive_id int unsigned not null auto_increment, utilisateur_id int unsigned not null, utilisateur_user_id int not null, utilisateur_nom varchar(64) not null, @@ -28,10 +28,10 @@ CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_utilisateur ( utilisateur_updater_id int not null, utilisateur_drop_time varbinary(14) default null, utilisateur_drop_updater_id int default null, - PRIMARY KEY (archive_id) + PRIMARY KEY (utilisateur_archive_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_archive_utilisateur_lookup ON /*_*/mgw_archive_utilisateur (utilisateur_user_id, utilisateur_nom, utilisateur_prenom, utilisateur_level); +CREATE INDEX /*i*/mgw_utilisateur_archive_lookup ON /*_*/mgw_utilisateur_archive (utilisateur_user_id, utilisateur_nom, utilisateur_prenom, utilisateur_level); CREATE TABLE IF NOT EXISTS /*_*/mgw_institution ( institution_id int unsigned auto_increment not null, @@ -42,8 +42,8 @@ CREATE TABLE IF NOT EXISTS /*_*/mgw_institution ( PRIMARY KEY (institution_id) ) /*$wgDBTableOptions*/; -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_institution ( - archive_id int unsigned not null auto_increment, +CREATE TABLE IF NOT EXISTS /*_*/mgw_institution_archive ( + institution_archive_id int unsigned not null auto_increment, institution_id int unsigned not null, institution_page_id int not null, institution_nom varchar(64) not null, @@ -51,22 +51,14 @@ CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_institution ( institution_updater_id int not null, institution_drop_time varbinary(14) default null, institution_drop_updater_id int default null, - PRIMARY KEY (archive_id) -) /*$wgDBTableOptions*/; - --- table ne nécessitant pas d'archive -CREATE TABLE IF NOT EXISTS /*_*/mgw_institution_groupe ( - institution_groupe_id int unsigned not null auto_increment, - institution_groupe_type_id int unsigned not null, - institution_groupe_institution_id int unsigned not null, - PRIMARY KEY (institution_groupe_id) + PRIMARY KEY (institution_archive_id) ) /*$wgDBTableOptions*/; CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe ( groupe_id int unsigned auto_increment not null, groupe_institution_id int unsigned not null, groupe_page_id int, - groupe_type_id int unsigned, + groupe_frame_id int unsigned, groupe_start_time varbinary(14), groupe_end_time varbinary(14), groupe_actif smallint default 1, @@ -75,79 +67,87 @@ CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe ( PRIMARY KEY (groupe_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_groupe_lookup ON /*_*/mgw_groupe (groupe_institution_id, groupe_page_id, groupe_type_id, groupe_actif); +CREATE INDEX /*i*/mgw_groupe_lookup ON /*_*/mgw_groupe (groupe_institution_id, groupe_page_id, groupe_frame_id, groupe_actif); -- pas de cas d'usage drop pour les groupes -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe ( - archive_id int unsigned not null auto_increment, +CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe_archive ( + groupe_archive_id int unsigned not null auto_increment, groupe_id int unsigned not null, groupe_institution_id int unsigned not null, groupe_page_id int, - groupe_type_id int unsigned, + groupe_frame_id int unsigned, groupe_start_time varbinary(14), groupe_end_time varbinary(14), groupe_actif smallint, groupe_update_time varbinary(14) not null, groupe_updater_id int not null, - PRIMARY KEY (archive_id) + PRIMARY KEY (groupe_archive_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_archive_groupe_lookup ON /*_*/mgw_archive_groupe (groupe_institution_id, groupe_page_id, groupe_type_id, groupe_actif); +CREATE INDEX /*i*/mgw_groupe_archive_lookup ON /*_*/mgw_groupe_archive (groupe_institution_id, groupe_page_id, groupe_frame_id, groupe_actif); -CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe_membre ( - groupe_membre_id int unsigned auto_increment not null, - groupe_membre_groupe_id int unsigned not null, - groupe_membre_user_id int not null, - groupe_membre_isadmin smallint default 0, - groupe_membre_update_time varbinary(14) not null, - groupe_membre_updater_id int not null, - PRIMARY KEY (groupe_membre_id) +CREATE TABLE IF NOT EXISTS /*_*/mgw_membre ( + membre_id int unsigned auto_increment not null, + membre_groupe_id int unsigned not null, + membre_user_id int not null, + membre_isadmin smallint default 0, + membre_update_time varbinary(14) not null, + membre_updater_id int not null, + PRIMARY KEY (membre_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_groupe_membre_lookup ON /*_*/mgw_groupe_membre (groupe_membre_groupe_id, groupe_membre_user_id, groupe_membre_isadmin); - -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe_membre ( - archive_id int unsigned not null auto_increment, - groupe_membre_id int unsigned not null, - groupe_membre_groupe_id int unsigned not null, - groupe_membre_user_id int not null, - groupe_membre_isadmin smallint, - groupe_membre_update_time varbinary(14) not null, - groupe_membre_updater_id int not null, - groupe_membre_drop_time varbinary(14) default null, - groupe_membre_drop_updater_id int default null, - PRIMARY KEY (archive_id) +CREATE INDEX /*i*/mgw_membre_lookup ON /*_*/mgw_membre (membre_groupe_id, membre_user_id, membre_isadmin); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_membre_archive ( + membre_archive_id int unsigned not null auto_increment, + membre_id int unsigned not null, + membre_groupe_id int unsigned not null, + membre_user_id int not null, + membre_isadmin smallint, + membre_update_time varbinary(14) not null, + membre_updater_id int not null, + membre_drop_time varbinary(14) default null, + membre_drop_updater_id int default null, + PRIMARY KEY (membre_archive_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_archive_groupe_membre_lookup ON /*_*/mgw_archive_groupe_membre (groupe_membre_groupe_id, groupe_membre_user_id, groupe_membre_isadmin); - -CREATE TABLE IF NOT EXISTS /*_*/mgw_groupe_type ( - groupe_type_id int unsigned auto_increment not null, - groupe_type_nom varchar(64) not null, - groupe_type_page_id int, - groupe_type_admin_level smallint not null, - groupe_type_user_level smallint not null, - groupe_type_default_duration int default null, - groupe_type_update_time varbinary(14) not null, - groupe_type_updater_id int not null, - PRIMARY KEY (groupe_type_id) +CREATE INDEX /*i*/mgw_membre_archive_lookup ON /*_*/mgw_membre_archive (membre_groupe_id, membre_user_id, membre_isadmin); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_frame ( + frame_id int unsigned auto_increment not null, + frame_nom varchar(64) not null, + frame_page_id int, + frame_admin_level smallint not null, + frame_user_level smallint not null, + frame_default_duration int default null, + frame_update_time varbinary(14) not null, + frame_updater_id int not null, + PRIMARY KEY (frame_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_groupe_type_lookup ON /*_*/mgw_groupe_type (groupe_type_nom, groupe_type_page_id, groupe_type_user_level, groupe_type_admin_level); - -CREATE TABLE IF NOT EXISTS /*_*/mgw_archive_groupe_type ( - archive_id int unsigned not null auto_increment, - groupe_type_id int unsigned not null, - groupe_type_nom varchar(64) not null, - groupe_type_page_id int, - groupe_type_admin_level smallint not null, - groupe_type_user_level smallint not null, - groupe_type_default_duration int, - groupe_type_update_time varbinary(14) not null, - groupe_type_updater_id int not null, - groupe_type_drop_time varbinary(14) default null, - groupe_type_drop_updater_id int default null, - PRIMARY KEY (archive_id) +CREATE INDEX /*i*/mgw_frame_lookup ON /*_*/mgw_frame (frame_nom, frame_page_id, frame_user_level, frame_admin_level); + +CREATE TABLE IF NOT EXISTS /*_*/mgw_frame_archive ( + frame_archive_id int unsigned not null auto_increment, + frame_id int unsigned not null, + frame_nom varchar(64) not null, + frame_page_id int, + frame_admin_level smallint not null, + frame_user_level smallint not null, + frame_default_duration int, + frame_update_time varbinary(14) not null, + frame_updater_id int not null, + frame_drop_time varbinary(14) default null, + frame_drop_updater_id int default null, + PRIMARY KEY (frame_archive_id) ) /*$wgDBTableOptions*/; -CREATE INDEX /*i*/mgw_archive_groupe_type_lookup ON /*_*/mgw_archive_groupe_type (groupe_type_nom, groupe_type_page_id, groupe_type_user_level, groupe_type_admin_level); +CREATE INDEX /*i*/mgw_frame_archive_lookup ON /*_*/mgw_frame_archive (frame_nom, frame_page_id, frame_user_level, frame_admin_level); + +-- table ne nécessitant pas d'archive +CREATE TABLE IF NOT EXISTS /*_*/mgw_instit_allow ( + instit_allow_id int unsigned not null auto_increment, + instit_allow_institution_id int unsigned not null, + instit_allow_frame_id int unsigned not null, + PRIMARY KEY (institution_groupe_id) +) /*$wgDBTableOptions*/; diff --git a/resources/ext.mgwiki-dev.css b/resources/ext.mgwiki-dev.css index b931ef4..91ba6ec 100644 --- a/resources/ext.mgwiki-dev.css +++ b/resources/ext.mgwiki-dev.css @@ -67,3 +67,13 @@ color:red; cursor:pointer; } + +.mgw-link{ + text-decoration: none; +} + +.mgw-parse-error{ + font-style:italic; + font-weight:bold; + color:red; +} diff --git a/resources/ext.mgwiki-specialadmin.css b/resources/ext.mgwiki-specialadmin.css index 8835d12..3dea3c3 100644 --- a/resources/ext.mgwiki-specialadmin.css +++ b/resources/ext.mgwiki-specialadmin.css @@ -1,3 +1,23 @@ +#content{ +} + +#mgw-admin-form{ + display:flex; + justify-content: space-around; +} + +#mgw-fieldset-tri, #mgw-fieldset-filtre{ + border:none; + font-size:small; +} + +legend{ + font-weight:bold; +} + +#mgw-admin-buttons{ + align-self: flex-end; +} .mgw-radio{ display:block; margin:auto; @@ -6,11 +26,24 @@ .mgw-action-button{ margin-left:50px; margin-bottom:20px; + background-color: #848484; + border-radius: 4px; + color: #eeeeee; + text-align: center; + font-size: 18px; + cursor: pointer; + padding-left:10px; + padding-right:10px; + + float:right; } .mgw-show-array{ + background:#f6f6f6; border: 1px solid black; - height:500px; + min-height:150px; + max-height:500px; + resize:vertical; overflow: auto; } @@ -32,3 +65,7 @@ .mgw-deleted{ background:#ffcece; } + +.mgw-history-link{ + margin-left:5px; +} From 2cd803d6ead989c2e8f118edd104c279030e66fa Mon Sep 17 00:00:00 2001 From: Looxloox Date: Thu, 17 Dec 2020 16:56:20 +0100 Subject: [PATCH 15/15] =?UTF-8?q?page=20Special:AdminGroupesTypes=20d?= =?UTF-8?q?=C3=A9sormais=20fonctionnelle.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C'est un prototype comprenant: * l'affichage et l'administration des données des tables mgw_ * parsers permettant d'inclure les données issues des tables dans les pages * l'automatisation de update/rename/delete/undelete des pages liées à la table * redirect vers le formulaire dédié lorsque l'utilisateur veut renommer la page elle-même --- includes/SpecialAdminGroupTypes.php | 88 +++++++++++++++++++++------ includes/Utilities/PagesFunctions.php | 57 +++++++++++++++-- 2 files changed, 122 insertions(+), 23 deletions(-) diff --git a/includes/SpecialAdminGroupTypes.php b/includes/SpecialAdminGroupTypes.php index a8634e8..39a5481 100644 --- a/includes/SpecialAdminGroupTypes.php +++ b/includes/SpecialAdminGroupTypes.php @@ -182,16 +182,38 @@ private function undo( $id, $archive_id ) { [ 'id', 'nom' ] ); - // on vérifie la non création de doublon + // on interdit la création de doublon foreach ( $currentNames as $row ) { - if ( strtolower( $row['nom'] ) == strtolower( $old['nom'] ) ) { + if ( $row['id'] != $old['id'] && + strtolower( $row['nom'] ) == strtolower( $old['nom'] ) ) + { return Status::newFailed( 'Le nom '.$old['nom'] . ' correspond à un autre type de groupe en cours d\'utilisation.' .' Veuillez rétablir les modifications manuellement avec un autre nom.' ); } } - // renamePage si besoin + // on vérifie l'existence de la page + $page_id = PageF::pageID( 'MGWiki:Types de groupes/' . $actual['nom'] ); + if ( $page_id < 1 ) { + $actualPage = PageF::newPage( + 'Types de groupes/' . $actual['nom'], + NS_PROJECT, + '{{Modèle:Types de groupes}}', + Msg::get( 'specialadmingrouptypes-newpage-summary' ), + $wgUser + ); + if( !$actualPage->done() ) { + return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . + $actual['nom'] . ' n\'a pas pu être créée. (' . $actualPage->mess() . ')' ); + } + $old['page_id'] = $actualPage->extra(); + } + elseif ( $old['page_id'] != $page_id ) { + $old['page_id'] != $page_id; + } + + // rename si besoin if ( $old['nom'] != $actual['nom'] ) { $rename = PageF::renamePage( 'Types de groupes/' . $actual['nom'], @@ -280,18 +302,32 @@ private function undelete( $archive_id ) { } // on re-crée la page - $newPage = PageF::newPage( - 'Types de groupes/' . $old['nom'], - NS_PROJECT, - '{{Modèle:Types de groupes}}', - Msg::get( 'specialadmingrouptypes-newpage-summary' ), - $wgUser - ); - if( !$newPage->done() ) { - return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . - $old['nom'] . ' n\'a pas pu être créée. (' . $newPage->mess() . ')' ); + if ( PageF::pageArchiveExists( 'MGWiki:Types de groupes/' . $old['nom'] ) ) { + $undelete = PageF::undeletePage( + 'MGWiki:Types de groupes/' . $old['nom'], + 'Restauration du type de groupe.', + $wgUser + ); + if( !$undelete->done() ) { + return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . + $old['nom'] . ' n\'a pas pu être restaurée.' ); + } + $old['page_id'] = $undelete->extra(); + } + else { + $newPage = PageF::newPage( + 'Types de groupes/' . $old['nom'], + NS_PROJECT, + '{{Modèle:Types de groupes}}', + Msg::get( 'specialadmingrouptypes-newpage-summary' ), + $wgUser + ); + if( !$newPage->done() ) { + return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . + $old['nom'] . ' n\'a pas pu être créée. (' . $newPage->mess() . ')' ); + } + $old['page_id'] = $newPage->extra(); } - $old['page_id'] = $newPage->extra(); // on rétablit l'ancienne version $status = DbF::insert( 'groupe_type', $old, $wgUser->getId() ); @@ -299,7 +335,6 @@ private function undelete( $archive_id ) { return $status; } - // page refresh $refresh = PageF::refreshPage( $old['page_id'], Msg::get( 'specialadmingrouptypes-update-summary' ), @@ -689,10 +724,29 @@ public static function formCallback( &$reqData, &$edit, &$show ) { $data['page_id'] = $status->extra(); } else { - - # on renomme la page si le nom est modifié $oldData = DbF::select_clean( 'groupe_type', ['id', 'nom', 'page_id' ], 'id = ' . $reqData['id'] )[0]; + # on vérifie l'existence de la page et l'actualité de la base + $page_id = PageF::pageID( 'MGWiki:Types de groupes/' . $oldData['nom'] ); + if ( $page_id < 1 ) { + $actualPage = PageF::newPage( + 'Types de groupes/' . $oldData['nom'], + NS_PROJECT, + '{{Modèle:Types de groupes}}', + Msg::get( 'specialadmingrouptypes-newpage-summary' ), + $wgUser + ); + if ( !$actualPage->done() ) { + return Status::newFailed( 'Action annulée: la page MGWiki:Types de groupes/' . + $oldData['nom'] . ' n\'a pas pu être créée. (' . $actualPage->mess() . ')' ); + } + $oldData['page_id'] = $actualPage->extra(); + } + elseif ( $oldData['page_id'] != $page_id ) { + $oldData['page_id'] = $page_id; + } + + # on renomme la page si le nom est modifié if ( $oldData['nom'] != $data['nom'] ) { $status = PageF::renamePage( 'Types de groupes/' . $oldData['nom'], diff --git a/includes/Utilities/PagesFunctions.php b/includes/Utilities/PagesFunctions.php index 166afc3..3e07995 100644 --- a/includes/Utilities/PagesFunctions.php +++ b/includes/Utilities/PagesFunctions.php @@ -14,6 +14,24 @@ */ class PagesFunctions { + /** + * @param int $page_id + * @return bool + */ + public static function pageID( $page_name ) { + $title = Title::newFromText( $page_name ); + return $title->getArticleID(); + } + + /** + * @param string $page_name + * @return bool + */ + public static function pageArchiveExists( $page_name ) { + $title = Title::newFromText( $page_name ); + return $title->isDeletedQuick(); + } + /** * @param string $pagename : titre de la page * @param int $namespace : constante de l'espace de nom de la page (ex.: 'NS_MAIN') @@ -250,14 +268,41 @@ public static function refreshPage( $page_id, $summary, $user ) { public static function lightDelete( $page_id, $reason ) { $title = Title::newFromID( $page_id ); - $article = WikiPage::factory( $title ); - $delete = $article->doDeleteArticleReal( $reason ); - if ( ! $delete->isOK() ) { - return Status::newFailed( 'La page n\'a pas pu être supprimée ( ' . - $delete->getMessage() .' )' ); + if ( !is_null( $title ) ) { + $article = WikiPage::factory( $title ); + $delete = $article->doDeleteArticleReal( $reason ); + if ( ! $delete->isOK() ) { + return Status::newFailed( 'La page n\'a pas pu être supprimée ( ' . + $delete->getMessage() .' )' ); + } + } + return Status::newDone( 'La page a été supprimée' ); + } + + + /** + * @param int $page_id + * @param string $summary + * @param User $user + * + * @return MGWStatus + */ + public static function undeletePage( $page_name, $summary, $user ) { + + $title = Title::newFromText( $page_name ); + if ( !$title ) { + return Status::newFailed( "Invalid title" ); + } + + $archive = new PageArchive( $title, \RequestContext::getMain()->getConfig() ); + $archive->undeleteAsUser( [], $user, $reason ); + $page_id = $title->getArticleID(); + + if ( $page_id < 1 ) { + return Status::newFailed( 'La page n\'a pas pu être restaurée' ); } else { - return Status::newDone( 'La page a été supprimée' ); + return Status::newDone( 'La page a été restaurée', $page_id ); } } }