4
* Although marked as a stub, can work independently.
10
class NewParserTest extends MediaWikiTestCase {
11
static protected $articles = array(); // Array of test articles defined by the tests
12
/* The dataProvider is run on a different instance than the test, so it must be static
13
* When running tests from several files, all tests will see all articles.
15
static protected $backendToUse;
17
public $keepUploads = false;
18
public $runDisabled = false;
20
public $showProgress = true;
21
public $savedInitialGlobals = array();
22
public $savedWeirdGlobals = array();
23
public $savedGlobals = array();
24
public $hooks = array();
25
public $functionHooks = array();
28
public $maxFuzzTestLength = 300;
30
public $memoryLimit = 50;
32
protected $file = false;
35
global $wgContLang, $wgNamespaceProtection, $wgNamespaceAliases;
37
$wgContLang = Language::factory( 'en' );
40
if ( $this->getCliArg( 'regex=' ) ) {
41
$this->regex = $this->getCliArg( 'regex=' );
47
$this->keepUploads = $this->getCliArg( 'keep-uploads' );
49
$tmpGlobals = array();
51
$tmpGlobals['wgScript'] = '/index.php';
52
$tmpGlobals['wgScriptPath'] = '/';
53
$tmpGlobals['wgArticlePath'] = '/wiki/$1';
54
$tmpGlobals['wgStyleSheetPath'] = '/skins';
55
$tmpGlobals['wgStylePath'] = '/skins';
56
$tmpGlobals['wgThumbnailScriptPath'] = false;
57
$tmpGlobals['wgLocalFileRepo'] = array(
58
'class' => 'LocalRepo',
60
'url' => 'http://example.com/images',
62
'transformVia404' => false,
63
'backend' => 'local-backend'
65
$tmpGlobals['wgForeignFileRepos'] = array();
66
$tmpGlobals['wgEnableParserCache'] = false;
67
$tmpGlobals['wgHooks'] = $wgHooks;
68
$tmpGlobals['wgDeferredUpdateList'] = array();
69
$tmpGlobals['wgMemc'] = wfGetMainCache();
70
$tmpGlobals['messageMemc'] = wfGetMessageCacheStorage();
71
$tmpGlobals['parserMemc'] = wfGetParserCacheStorage();
73
// $tmpGlobals['wgContLang'] = new StubContLang;
74
$tmpGlobals['wgUser'] = new User;
75
$context = new RequestContext();
76
$tmpGlobals['wgLang'] = $context->getLanguage();
77
$tmpGlobals['wgOut'] = $context->getOutput();
78
$tmpGlobals['wgParser'] = new StubObject( 'wgParser', $GLOBALS['wgParserConf']['class'], array( $GLOBALS['wgParserConf'] ) );
79
$tmpGlobals['wgRequest'] = $context->getRequest();
81
if ( $GLOBALS['wgStyleDirectory'] === false ) {
82
$tmpGlobals['wgStyleDirectory'] = "$IP/skins";
86
foreach ( $tmpGlobals as $var => $val ) {
87
if ( array_key_exists( $var, $GLOBALS ) ) {
88
$this->savedInitialGlobals[$var] = $GLOBALS[$var];
91
$GLOBALS[$var] = $val;
94
$this->savedWeirdGlobals['mw_namespace_protection'] = $wgNamespaceProtection[NS_MEDIAWIKI];
95
$this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image'];
96
$this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk'];
98
$wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
99
$wgNamespaceAliases['Image'] = NS_FILE;
100
$wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
103
public function tearDown() {
104
foreach ( $this->savedInitialGlobals as $var => $val ) {
105
$GLOBALS[$var] = $val;
108
global $wgNamespaceProtection, $wgNamespaceAliases;
110
$wgNamespaceProtection[NS_MEDIAWIKI] = $this->savedWeirdGlobals['mw_namespace_protection'];
111
$wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias'];
112
$wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias'];
115
RepoGroup::destroySingleton();
116
FileBackendGroup::destroySingleton();
119
function addDBData() {
120
$this->tablesUsed[] = 'site_stats';
121
$this->tablesUsed[] = 'interwiki';
122
# disabled for performance
123
#$this->tablesUsed[] = 'image';
125
# Hack: insert a few Wikipedia in-project interwiki prefixes,
126
# for testing inter-language links
127
$this->db->insert( 'interwiki', array(
128
array( 'iw_prefix' => 'wikipedia',
129
'iw_url' => 'http://en.wikipedia.org/wiki/$1',
133
array( 'iw_prefix' => 'meatball',
134
'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
138
array( 'iw_prefix' => 'zh',
139
'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
143
array( 'iw_prefix' => 'es',
144
'iw_url' => 'http://es.wikipedia.org/wiki/$1',
148
array( 'iw_prefix' => 'fr',
149
'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
153
array( 'iw_prefix' => 'ru',
154
'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
159
* @todo Fixme! Why are we inserting duplicate data here? Shouldn't
160
* need this IGNORE or shouldn't need the insert at all.
162
), __METHOD__, array( 'IGNORE' )
166
# Update certain things in site_stats
167
$this->db->insert( 'site_stats',
168
array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ),
172
# Reinitialise the LocalisationCache to match the database state
173
Language::getLocalisationCache()->unloadAll();
175
# Clear the message cache
176
MessageCache::singleton()->clear();
178
$user = User::newFromId( 0 );
179
LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision
181
# Upload DB table entries for files.
182
# We will upload the actual files later. Note that if anything causes LocalFile::load()
183
# to be triggered before then, it will break via maybeUpgrade() setting the fileExists
184
# member to false and storing it in cache.
185
$image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
186
if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) {
187
$image->recordUpload2(
189
'Upload of some lame file',
196
'media_type' => MEDIATYPE_BITMAP,
197
'mime' => 'image/jpeg',
198
'metadata' => serialize( array() ),
199
'sha1' => wfBaseConvert( '', 16, 36, 31 ),
200
'fileExists' => true ),
201
$this->db->timestamp( '20010115123500' ), $user
205
# This image will be blacklisted in [[MediaWiki:Bad image list]]
206
$image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
207
if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) {
208
$image->recordUpload2(
217
'media_type' => MEDIATYPE_BITMAP,
218
'mime' => 'image/jpeg',
219
'metadata' => serialize( array() ),
220
'sha1' => wfBaseConvert( '', 16, 36, 31 ),
221
'fileExists' => true ),
222
$this->db->timestamp( '20010115123500' ), $user
230
//ParserTest setup/teardown functions
233
* Set up the global variables for a consistent environment for each test.
234
* Ideally this should replace the global configuration entirely.
236
protected function setupGlobals( $opts = '', $config = '' ) {
237
global $wgFileBackends;
238
# Find out values for some special options.
240
self::getOptionValue( 'language', $opts, 'en' );
242
self::getOptionValue( 'variant', $opts, false );
244
self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
245
$linkHolderBatchSize =
246
self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
248
$uploadDir = $this->getUploadDir();
249
if ( $this->getCliArg( 'use-filebackend=' ) ) {
250
if ( self::$backendToUse ) {
251
$backend = self::$backendToUse;
253
$name = $this->getCliArg( 'use-filebackend=' );
254
$useConfig = array();
255
foreach ( $wgFileBackends as $conf ) {
256
if ( $conf['name'] == $name ) {
260
$useConfig['name'] = 'local-backend'; // swap name
261
$class = $conf['class'];
262
self::$backendToUse = new $class( $useConfig );
263
$backend = self::$backendToUse;
266
$backend = new FSFileBackend( array(
267
'name' => 'local-backend',
268
'lockManager' => 'nullLockManager',
269
'containerPaths' => array(
270
'local-public' => "$uploadDir",
271
'local-thumb' => "$uploadDir/thumb",
277
'wgServer' => 'http://Britney-Spears',
278
'wgScript' => '/index.php',
279
'wgScriptPath' => '/',
280
'wgArticlePath' => '/wiki/$1',
281
'wgExtensionAssetsPath' => '/extensions',
282
'wgActionPaths' => array(),
283
'wgLocalFileRepo' => array(
284
'class' => 'LocalRepo',
286
'url' => 'http://example.com/images',
288
'transformVia404' => false,
289
'backend' => $backend
291
'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
292
'wgStylePath' => '/skins',
293
'wgStyleSheetPath' => '/skins',
294
'wgSitename' => 'MediaWiki',
295
'wgLanguageCode' => $lang,
296
'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_',
297
'wgRawHtml' => isset( $opts['rawhtml'] ),
299
'wgContLang' => null,
300
'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ),
301
'wgMaxTocLevel' => $maxtoclevel,
302
'wgCapitalLinks' => true,
303
'wgNoFollowLinks' => true,
304
'wgNoFollowDomainExceptions' => array(),
305
'wgThumbnailScriptPath' => false,
306
'wgUseImageResize' => false,
307
'wgUseTeX' => isset( $opts['math'] ),
308
'wgMathDirectory' => $uploadDir . '/math',
309
'wgLocaltimezone' => 'UTC',
310
'wgAllowExternalImages' => true,
311
'wgUseTidy' => false,
312
'wgDefaultLanguageVariant' => $variant,
313
'wgVariantArticlePath' => false,
314
'wgGroupPermissions' => array( '*' => array(
315
'createaccount' => true,
318
'createpage' => true,
319
'createtalk' => true,
321
'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ),
322
'wgDefaultExternalStore' => array(),
323
'wgForeignFileRepos' => array(),
324
'wgLinkHolderBatchSize' => $linkHolderBatchSize,
325
'wgExperimentalHtmlIds' => false,
326
'wgExternalLinkTarget' => false,
327
'wgAlwaysUseTidy' => false,
329
'wgCleanupPresentationalAttributes' => true,
330
'wgWellFormedXml' => true,
331
'wgAllowMicrodataAttributes' => true,
332
'wgAdaptiveMessageCache' => true,
333
'wgUseDatabaseMessages' => true,
337
$configLines = explode( "\n", $config );
339
foreach ( $configLines as $line ) {
340
list( $var, $value ) = explode( '=', $line, 2 );
342
$settings[$var] = eval( "return $value;" ); //???
346
$this->savedGlobals = array();
348
foreach ( $settings as $var => $val ) {
349
if ( array_key_exists( $var, $GLOBALS ) ) {
350
$this->savedGlobals[$var] = $GLOBALS[$var];
353
$GLOBALS[$var] = $val;
356
$langObj = Language::factory( $lang );
357
$GLOBALS['wgContLang'] = $langObj;
358
$context = new RequestContext();
359
$GLOBALS['wgLang'] = $context->getLanguage();
361
$GLOBALS['wgMemc'] = new EmptyBagOStuff;
362
$GLOBALS['wgOut'] = $context->getOutput();
363
$GLOBALS['wgUser'] = $context->getUser();
367
$wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
368
$wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
370
MagicWord::clearCache();
371
RepoGroup::destroySingleton();
372
FileBackendGroup::destroySingleton();
374
# Create dummy files in storage
375
$this->setupUploads();
377
# Publish the articles after we have the final language set
378
$this->publishTestArticles();
380
# The entries saved into RepoGroup cache with previous globals will be wrong.
381
RepoGroup::destroySingleton();
382
FileBackendGroup::destroySingleton();
383
MessageCache::singleton()->destroyInstance();
389
* Get an FS upload directory (only applies to FSFileBackend)
391
* @return String: the directory
393
protected function getUploadDir() {
394
if ( $this->keepUploads ) {
395
$dir = wfTempDir() . '/mwParser-images';
397
if ( is_dir( $dir ) ) {
401
$dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
404
// wfDebug( "Creating upload directory $dir\n" );
405
if ( file_exists( $dir ) ) {
406
wfDebug( "Already exists!\n" );
414
* Create a dummy uploads directory which will contain a couple
415
* of files in order to pass existence tests.
417
* @return String: the directory
419
protected function setupUploads() {
422
$base = $this->getBaseDir();
423
$backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
424
$backend->prepare( array( 'dir' => "$base/local-public/3/3a" ) );
425
$backend->store( array(
426
'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg"
428
$backend->prepare( array( 'dir' => "$base/local-public/0/09" ) );
429
$backend->store( array(
430
'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/0/09/Bad.jpg"
435
* Restore default values and perform any necessary clean-up
436
* after each test runs.
438
protected function teardownGlobals() {
439
$this->teardownUploads();
441
foreach ( $this->savedGlobals as $var => $val ) {
442
$GLOBALS[$var] = $val;
445
RepoGroup::destroySingleton();
446
LinkCache::singleton()->clear();
450
* Remove the dummy uploads directory
452
private function teardownUploads() {
453
if ( $this->keepUploads ) {
457
$base = $this->getBaseDir();
458
// delete the files first, then the dirs.
461
"$base/local-public/3/3a/Foobar.jpg",
462
"$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg",
463
"$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
464
"$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
465
"$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
467
"$base/local-public/0/09/Bad.jpg",
468
"$base/local-thumb/0/09/Bad.jpg",
470
"$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
476
* Delete the specified files, if they exist.
477
* @param $files Array: full paths to files to delete.
479
private static function deleteFiles( $files ) {
480
$backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
481
foreach ( $files as $file ) {
482
$backend->delete( array( 'src' => $file ), array( 'force' => 1 ) );
484
foreach ( $files as $file ) {
486
while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) {
487
if ( !$backend->clean( array( 'dir' => $tmp ) )->isOK() ) {
494
protected function getBaseDir() {
495
return 'mwstore://local-backend';
498
public function parserTestProvider() {
499
if ( $this->file === false ) {
500
global $wgParserTestFiles;
501
$this->file = $wgParserTestFiles[0];
503
return new TestFileIterator( $this->file, $this );
507
* Set the file from whose tests will be run by this instance
509
public function setParserTestFile( $filename ) {
510
$this->file = $filename;
515
* @dataProvider parserTestProvider
517
public function testParserTest( $desc, $input, $result, $opts, $config ) {
518
if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) {
519
$this->assertTrue( true ); // XXX: don't flood output with "test made no assertions"
520
//$this->markTestSkipped( 'Filtered out by the user' );
524
wfDebug( "Running parser test: $desc\n" );
526
$opts = $this->parseOptions( $opts );
527
$context = $this->setupGlobals( $opts, $config );
529
$user = $context->getUser();
530
$options = ParserOptions::newFromContext( $context );
532
if ( isset( $opts['title'] ) ) {
533
$titleText = $opts['title'];
536
$titleText = 'Parser test';
539
$local = isset( $opts['local'] );
540
$preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
541
$parser = $this->getParser( $preprocessor );
543
$title = Title::newFromText( $titleText );
545
if ( isset( $opts['pst'] ) ) {
546
$out = $parser->preSaveTransform( $input, $title, $user, $options );
547
} elseif ( isset( $opts['msg'] ) ) {
548
$out = $parser->transformMsg( $input, $options, $title );
549
} elseif ( isset( $opts['section'] ) ) {
550
$section = $opts['section'];
551
$out = $parser->getSection( $input, $section );
552
} elseif ( isset( $opts['replace'] ) ) {
553
$section = $opts['replace'][0];
554
$replace = $opts['replace'][1];
555
$out = $parser->replaceSection( $input, $section, $replace );
556
} elseif ( isset( $opts['comment'] ) ) {
557
$out = Linker::formatComment( $input, $title, $local );
558
} elseif ( isset( $opts['preload'] ) ) {
559
$out = $parser->getpreloadText( $input, $title, $options );
561
$output = $parser->parse( $input, $title, $options, true, true, 1337 );
562
$out = $output->getText();
564
if ( isset( $opts['showtitle'] ) ) {
565
if ( $output->getTitleText() ) {
566
$title = $output->getTitleText();
569
$out = "$title\n$out";
572
if ( isset( $opts['ill'] ) ) {
573
$out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) );
574
} elseif ( isset( $opts['cat'] ) ) {
575
$outputPage = $context->getOutput();
576
$outputPage->addCategoryLinks( $output->getCategories() );
577
$cats = $outputPage->getCategoryLinks();
579
if ( isset( $cats['normal'] ) ) {
580
$out = $this->tidy( implode( ' ', $cats['normal'] ) );
585
$parser->mPreprocessor = null;
587
$result = $this->tidy( $result );
590
$this->teardownGlobals();
592
$this->assertEquals( $result, $out, $desc );
596
* Run a fuzz test series
597
* Draw input from a set of test files
599
* @todo @fixme Needs some work to not eat memory until the world explodes
603
function testFuzzTests() {
604
global $wgParserTestFiles;
606
$files = $wgParserTestFiles;
608
if( $this->getCliArg( 'file=' ) ) {
609
$files = array( $this->getCliArg( 'file=' ) );
612
$dict = $this->getFuzzInput( $files );
613
$dictSize = strlen( $dict );
614
$logMaxLength = log( $this->maxFuzzTestLength );
616
ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
619
$opts = ParserOptions::newFromUser( $user );
620
$title = Title::makeTitle( NS_MAIN, 'Parser_test' );
626
// Generate test input
627
mt_srand( ++$this->fuzzSeed );
628
$totalLength = mt_rand( 1, $this->maxFuzzTestLength );
631
while ( strlen( $input ) < $totalLength ) {
632
$logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
633
$hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
634
$offset = mt_rand( 0, $dictSize - $hairLength );
635
$input .= substr( $dict, $offset, $hairLength );
638
$this->setupGlobals();
639
$parser = $this->getParser();
643
$parser->parse( $input, $title, $opts );
644
$this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" );
645
} catch ( Exception $exception ) {
646
$input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input );
648
$this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\nInput: $input_dump\n\nError: {$exception->getMessage()}\n\nBacktrace: {$exception->getTraceAsString()}" );
651
$this->teardownGlobals();
652
$parser->__destruct();
654
if ( $id % 100 == 0 ) {
655
$usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
656
//echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
658
$ret = "Out of memory:\n";
659
$memStats = $this->getMemoryBreakdown();
661
foreach ( $memStats as $name => $usage ) {
662
$ret .= "$name: $usage\n";
665
throw new MWException( $ret );
674
//Various getter functions
677
* Get an input dictionary from a set of parser test files
679
function getFuzzInput( $filenames ) {
682
foreach ( $filenames as $filename ) {
683
$contents = file_get_contents( $filename );
684
preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches );
686
foreach ( $matches[1] as $match ) {
687
$dict .= $match . "\n";
695
* Get a memory usage breakdown
697
function getMemoryBreakdown() {
700
foreach ( $GLOBALS as $name => $value ) {
701
$memStats['$' . $name] = strlen( serialize( $value ) );
704
$classes = get_declared_classes();
706
foreach ( $classes as $class ) {
707
$rc = new ReflectionClass( $class );
708
$props = $rc->getStaticProperties();
709
$memStats[$class] = strlen( serialize( $props ) );
710
$methods = $rc->getMethods();
712
foreach ( $methods as $method ) {
713
$memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
717
$functions = get_defined_functions();
719
foreach ( $functions['user'] as $function ) {
720
$rf = new ReflectionFunction( $function );
721
$memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
730
* Get a Parser object
732
function getParser( $preprocessor = null ) {
733
global $wgParserConf;
735
$class = $wgParserConf['class'];
736
$parser = new $class( array( 'preprocessorClass' => $preprocessor ) + $wgParserConf );
738
wfRunHooks( 'ParserTestParser', array( &$parser ) );
743
//Various action functions
745
public function addArticle( $name, $text, $line ) {
746
self::$articles[$name] = array( $text, $line );
749
public function publishTestArticles() {
750
if ( empty( self::$articles ) ) {
754
foreach ( self::$articles as $name => $info ) {
755
list( $text, $line ) = $info;
756
ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
761
* Steal a callback function from the primary parser, save it for
762
* application to our scary parser. If the hook is not installed,
763
* abort processing of this file.
765
* @param $name String
766
* @return Bool true if tag hook is present
768
public function requireHook( $name ) {
770
$wgParser->firstCallInit( ); // make sure hooks are loaded.
771
return isset( $wgParser->mTagHooks[$name] );
774
public function requireFunctionHook( $name ) {
776
$wgParser->firstCallInit( ); // make sure hooks are loaded.
777
return isset( $wgParser->mFunctionHooks[$name] );
779
//Various "cleanup" functions
782
* Run the "tidy" command on text if the $wgUseTidy
785
* @param $text String: the text to tidy
788
protected function tidy( $text ) {
792
$text = MWTidy::tidy( $text );
799
* Remove last character if it is a newline
801
public function removeEndingNewline( $s ) {
802
if ( substr( $s, -1 ) === "\n" ) {
803
return substr( $s, 0, -1 );
810
//Test options parser functions
812
protected function parseOptions( $instring ) {
818
// foo=bar,"baz quux"
842
\[\[[^]]*\]\] # Link target
850
if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
851
foreach ( $matches as $bits ) {
852
array_shift( $bits );
853
$key = strtolower( array_shift( $bits ) );
854
if ( count( $bits ) == 0 ) {
856
} elseif ( count( $bits ) == 1 ) {
857
$opts[$key] = $this->cleanupOption( array_shift( $bits ) );
860
$opts[$key] = array_map( array( $this, 'cleanupOption' ), $bits );
867
protected function cleanupOption( $opt ) {
868
if ( substr( $opt, 0, 1 ) == '"' ) {
869
return substr( $opt, 1, -1 );
872
if ( substr( $opt, 0, 2 ) == '[[' ) {
873
return substr( $opt, 2, -2 );
879
* Use a regex to find out the value of an option
880
* @param $key String: name of option val to retrieve
881
* @param $opts Options array to look in
882
* @param $default Mixed: default value returned if not found
884
protected static function getOptionValue( $key, $opts, $default ) {
885
$key = strtolower( $key );
887
if ( isset( $opts[$key] ) ) {