MediaWiki  1.34.0
GenerateFancyCaptchas.php
Go to the documentation of this file.
1 <?php
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 
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->addDescription( "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 = [];
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  $storeSucceeded = 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  $storeSucceeded = false;
197  $this->error( sprintf( "\nFailed to copy %d captchas\n", $ret->failCount ) );
198  }
199  if ( $ret->successCount + $ret->failCount !== $captchasGenerated ) {
200  $storeSucceeded = 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  $storeSucceeded = false;
209  $this->output( "Errored.\n" );
210  $this->error(
211  Status::wrap( $ret )->getWikiText( null, null, 'en' ) .
212  "\n"
213  );
214  }
215 
216  if ( $storeSucceeded && $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::class;
263 require_once RUN_MAINTENANCE_IF_MAIN;
RUN_MAINTENANCE_IF_MAIN
const RUN_MAINTENANCE_IF_MAIN
Definition: Maintenance.php:39
wfMkdirParents
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
Definition: GlobalFunctions.php:1966
Maintenance\addDescription
addDescription( $text)
Set the description text.
Definition: Maintenance.php:348
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
Maintenance
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:82
ConfirmEditHooks\getInstance
static getInstance()
Get the global Captcha instance.
Definition: ConfirmEditHooks.php:13
Status\wrap
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:55
Maintenance\addOption
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
Definition: Maintenance.php:267
$IP
$IP
Definition: update.php:3
Maintenance\requireExtension
requireExtension( $name)
Indicate that the specified extension must be loaded before the script can run.
Definition: Maintenance.php:638
$maintClass
$maintClass
Definition: GenerateFancyCaptchas.php:262
FancyCaptcha
FancyCaptcha for displaying captchas precomputed by captcha.py.
Definition: FancyCaptcha.php:9
GenerateFancyCaptchas\execute
execute()
Do the actual work.
Definition: GenerateFancyCaptchas.php:60
GenerateFancyCaptchas
Maintenance script to generate fancy captchas using a python script and copy them into storage.
Definition: GenerateFancyCaptchas.php:37
GenerateFancyCaptchas\__construct
__construct()
Default constructor.
Definition: GenerateFancyCaptchas.php:38
wfTempDir
wfTempDir()
Tries to get the system directory for temporary files.
Definition: GlobalFunctions.php:1947
Maintenance\getOption
getOption( $name, $default=null)
Get an option, or return the default.
Definition: Maintenance.php:302
wfRecursiveRemoveDir
wfRecursiveRemoveDir( $dir)
Remove a directory and all its content.
Definition: GlobalFunctions.php:2009
$path
$path
Definition: NoLocalSettings.php:25
wfEscapeShellArg
wfEscapeShellArg(... $args)
Version of escapeshellarg() that works better on Windows.
Definition: GlobalFunctions.php:2099
Maintenance\error
error( $err, $die=0)
Throw an error to the user.
Definition: Maintenance.php:481
Maintenance\output
output( $out, $channel=null)
Throw some output to the user.
Definition: Maintenance.php:453
Maintenance\hasOption
hasOption( $name)
Checks to see if a particular option exists.
Definition: Maintenance.php:288
wfShellExec
wfShellExec( $cmd, &$retval=null, $environ=[], $limits=[], $options=[])
Execute a shell command, with time and memory limits mirrored from the PHP configuration if supported...
Definition: GlobalFunctions.php:2127
wfRandomString
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
Definition: GlobalFunctions.php:274