]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - extensions/ConfirmEdit/maintenance/GenerateFancyCaptchas.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / extensions / ConfirmEdit / maintenance / GenerateFancyCaptchas.php
1 <?php
2 /**
3  * Generate fancy captchas using a python script and copy them into storage.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @author Aaron Schulz
22  * @ingroup Maintenance
23  */
24 if ( getenv( 'MW_INSTALL_PATH' ) ) {
25         $IP = getenv( 'MW_INSTALL_PATH' );
26 } else {
27         $IP = __DIR__ . '/../../..';
28 }
29
30 require_once "$IP/maintenance/Maintenance.php";
31
32 /**
33  * Maintenance script to generate fancy captchas using a python script and copy them into storage.
34  *
35  * @ingroup Maintenance
36  */
37 class GenerateFancyCaptchas extends Maintenance {
38         public function __construct() {
39                 parent::__construct();
40
41                 // See captcha.py for argument usage
42                 $this->addOption( "wordlist", 'A list of words', true, true );
43                 $this->addOption( "font", "The font to use", true, true );
44                 $this->addOption( "font-size", "The font size ", false, true );
45                 $this->addOption( "blacklist", "A blacklist of words that should not be used", false, true );
46                 $this->addOption( "fill", "Fill the captcha container to N files", true, true );
47                 $this->addOption( "verbose", "Show debugging information" );
48                 $this->addOption(
49                         "oldcaptcha",
50                         "Whether to use captcha-old.py which doesn't have OCR fighting improvements"
51                 );
52                 $this->addOption( "delete", "Delete the old captches" );
53                 $this->addOption( "threads", "The number of threads to use to generate the images",
54                         false, true );
55                 $this->mDescription = "Generate new fancy captchas and move them into storage";
56
57                 $this->requireExtension( "FancyCaptcha" );
58         }
59
60         public function execute() {
61                 global $wgCaptchaSecret, $wgCaptchaDirectoryLevels;
62
63                 $totalTime = -microtime( true );
64
65                 $instance = ConfirmEditHooks::getInstance();
66                 if ( !( $instance instanceof FancyCaptcha ) ) {
67                         $this->error( "\$wgCaptchaClass is not FancyCaptcha.\n", 1 );
68                 }
69                 $backend = $instance->getBackend();
70
71                 $deleteOldCaptchas = $this->getOption( 'delete' );
72
73                 $countGen = (int)$this->getOption( 'fill' );
74                 if ( !$deleteOldCaptchas ) {
75                         $countAct = $instance->getCaptchaCount();
76                         $this->output( "Current number of captchas is $countAct.\n" );
77                         $countGen -= $countAct;
78                 }
79
80                 if ( $countGen <= 0 ) {
81                         $this->output( "No need to generate anymore captchas.\n" );
82                         return;
83                 }
84
85                 $tmpDir = wfTempDir() . '/mw-fancycaptcha-' . time() . '-' . wfRandomString( 6 );
86                 if ( !wfMkdirParents( $tmpDir ) ) {
87                         $this->error( "Could not create temp directory.\n", 1 );
88                 }
89
90                 $captchaScript = 'captcha.py';
91
92                 if ( $this->hasOption( 'oldcaptcha' ) ) {
93                         $captchaScript = 'captcha-old.py';
94                 }
95
96                 $cmd = sprintf( "python %s --key %s --output %s --count %s --dirs %s",
97                         wfEscapeShellArg( dirname( __DIR__ ) . '/' . $captchaScript ),
98                         wfEscapeShellArg( $wgCaptchaSecret ),
99                         wfEscapeShellArg( $tmpDir ),
100                         wfEscapeShellArg( $countGen ),
101                         wfEscapeShellArg( $wgCaptchaDirectoryLevels )
102                 );
103                 foreach (
104                         [ 'wordlist', 'font', 'font-size', 'blacklist', 'verbose', 'threads' ] as $par
105                 ) {
106                         if ( $this->hasOption( $par ) ) {
107                                 $cmd .= " --$par " . wfEscapeShellArg( $this->getOption( $par ) );
108                         }
109                 }
110
111                 $this->output( "Generating $countGen new captchas.." );
112                 $retVal = 1;
113                 $captchaTime = -microtime( true );
114                 wfShellExec( $cmd, $retVal, [], [ 'time' => 0 ] );
115                 if ( $retVal != 0 ) {
116                         wfRecursiveRemoveDir( $tmpDir );
117                         $this->error( "Could not run generation script.\n", 1 );
118                 }
119
120                 $captchaTime += microtime( true );
121                 $this->output( " Done.\n" );
122
123                 $this->output(
124                         sprintf(
125                                 "\nGenerated %d captchas in %.1f seconds\n",
126                                 $countGen,
127                                 $captchaTime
128                         )
129                 );
130
131                 $filesToDelete = [];
132                 if ( $deleteOldCaptchas ) {
133                         $this->output( "Getting a list of old captchas to delete..." );
134                         $path = $backend->getRootStoragePath() . '/captcha-render';
135                         foreach ( $backend->getFileList( [ 'dir' => $path ] ) as $file ) {
136                                 $filesToDelete[] = [
137                                         'op' => 'delete',
138                                         'src' => $path . '/' . $file,
139                                 ];
140                         }
141                         $this->output( " Done.\n" );
142                 }
143
144                 $this->output( "Copying the new captchas to storage..." );
145
146                 $storeTime = -microtime( true );
147                 $iter = new RecursiveIteratorIterator(
148                         new RecursiveDirectoryIterator(
149                                 $tmpDir,
150                                 FilesystemIterator::SKIP_DOTS
151                         ),
152                         RecursiveIteratorIterator::LEAVES_ONLY
153                 );
154
155                 $captchasGenerated = iterator_count( $iter );
156                 $filesToStore = [];
157                 /**
158                  * @var $fileInfo SplFileInfo
159                  */
160                 foreach ( $iter as $fileInfo ) {
161                         if ( !$fileInfo->isFile() ) {
162                                 continue;
163                         }
164                         list( $salt, $hash ) = $instance->hashFromImageName( $fileInfo->getBasename() );
165                         $dest = $instance->imagePath( $salt, $hash );
166                         $backend->prepare( [ 'dir' => dirname( $dest ) ] );
167                         $filesToStore[] = [
168                                 'op' => 'store',
169                                 'src' => $fileInfo->getPathname(),
170                                 'dst' => $dest,
171                         ];
172                 }
173
174                 $ret = $backend->doQuickOperations( $filesToStore );
175
176                 $storeTime += microtime( true );
177
178                 $storeSuceeded = true;
179                 if ( $ret->isOK() ) {
180                         $this->output( " Done.\n" );
181                         $this->output(
182                                 sprintf(
183                                         "\nCopied %d captchas to storage in %.1f seconds\n",
184                                         $ret->successCount,
185                                         $storeTime
186                                 )
187                         );
188                         if ( !$ret->isGood() ) {
189                                 $this->output(
190                                         "Non fatal errors:\n" .
191                                         Status::wrap( $ret )->getWikiText( null, null, 'en' ) .
192                                         "\n"
193                                 );
194                         }
195                         if ( $ret->failCount ) {
196                                 $storeSuceeded = false;
197                                 $this->error( sprintf( "\nFailed to copy %d captchas\n", $ret->failCount ) );
198                         }
199                         if ( $ret->successCount + $ret->failCount !== $captchasGenerated ) {
200                                 $storeSuceeded = false;
201                                 $this->error(
202                                         sprintf( "Internal error: captchasGenerated: %d, successCount: %d, failCount: %d\n",
203                                                 $captchasGenerated, $ret->successCount, $ret->failCount
204                                         )
205                                 );
206                         }
207                 } else {
208                         $storeSuceeded = false;
209                         $this->output( "Errored.\n" );
210                         $this->error(
211                                 Status::wrap( $ret )->getWikiText( null, null, 'en' ) .
212                                 "\n"
213                         );
214                 }
215
216                 if ( $storeSuceeded && $deleteOldCaptchas ) {
217                         $numOriginalFiles = count( $filesToDelete );
218                         $this->output( "Deleting {$numOriginalFiles} old captchas...\n" );
219                         $deleteTime = -microtime( true );
220                         $ret = $backend->doQuickOperations( $filesToDelete );
221
222                         $deleteTime += microtime( true );
223                         if ( $ret->isOK() ) {
224                                 $this->output( "Done.\n" );
225                                 $this->output(
226                                         sprintf(
227                                                 "\nDeleted %d old captchas in %.1f seconds\n",
228                                                 $numOriginalFiles,
229                                                 $deleteTime
230                                         )
231                                 );
232                                 if ( !$ret->isGood() ) {
233                                         $this->output(
234                                                 "Non fatal errors:\n" .
235                                                 Status::wrap( $ret )->getWikiText( null, null, 'en' ) .
236                                                 "\n"
237                                         );
238                                 }
239                         } else {
240                                 $this->output( "Errored.\n" );
241                                 $this->error(
242                                         Status::wrap( $ret )->getWikiText( null, null, 'en' ) .
243                                         "\n"
244                                 );
245                         }
246
247                 }
248                 $this->output( "Removing temporary files..." );
249                 wfRecursiveRemoveDir( $tmpDir );
250                 $this->output( " Done.\n" );
251
252                 $totalTime += microtime( true );
253                 $this->output(
254                         sprintf(
255                                 "\nWhole captchas generation process took %.1f seconds\n",
256                                 $totalTime
257                         )
258                 );
259         }
260 }
261
262 $maintClass = "GenerateFancyCaptchas";
263 require_once RUN_MAINTENANCE_IF_MAIN;