filter.test 86 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972
  1. <?php
  2. /**
  3. * @file
  4. * Tests for filter.module.
  5. */
  6. /**
  7. * Tests for text format and filter CRUD operations.
  8. */
  9. class FilterCRUDTestCase extends DrupalWebTestCase {
  10. public static function getInfo() {
  11. return array(
  12. 'name' => 'Filter CRUD operations',
  13. 'description' => 'Test creation, loading, updating, deleting of text formats and filters.',
  14. 'group' => 'Filter',
  15. );
  16. }
  17. function setUp() {
  18. parent::setUp('filter_test');
  19. }
  20. /**
  21. * Tests CRUD operations for text formats and filters.
  22. */
  23. function testTextFormatCRUD() {
  24. // Add a text format with minimum data only.
  25. $format = new stdClass();
  26. $format->format = 'empty_format';
  27. $format->name = 'Empty format';
  28. filter_format_save($format);
  29. $this->verifyTextFormat($format);
  30. $this->verifyFilters($format);
  31. // Add another text format specifying all possible properties.
  32. $format = new stdClass();
  33. $format->format = 'custom_format';
  34. $format->name = 'Custom format';
  35. $format->filters = array(
  36. 'filter_url' => array(
  37. 'status' => 1,
  38. 'settings' => array(
  39. 'filter_url_length' => 30,
  40. ),
  41. ),
  42. );
  43. filter_format_save($format);
  44. $this->verifyTextFormat($format);
  45. $this->verifyFilters($format);
  46. // Alter some text format properties and save again.
  47. $format->name = 'Altered format';
  48. $format->filters['filter_url']['status'] = 0;
  49. $format->filters['filter_autop']['status'] = 1;
  50. filter_format_save($format);
  51. $this->verifyTextFormat($format);
  52. $this->verifyFilters($format);
  53. // Add a uncacheable filter and save again.
  54. $format->filters['filter_test_uncacheable']['status'] = 1;
  55. filter_format_save($format);
  56. $this->verifyTextFormat($format);
  57. $this->verifyFilters($format);
  58. // Disable the text format.
  59. filter_format_disable($format);
  60. $db_format = db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format->format))->fetchObject();
  61. $this->assertFalse($db_format->status, t('Database: Disabled text format is marked as disabled.'));
  62. $formats = filter_formats();
  63. $this->assertTrue(!isset($formats[$format->format]), t('filter_formats: Disabled text format no longer exists.'));
  64. }
  65. /**
  66. * Verifies that a text format is properly stored.
  67. */
  68. function verifyTextFormat($format) {
  69. $t_args = array('%format' => $format->name);
  70. // Verify text format database record.
  71. $db_format = db_select('filter_format', 'ff')
  72. ->fields('ff')
  73. ->condition('format', $format->format)
  74. ->execute()
  75. ->fetchObject();
  76. $this->assertEqual($db_format->format, $format->format, t('Database: Proper format id for text format %format.', $t_args));
  77. $this->assertEqual($db_format->name, $format->name, t('Database: Proper title for text format %format.', $t_args));
  78. $this->assertEqual($db_format->cache, $format->cache, t('Database: Proper cache indicator for text format %format.', $t_args));
  79. $this->assertEqual($db_format->weight, $format->weight, t('Database: Proper weight for text format %format.', $t_args));
  80. // Verify filter_format_load().
  81. $filter_format = filter_format_load($format->format);
  82. $this->assertEqual($filter_format->format, $format->format, t('filter_format_load: Proper format id for text format %format.', $t_args));
  83. $this->assertEqual($filter_format->name, $format->name, t('filter_format_load: Proper title for text format %format.', $t_args));
  84. $this->assertEqual($filter_format->cache, $format->cache, t('filter_format_load: Proper cache indicator for text format %format.', $t_args));
  85. $this->assertEqual($filter_format->weight, $format->weight, t('filter_format_load: Proper weight for text format %format.', $t_args));
  86. // Verify the 'cache' text format property according to enabled filters.
  87. $filter_info = filter_get_filters();
  88. $filters = filter_list_format($filter_format->format);
  89. $cacheable = TRUE;
  90. foreach ($filters as $name => $filter) {
  91. // If this filter is not cacheable, update $cacheable accordingly, so we
  92. // can verify $format->cache after iterating over all filters.
  93. if ($filter->status && isset($filter_info[$name]['cache']) && !$filter_info[$name]['cache']) {
  94. $cacheable = FALSE;
  95. break;
  96. }
  97. }
  98. $this->assertEqual($filter_format->cache, $cacheable, t('Text format contains proper cache property.'));
  99. }
  100. /**
  101. * Verifies that filters are properly stored for a text format.
  102. */
  103. function verifyFilters($format) {
  104. // Verify filter database records.
  105. $filters = db_query("SELECT * FROM {filter} WHERE format = :format", array(':format' => $format->format))->fetchAllAssoc('name');
  106. $format_filters = $format->filters;
  107. foreach ($filters as $name => $filter) {
  108. $t_args = array('%format' => $format->name, '%filter' => $name);
  109. // Verify that filter status is properly stored.
  110. $this->assertEqual($filter->status, $format_filters[$name]['status'], t('Database: Proper status for %filter in text format %format.', $t_args));
  111. // Verify that filter settings were properly stored.
  112. $this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('Database: Proper filter settings for %filter in text format %format.', $t_args));
  113. // Verify that each filter has a module name assigned.
  114. $this->assertTrue(!empty($filter->module), t('Database: Proper module name for %filter in text format %format.', $t_args));
  115. // Remove the filter from the copy of saved $format to check whether all
  116. // filters have been processed later.
  117. unset($format_filters[$name]);
  118. }
  119. // Verify that all filters have been processed.
  120. $this->assertTrue(empty($format_filters), t('Database contains values for all filters in the saved format.'));
  121. // Verify filter_list_format().
  122. $filters = filter_list_format($format->format);
  123. $format_filters = $format->filters;
  124. foreach ($filters as $name => $filter) {
  125. $t_args = array('%format' => $format->name, '%filter' => $name);
  126. // Verify that filter status is properly stored.
  127. $this->assertEqual($filter->status, $format_filters[$name]['status'], t('filter_list_format: Proper status for %filter in text format %format.', $t_args));
  128. // Verify that filter settings were properly stored.
  129. $this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args));
  130. // Verify that each filter has a module name assigned.
  131. $this->assertTrue(!empty($filter->module), t('filter_list_format: Proper module name for %filter in text format %format.', $t_args));
  132. // Remove the filter from the copy of saved $format to check whether all
  133. // filters have been processed later.
  134. unset($format_filters[$name]);
  135. }
  136. // Verify that all filters have been processed.
  137. $this->assertTrue(empty($format_filters), t('filter_list_format: Loaded filters contain values for all filters in the saved format.'));
  138. }
  139. }
  140. /**
  141. * Tests the administrative functionality of the Filter module.
  142. */
  143. class FilterAdminTestCase extends DrupalWebTestCase {
  144. public static function getInfo() {
  145. return array(
  146. 'name' => 'Filter administration functionality',
  147. 'description' => 'Thoroughly test the administrative interface of the filter module.',
  148. 'group' => 'Filter',
  149. );
  150. }
  151. function setUp() {
  152. parent::setUp();
  153. // Create users.
  154. $filtered_html_format = filter_format_load('filtered_html');
  155. $full_html_format = filter_format_load('full_html');
  156. $this->admin_user = $this->drupalCreateUser(array(
  157. 'administer filters',
  158. filter_permission_name($filtered_html_format),
  159. filter_permission_name($full_html_format),
  160. ));
  161. $this->web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
  162. $this->drupalLogin($this->admin_user);
  163. }
  164. /**
  165. * Tests the format administration functionality.
  166. */
  167. function testFormatAdmin() {
  168. // Add text format.
  169. $this->drupalGet('admin/config/content/formats');
  170. $this->clickLink('Add text format');
  171. $format_id = drupal_strtolower($this->randomName());
  172. $name = $this->randomName();
  173. $edit = array(
  174. 'format' => $format_id,
  175. 'name' => $name,
  176. );
  177. $this->drupalPost(NULL, $edit, t('Save configuration'));
  178. // Verify default weight of the text format.
  179. $this->drupalGet('admin/config/content/formats');
  180. $this->assertFieldByName("formats[$format_id][weight]", 0, t('Text format weight was saved.'));
  181. // Change the weight of the text format.
  182. $edit = array(
  183. "formats[$format_id][weight]" => 5,
  184. );
  185. $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
  186. $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was saved.'));
  187. // Edit text format.
  188. $this->drupalGet('admin/config/content/formats');
  189. $this->assertLinkByHref('admin/config/content/formats/' . $format_id);
  190. $this->drupalGet('admin/config/content/formats/' . $format_id);
  191. $this->drupalPost(NULL, array(), t('Save configuration'));
  192. // Verify that the custom weight of the text format has been retained.
  193. $this->drupalGet('admin/config/content/formats');
  194. $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was retained.'));
  195. // Disable text format.
  196. $this->assertLinkByHref('admin/config/content/formats/' . $format_id . '/disable');
  197. $this->drupalGet('admin/config/content/formats/' . $format_id . '/disable');
  198. $this->drupalPost(NULL, array(), t('Disable'));
  199. // Verify that disabled text format no longer exists.
  200. $this->drupalGet('admin/config/content/formats/' . $format_id);
  201. $this->assertResponse(404, t('Disabled text format no longer exists.'));
  202. // Attempt to create a format of the same machine name as the disabled
  203. // format but with a different human readable name.
  204. $edit = array(
  205. 'format' => $format_id,
  206. 'name' => 'New format',
  207. );
  208. $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
  209. $this->assertText('The machine-readable name is already in use. It must be unique.');
  210. // Attempt to create a format of the same human readable name as the
  211. // disabled format but with a different machine name.
  212. $edit = array(
  213. 'format' => 'new_format',
  214. 'name' => $name,
  215. );
  216. $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
  217. $this->assertRaw(t('Text format names must be unique. A format named %name already exists.', array(
  218. '%name' => $name,
  219. )));
  220. }
  221. /**
  222. * Tests filter administration functionality.
  223. */
  224. function testFilterAdmin() {
  225. // URL filter.
  226. $first_filter = 'filter_url';
  227. // Line filter.
  228. $second_filter = 'filter_autop';
  229. $filtered = 'filtered_html';
  230. $full = 'full_html';
  231. $plain = 'plain_text';
  232. // Check that the fallback format exists and cannot be disabled.
  233. $this->assertTrue($plain == filter_fallback_format(), t('The fallback format is set to plain text.'));
  234. $this->drupalGet('admin/config/content/formats');
  235. $this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', t('Disable link for the fallback format not found.'));
  236. $this->drupalGet('admin/config/content/formats/' . $plain . '/disable');
  237. $this->assertResponse(403, t('The fallback format cannot be disabled.'));
  238. // Verify access permissions to Full HTML format.
  239. $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.'));
  240. $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.'));
  241. // Add an additional tag.
  242. $edit = array();
  243. $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
  244. $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
  245. $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Allowed HTML tag added.'));
  246. $result = db_query('SELECT * FROM {cache_filter}')->fetchObject();
  247. $this->assertFalse($result, t('Cache cleared.'));
  248. $elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array(
  249. ':first' => 'filters[' . $first_filter . '][weight]',
  250. ':second' => 'filters[' . $second_filter . '][weight]',
  251. ));
  252. $this->assertTrue(!empty($elements), t('Order confirmed in admin interface.'));
  253. // Reorder filters.
  254. $edit = array();
  255. $edit['filters[' . $second_filter . '][weight]'] = 1;
  256. $edit['filters[' . $first_filter . '][weight]'] = 2;
  257. $this->drupalPost(NULL, $edit, t('Save configuration'));
  258. $this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, t('Order saved successfully.'));
  259. $this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, t('Order saved successfully.'));
  260. $elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array(
  261. ':first' => 'filters[' . $second_filter . '][weight]',
  262. ':second' => 'filters[' . $first_filter . '][weight]',
  263. ));
  264. $this->assertTrue(!empty($elements), t('Reorder confirmed in admin interface.'));
  265. $result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered));
  266. $filters = array();
  267. foreach ($result as $filter) {
  268. if ($filter->name == $second_filter || $filter->name == $first_filter) {
  269. $filters[] = $filter;
  270. }
  271. }
  272. $this->assertTrue(($filters[0]->name == $second_filter && $filters[1]->name == $first_filter), t('Order confirmed in database.'));
  273. // Add format.
  274. $edit = array();
  275. $edit['format'] = drupal_strtolower($this->randomName());
  276. $edit['name'] = $this->randomName();
  277. $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1;
  278. $edit['filters[' . $second_filter . '][status]'] = TRUE;
  279. $edit['filters[' . $first_filter . '][status]'] = TRUE;
  280. $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
  281. $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.'));
  282. drupal_static_reset('filter_formats');
  283. $format = filter_format_load($edit['format']);
  284. $this->assertNotNull($format, t('Format found in database.'));
  285. $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', '', t('Role found.'));
  286. $this->assertFieldByName('filters[' . $second_filter . '][status]', '', t('Line break filter found.'));
  287. $this->assertFieldByName('filters[' . $first_filter . '][status]', '', t('Url filter found.'));
  288. // Disable new filter.
  289. $this->drupalPost('admin/config/content/formats/' . $format->format . '/disable', array(), t('Disable'));
  290. $this->assertRaw(t('Disabled text format %format.', array('%format' => $edit['name'])), t('Format successfully disabled.'));
  291. // Allow authenticated users on full HTML.
  292. $format = filter_format_load($full);
  293. $edit = array();
  294. $edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 0;
  295. $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1;
  296. $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
  297. $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.'));
  298. // Switch user.
  299. $this->drupalLogout();
  300. $this->drupalLogin($this->web_user);
  301. $this->drupalGet('node/add/page');
  302. $this->assertRaw('<option value="' . $full . '">Full HTML</option>', t('Full HTML filter accessible.'));
  303. // Use filtered HTML and see if it removes tags that are not allowed.
  304. $body = '<em>' . $this->randomName() . '</em>';
  305. $extra_text = 'text';
  306. $text = $body . '<random>' . $extra_text . '</random>';
  307. $edit = array();
  308. $langcode = LANGUAGE_NONE;
  309. $edit["title"] = $this->randomName();
  310. $edit["body[$langcode][0][value]"] = $text;
  311. $edit["body[$langcode][0][format]"] = $filtered;
  312. $this->drupalPost('node/add/page', $edit, t('Save'));
  313. $this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit["title"])), t('Filtered node created.'));
  314. $node = $this->drupalGetNodeByTitle($edit["title"]);
  315. $this->assertTrue($node, t('Node found in database.'));
  316. $this->drupalGet('node/' . $node->nid);
  317. $this->assertRaw($body . $extra_text, t('Filter removed invalid tag.'));
  318. // Use plain text and see if it escapes all tags, whether allowed or not.
  319. $edit = array();
  320. $edit["body[$langcode][0][format]"] = $plain;
  321. $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
  322. $this->drupalGet('node/' . $node->nid);
  323. $this->assertText(check_plain($text), t('The "Plain text" text format escapes all HTML tags.'));
  324. // Switch user.
  325. $this->drupalLogout();
  326. $this->drupalLogin($this->admin_user);
  327. // Clean up.
  328. // Allowed tags.
  329. $edit = array();
  330. $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
  331. $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
  332. $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Changes reverted.'));
  333. // Full HTML.
  334. $edit = array();
  335. $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = FALSE;
  336. $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
  337. $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.'));
  338. $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'], t('Changes reverted.'));
  339. // Filter order.
  340. $edit = array();
  341. $edit['filters[' . $second_filter . '][weight]'] = 2;
  342. $edit['filters[' . $first_filter . '][weight]'] = 1;
  343. $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
  344. $this->assertFieldByName('filters[' . $second_filter . '][weight]', $edit['filters[' . $second_filter . '][weight]'], t('Changes reverted.'));
  345. $this->assertFieldByName('filters[' . $first_filter . '][weight]', $edit['filters[' . $first_filter . '][weight]'], t('Changes reverted.'));
  346. }
  347. /**
  348. * Tests the URL filter settings form is properly validated.
  349. */
  350. function testUrlFilterAdmin() {
  351. // The form does not save with an invalid filter URL length.
  352. $edit = array(
  353. 'filters[filter_url][settings][filter_url_length]' => $this->randomName(4),
  354. );
  355. $this->drupalPost('admin/config/content/formats/filtered_html', $edit, t('Save configuration'));
  356. $this->assertNoRaw(t('The text format %format has been updated.', array('%format' => 'Filtered HTML')));
  357. }
  358. }
  359. /**
  360. * Tests the filter format access functionality in the Filter module.
  361. */
  362. class FilterFormatAccessTestCase extends DrupalWebTestCase {
  363. /**
  364. * A user with administrative permissions.
  365. *
  366. * @var object
  367. */
  368. protected $admin_user;
  369. /**
  370. * A user with 'administer filters' permission.
  371. *
  372. * @var object
  373. */
  374. protected $filter_admin_user;
  375. /**
  376. * A user with permission to create and edit own content.
  377. *
  378. * @var object
  379. */
  380. protected $web_user;
  381. /**
  382. * An object representing an allowed text format.
  383. *
  384. * @var object
  385. */
  386. protected $allowed_format;
  387. /**
  388. * An object representing a disallowed text format.
  389. *
  390. * @var object
  391. */
  392. protected $disallowed_format;
  393. public static function getInfo() {
  394. return array(
  395. 'name' => 'Filter format access',
  396. 'description' => 'Tests access to text formats.',
  397. 'group' => 'Filter',
  398. );
  399. }
  400. function setUp() {
  401. parent::setUp();
  402. // Create a user who can administer text formats, but does not have
  403. // specific permission to use any of them.
  404. $this->filter_admin_user = $this->drupalCreateUser(array(
  405. 'administer filters',
  406. 'create page content',
  407. 'edit any page content',
  408. ));
  409. // Create two text formats.
  410. $this->drupalLogin($this->filter_admin_user);
  411. $formats = array();
  412. for ($i = 0; $i < 2; $i++) {
  413. $edit = array(
  414. 'format' => drupal_strtolower($this->randomName()),
  415. 'name' => $this->randomName(),
  416. );
  417. $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
  418. $this->resetFilterCaches();
  419. $formats[] = filter_format_load($edit['format']);
  420. }
  421. list($this->allowed_format, $this->disallowed_format) = $formats;
  422. $this->drupalLogout();
  423. // Create a regular user with access to one of the formats.
  424. $this->web_user = $this->drupalCreateUser(array(
  425. 'create page content',
  426. 'edit any page content',
  427. filter_permission_name($this->allowed_format),
  428. ));
  429. // Create an administrative user who has access to use both formats.
  430. $this->admin_user = $this->drupalCreateUser(array(
  431. 'administer filters',
  432. 'create page content',
  433. 'edit any page content',
  434. filter_permission_name($this->allowed_format),
  435. filter_permission_name($this->disallowed_format),
  436. ));
  437. }
  438. /**
  439. * Tests the Filter format access permissions functionality.
  440. */
  441. function testFormatPermissions() {
  442. // Make sure that a regular user only has access to the text format they
  443. // were granted access to, as well to the fallback format.
  444. $this->assertTrue(filter_access($this->allowed_format, $this->web_user), t('A regular user has access to a text format they were granted access to.'));
  445. $this->assertFalse(filter_access($this->disallowed_format, $this->web_user), t('A regular user does not have access to a text format they were not granted access to.'));
  446. $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), t('A regular user has access to the fallback format.'));
  447. // Perform similar checks as above, but now against the entire list of
  448. // available formats for this user.
  449. $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), t('The allowed format appears in the list of available formats for a regular user.'));
  450. $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), t('The disallowed format does not appear in the list of available formats for a regular user.'));
  451. $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), t('The fallback format appears in the list of available formats for a regular user.'));
  452. // Make sure that a regular user only has permission to use the format
  453. // they were granted access to.
  454. $this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), t('A regular user has permission to use the allowed text format.'));
  455. $this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), t('A regular user does not have permission to use the disallowed text format.'));
  456. // Make sure that the allowed format appears on the node form and that
  457. // the disallowed format does not.
  458. $this->drupalLogin($this->web_user);
  459. $this->drupalGet('node/add/page');
  460. $langcode = LANGUAGE_NONE;
  461. $elements = $this->xpath('//select[@name=:name]/option', array(
  462. ':name' => "body[$langcode][0][format]",
  463. ':option' => $this->allowed_format->format,
  464. ));
  465. $options = array();
  466. foreach ($elements as $element) {
  467. $options[(string) $element['value']] = $element;
  468. }
  469. $this->assertTrue(isset($options[$this->allowed_format->format]), t('The allowed text format appears as an option when adding a new node.'));
  470. $this->assertFalse(isset($options[$this->disallowed_format->format]), t('The disallowed text format does not appear as an option when adding a new node.'));
  471. $this->assertTrue(isset($options[filter_fallback_format()]), t('The fallback format appears as an option when adding a new node.'));
  472. }
  473. /**
  474. * Tests if text format is available to a role.
  475. */
  476. function testFormatRoles() {
  477. // Get the role ID assigned to the regular user; it must be the maximum.
  478. $rid = max(array_keys($this->web_user->roles));
  479. // Check that this role appears in the list of roles that have access to an
  480. // allowed text format, but does not appear in the list of roles that have
  481. // access to a disallowed text format.
  482. $this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), t('A role which has access to a text format appears in the list of roles that have access to that format.'));
  483. $this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), t('A role which does not have access to a text format does not appear in the list of roles that have access to that format.'));
  484. // Check that the correct text format appears in the list of formats
  485. // available to that role.
  486. $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role has access to appears in the list of formats available to that role.'));
  487. $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role does not have access to does not appear in the list of formats available to that role.'));
  488. // Check that the fallback format is always allowed.
  489. $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), t('All roles have access to the fallback format.'));
  490. $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), t('The fallback format appears in the list of allowed formats for any role.'));
  491. }
  492. /**
  493. * Tests editing a page using a disallowed text format.
  494. *
  495. * Verifies that regular users and administrators are able to edit a page, but
  496. * not allowed to change the fields which use an inaccessible text format.
  497. * Also verifies that fields which use a text format that does not exist can
  498. * be edited by administrators only, but that the administrator is forced to
  499. * choose a new format before saving the page.
  500. */
  501. function testFormatWidgetPermissions() {
  502. $langcode = LANGUAGE_NONE;
  503. $title_key = "title";
  504. $body_value_key = "body[$langcode][0][value]";
  505. $body_format_key = "body[$langcode][0][format]";
  506. // Create node to edit.
  507. $this->drupalLogin($this->admin_user);
  508. $edit = array();
  509. $edit['title'] = $this->randomName(8);
  510. $edit[$body_value_key] = $this->randomName(16);
  511. $edit[$body_format_key] = $this->disallowed_format->format;
  512. $this->drupalPost('node/add/page', $edit, t('Save'));
  513. $node = $this->drupalGetNodeByTitle($edit['title']);
  514. // Try to edit with a less privileged user.
  515. $this->drupalLogin($this->web_user);
  516. $this->drupalGet('node/' . $node->nid);
  517. $this->clickLink(t('Edit'));
  518. // Verify that body field is read-only and contains replacement value.
  519. $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
  520. // Verify that title can be changed, but preview displays original body.
  521. $new_edit = array();
  522. $new_edit['title'] = $this->randomName(8);
  523. $this->drupalPost(NULL, $new_edit, t('Preview'));
  524. $this->assertText($edit[$body_value_key], t('Old body found in preview.'));
  525. // Save and verify that only the title was changed.
  526. $this->drupalPost(NULL, $new_edit, t('Save'));
  527. $this->assertNoText($edit['title'], t('Old title not found.'));
  528. $this->assertText($new_edit['title'], t('New title found.'));
  529. $this->assertText($edit[$body_value_key], t('Old body found.'));
  530. // Check that even an administrator with "administer filters" permission
  531. // cannot edit the body field if they do not have specific permission to
  532. // use its stored format. (This must be disallowed so that the
  533. // administrator is never forced to switch the text format to something
  534. // else.)
  535. $this->drupalLogin($this->filter_admin_user);
  536. $this->drupalGet('node/' . $node->nid . '/edit');
  537. $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
  538. // Disable the text format used above.
  539. filter_format_disable($this->disallowed_format);
  540. $this->resetFilterCaches();
  541. // Log back in as the less privileged user and verify that the body field
  542. // is still disabled, since the less privileged user should not be able to
  543. // edit content that does not have an assigned format.
  544. $this->drupalLogin($this->web_user);
  545. $this->drupalGet('node/' . $node->nid . '/edit');
  546. $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
  547. // Log back in as the filter administrator and verify that the body field
  548. // can be edited.
  549. $this->drupalLogin($this->filter_admin_user);
  550. $this->drupalGet('node/' . $node->nid . '/edit');
  551. $this->assertNoFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", NULL, t('Text format access denied message not found.'));
  552. $this->assertFieldByXPath("//select[@name='$body_format_key']", NULL, t('Text format selector found.'));
  553. // Verify that trying to save the node without selecting a new text format
  554. // produces an error message, and does not result in the node being saved.
  555. $old_title = $new_edit['title'];
  556. $new_title = $this->randomName(8);
  557. $edit = array('title' => $new_title);
  558. $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
  559. $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.'));
  560. $this->drupalGet('node/' . $node->nid);
  561. $this->assertText($old_title, t('Old title found.'));
  562. $this->assertNoText($new_title, t('New title not found.'));
  563. // Now select a new text format and make sure the node can be saved.
  564. $edit[$body_format_key] = filter_fallback_format();
  565. $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
  566. $this->assertUrl('node/' . $node->nid);
  567. $this->assertText($new_title, t('New title found.'));
  568. $this->assertNoText($old_title, t('Old title not found.'));
  569. // Switch the text format to a new one, then disable that format and all
  570. // other formats on the site (leaving only the fallback format).
  571. $this->drupalLogin($this->admin_user);
  572. $edit = array($body_format_key => $this->allowed_format->format);
  573. $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
  574. $this->assertUrl('node/' . $node->nid);
  575. foreach (filter_formats() as $format) {
  576. if ($format->format != filter_fallback_format()) {
  577. filter_format_disable($format);
  578. }
  579. }
  580. // Since there is now only one available text format, the widget for
  581. // selecting a text format would normally not display when the content is
  582. // edited. However, we need to verify that the filter administrator still
  583. // is forced to make a conscious choice to reassign the text to a different
  584. // format.
  585. $this->drupalLogin($this->filter_admin_user);
  586. $old_title = $new_title;
  587. $new_title = $this->randomName(8);
  588. $edit = array('title' => $new_title);
  589. $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
  590. $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.'));
  591. $this->drupalGet('node/' . $node->nid);
  592. $this->assertText($old_title, t('Old title found.'));
  593. $this->assertNoText($new_title, t('New title not found.'));
  594. $edit[$body_format_key] = filter_fallback_format();
  595. $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
  596. $this->assertUrl('node/' . $node->nid);
  597. $this->assertText($new_title, t('New title found.'));
  598. $this->assertNoText($old_title, t('Old title not found.'));
  599. }
  600. /**
  601. * Rebuilds text format and permission caches in the thread running the tests.
  602. */
  603. protected function resetFilterCaches() {
  604. filter_formats_reset();
  605. $this->checkPermissions(array(), TRUE);
  606. }
  607. }
  608. /**
  609. * Tests the default filter functionality in the Filter module.
  610. */
  611. class FilterDefaultFormatTestCase extends DrupalWebTestCase {
  612. public static function getInfo() {
  613. return array(
  614. 'name' => 'Default text format functionality',
  615. 'description' => 'Test the default text formats for different users.',
  616. 'group' => 'Filter',
  617. );
  618. }
  619. /**
  620. * Tests if the default text format is accessible to users.
  621. */
  622. function testDefaultTextFormats() {
  623. // Create two text formats, and two users. The first user has access to
  624. // both formats, but the second user only has access to the second one.
  625. $admin_user = $this->drupalCreateUser(array('administer filters'));
  626. $this->drupalLogin($admin_user);
  627. $formats = array();
  628. for ($i = 0; $i < 2; $i++) {
  629. $edit = array(
  630. 'format' => drupal_strtolower($this->randomName()),
  631. 'name' => $this->randomName(),
  632. );
  633. $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
  634. $this->resetFilterCaches();
  635. $formats[] = filter_format_load($edit['format']);
  636. }
  637. list($first_format, $second_format) = $formats;
  638. $first_user = $this->drupalCreateUser(array(filter_permission_name($first_format), filter_permission_name($second_format)));
  639. $second_user = $this->drupalCreateUser(array(filter_permission_name($second_format)));
  640. // Adjust the weights so that the first and second formats (in that order)
  641. // are the two lowest weighted formats available to any user.
  642. $minimum_weight = db_query("SELECT MIN(weight) FROM {filter_format}")->fetchField();
  643. $edit = array();
  644. $edit['formats[' . $first_format->format . '][weight]'] = $minimum_weight - 2;
  645. $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 1;
  646. $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
  647. $this->resetFilterCaches();
  648. // Check that each user's default format is the lowest weighted format that
  649. // the user has access to.
  650. $this->assertEqual(filter_default_format($first_user), $first_format->format, t("The first user's default format is the lowest weighted format that the user has access to."));
  651. $this->assertEqual(filter_default_format($second_user), $second_format->format, t("The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's."));
  652. // Reorder the two formats, and check that both users now have the same
  653. // default.
  654. $edit = array();
  655. $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3;
  656. $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
  657. $this->resetFilterCaches();
  658. $this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), t('After the formats are reordered, both users have the same default format.'));
  659. }
  660. /**
  661. * Rebuilds text format and permission caches in the thread running the tests.
  662. */
  663. protected function resetFilterCaches() {
  664. filter_formats_reset();
  665. $this->checkPermissions(array(), TRUE);
  666. }
  667. }
  668. /**
  669. * Tests the behavior of check_markup() when it is called without text format.
  670. */
  671. class FilterNoFormatTestCase extends DrupalWebTestCase {
  672. public static function getInfo() {
  673. return array(
  674. 'name' => 'Unassigned text format functionality',
  675. 'description' => 'Test the behavior of check_markup() when it is called without a text format.',
  676. 'group' => 'Filter',
  677. );
  678. }
  679. /**
  680. * Tests text without format.
  681. *
  682. * Tests if text with no format is filtered the same way as text in the
  683. * fallback format.
  684. */
  685. function testCheckMarkupNoFormat() {
  686. // Create some text. Include some HTML and line breaks, so we get a good
  687. // test of the filtering that is applied to it.
  688. $text = "<strong>" . $this->randomName(32) . "</strong>\n\n<div>" . $this->randomName(32) . "</div>";
  689. // Make sure that when this text is run through check_markup() with no text
  690. // format, it is filtered as though it is in the fallback format.
  691. $this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), t('Text with no format is filtered the same as text in the fallback format.'));
  692. }
  693. }
  694. /**
  695. * Security tests for missing/vanished text formats or filters.
  696. */
  697. class FilterSecurityTestCase extends DrupalWebTestCase {
  698. public static function getInfo() {
  699. return array(
  700. 'name' => 'Security',
  701. 'description' => 'Test the behavior of check_markup() when a filter or text format vanishes.',
  702. 'group' => 'Filter',
  703. );
  704. }
  705. function setUp() {
  706. parent::setUp('php', 'filter_test');
  707. $this->admin_user = $this->drupalCreateUser(array('administer modules', 'administer filters', 'administer site configuration'));
  708. $this->drupalLogin($this->admin_user);
  709. }
  710. /**
  711. * Tests removal of filtered content when an active filter is disabled.
  712. *
  713. * Tests that filtered content is emptied when an actively used filter module
  714. * is disabled.
  715. */
  716. function testDisableFilterModule() {
  717. // Create a new node.
  718. $node = $this->drupalCreateNode(array('promote' => 1));
  719. $body_raw = $node->body[LANGUAGE_NONE][0]['value'];
  720. $format_id = $node->body[LANGUAGE_NONE][0]['format'];
  721. $this->drupalGet('node/' . $node->nid);
  722. $this->assertText($body_raw, t('Node body found.'));
  723. // Enable the filter_test_replace filter.
  724. $edit = array(
  725. 'filters[filter_test_replace][status]' => 1,
  726. );
  727. $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration'));
  728. // Verify that filter_test_replace filter replaced the content.
  729. $this->drupalGet('node/' . $node->nid);
  730. $this->assertNoText($body_raw, t('Node body not found.'));
  731. $this->assertText('Filter: Testing filter', t('Testing filter output found.'));
  732. // Disable the text format entirely.
  733. $this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable'));
  734. // Verify that the content is empty, because the text format does not exist.
  735. $this->drupalGet('node/' . $node->nid);
  736. $this->assertNoText($body_raw, t('Node body not found.'));
  737. }
  738. }
  739. /**
  740. * Unit tests for core filters.
  741. */
  742. class FilterUnitTestCase extends DrupalUnitTestCase {
  743. public static function getInfo() {
  744. return array(
  745. 'name' => 'Filter module filters',
  746. 'description' => 'Tests Filter module filters individually.',
  747. 'group' => 'Filter',
  748. );
  749. }
  750. /**
  751. * Tests the line break filter.
  752. */
  753. function testLineBreakFilter() {
  754. // Setup dummy filter object.
  755. $filter = new stdClass();
  756. $filter->callback = '_filter_autop';
  757. // Since the line break filter naturally needs plenty of newlines in test
  758. // strings and expectations, we're using "\n" instead of regular newlines
  759. // here.
  760. $tests = array(
  761. // Single line breaks should be changed to <br /> tags, while paragraphs
  762. // separated with double line breaks should be enclosed with <p></p> tags.
  763. "aaa\nbbb\n\nccc" => array(
  764. "<p>aaa<br />\nbbb</p>\n<p>ccc</p>" => TRUE,
  765. ),
  766. // Skip contents of certain block tags entirely.
  767. "<script>aaa\nbbb\n\nccc</script>
  768. <style>aaa\nbbb\n\nccc</style>
  769. <pre>aaa\nbbb\n\nccc</pre>
  770. <object>aaa\nbbb\n\nccc</object>
  771. <iframe>aaa\nbbb\n\nccc</iframe>
  772. " => array(
  773. "<script>aaa\nbbb\n\nccc</script>" => TRUE,
  774. "<style>aaa\nbbb\n\nccc</style>" => TRUE,
  775. "<pre>aaa\nbbb\n\nccc</pre>" => TRUE,
  776. "<object>aaa\nbbb\n\nccc</object>" => TRUE,
  777. "<iframe>aaa\nbbb\n\nccc</iframe>" => TRUE,
  778. ),
  779. // Skip comments entirely.
  780. "One. <!-- comment --> Two.\n<!--\nThree.\n-->\n" => array(
  781. '<!-- comment -->' => TRUE,
  782. "<!--\nThree.\n-->" => TRUE,
  783. ),
  784. // Resulting HTML should produce matching paragraph tags.
  785. '<p><div> </div></p>' => array(
  786. "<p>\n<div> </div>\n</p>" => TRUE,
  787. ),
  788. '<div><p> </p></div>' => array(
  789. "<div>\n</div>" => TRUE,
  790. ),
  791. '<blockquote><pre>aaa</pre></blockquote>' => array(
  792. "<blockquote><pre>aaa</pre></blockquote>" => TRUE,
  793. ),
  794. "<pre>aaa\nbbb\nccc</pre>\nddd\neee" => array(
  795. "<pre>aaa\nbbb\nccc</pre>" => TRUE,
  796. "<p>ddd<br />\neee</p>" => TRUE,
  797. ),
  798. // Comments remain unchanged and subsequent lines/paragraphs are
  799. // transformed normally.
  800. "aaa<!--comment-->\n\nbbb\n\nccc\n\nddd<!--comment\nwith linebreak-->\n\neee\n\nfff" => array(
  801. "<p>aaa</p>\n<!--comment--><p>\nbbb</p>\n<p>ccc</p>\n<p>ddd</p>" => TRUE,
  802. "<!--comment\nwith linebreak--><p>\neee</p>\n<p>fff</p>" => TRUE,
  803. ),
  804. // Check that a comment in a PRE will result that the text after
  805. // the comment, but still in PRE, is not transformed.
  806. "<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>\nddd" => array(
  807. "<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>" => TRUE,
  808. ),
  809. // Bug 810824, paragraphs were appearing around iframe tags.
  810. "<iframe>aaa</iframe>\n\n" => array(
  811. "<p><iframe>aaa</iframe></p>" => FALSE,
  812. ),
  813. );
  814. $this->assertFilteredString($filter, $tests);
  815. // Very long string hitting PCRE limits.
  816. $limit = max(ini_get('pcre.backtrack_limit'), ini_get('pcre.recursion_limit'));
  817. $source = $this->randomName($limit);
  818. $result = _filter_autop($source);
  819. $success = $this->assertEqual($result, '<p>' . $source . "</p>\n", t('Line break filter can process very long strings.'));
  820. if (!$success) {
  821. $this->verbose("\n" . $source . "\n<hr />\n" . $result);
  822. }
  823. }
  824. /**
  825. * Tests limiting allowed tags and XSS prevention.
  826. *
  827. * XSS tests assume that script is disallowed by default and src is allowed
  828. * by default, but on* and style attributes are disallowed.
  829. *
  830. * Script injection vectors mostly adopted from http://ha.ckers.org/xss.html.
  831. *
  832. * Relevant CVEs:
  833. * - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973,
  834. * CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740.
  835. */
  836. function testFilterXSS() {
  837. // Tag stripping, different ways to work around removal of HTML tags.
  838. $f = filter_xss('<script>alert(0)</script>');
  839. $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- simple script without special characters.'));
  840. $f = filter_xss('<script src="http://www.example.com" />');
  841. $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- empty script with source.'));
  842. $f = filter_xss('<ScRipt sRc=http://www.example.com/>');
  843. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- varying case.'));
  844. $f = filter_xss("<script\nsrc\n=\nhttp://www.example.com/\n>");
  845. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- multiline tag.'));
  846. $f = filter_xss('<script/a src=http://www.example.com/a.js></script>');
  847. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- non whitespace character after tag name.'));
  848. $f = filter_xss('<script/src=http://www.example.com/a.js></script>');
  849. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no space between tag and attribute.'));
  850. // Null between < and tag name works at least with IE6.
  851. $f = filter_xss("<\0scr\0ipt>alert(0)</script>");
  852. $this->assertNoNormalized($f, 'ipt', t('HTML tag stripping evasion -- breaking HTML with nulls.'));
  853. $f = filter_xss("<scrscriptipt src=http://www.example.com/a.js>");
  854. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- filter just removing "script".'));
  855. $f = filter_xss('<<script>alert(0);//<</script>');
  856. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double opening brackets.'));
  857. $f = filter_xss('<script src=http://www.example.com/a.js?<b>');
  858. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing tag.'));
  859. // DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should
  860. // work consistently.
  861. $f = filter_xss('<script>>');
  862. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double closing tag.'));
  863. $f = filter_xss('<script src=//www.example.com/.a>');
  864. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no scheme or ending slash.'));
  865. $f = filter_xss('<script src=http://www.example.com/.a');
  866. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing bracket.'));
  867. $f = filter_xss('<script src=http://www.example.com/ <');
  868. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- opening instead of closing bracket.'));
  869. $f = filter_xss('<nosuchtag attribute="newScriptInjectionVector">');
  870. $this->assertNoNormalized($f, 'nosuchtag', t('HTML tag stripping evasion -- unknown tag.'));
  871. $f = filter_xss('<?xml:namespace ns="urn:schemas-microsoft-com:time">');
  872. $this->assertTrue(stripos($f, '<?xml') === FALSE, t('HTML tag stripping evasion -- starting with a question sign (processing instructions).'));
  873. $f = filter_xss('<t:set attributeName="innerHTML" to="&lt;script defer&gt;alert(0)&lt;/script&gt;">');
  874. $this->assertNoNormalized($f, 't:set', t('HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).'));
  875. $f = filter_xss('<img """><script>alert(0)</script>', array('img'));
  876. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- a malformed image tag.'));
  877. $f = filter_xss('<blockquote><script>alert(0)</script></blockquote>', array('blockquote'));
  878. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script in a blockqoute.'));
  879. $f = filter_xss("<!--[if true]><script>alert(0)</script><![endif]-->");
  880. $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script within a comment.'));
  881. // Dangerous attributes removal.
  882. $f = filter_xss('<p onmouseover="http://www.example.com/">', array('p'));
  883. $this->assertNoNormalized($f, 'onmouseover', t('HTML filter attributes removal -- events, no evasion.'));
  884. $f = filter_xss('<li style="list-style-image: url(javascript:alert(0))">', array('li'));
  885. $this->assertNoNormalized($f, 'style', t('HTML filter attributes removal -- style, no evasion.'));
  886. $f = filter_xss('<img onerror =alert(0)>', array('img'));
  887. $this->assertNoNormalized($f, 'onerror', t('HTML filter attributes removal evasion -- spaces before equals sign.'));
  888. $f = filter_xss('<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>', array('img'));
  889. $this->assertNoNormalized($f, 'onabort', t('HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.'));
  890. $f = filter_xss('<img oNmediAError=alert(0)>', array('img'));
  891. $this->assertNoNormalized($f, 'onmediaerror', t('HTML filter attributes removal evasion -- varying case.'));
  892. // Works at least with IE6.
  893. $f = filter_xss("<img o\0nfocus\0=alert(0)>", array('img'));
  894. $this->assertNoNormalized($f, 'focus', t('HTML filter attributes removal evasion -- breaking with nulls.'));
  895. // Only whitelisted scheme names allowed in attributes.
  896. $f = filter_xss('<img src="javascript:alert(0)">', array('img'));
  897. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- no evasion.'));
  898. $f = filter_xss('<img src=javascript:alert(0)>', array('img'));
  899. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no quotes.'));
  900. // A bit like CVE-2006-0070.
  901. $f = filter_xss('<img src="javascript:confirm(0)">', array('img'));
  902. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no alert ;)'));
  903. $f = filter_xss('<img src=`javascript:alert(0)`>', array('img'));
  904. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- grave accents.'));
  905. $f = filter_xss('<img dynsrc="javascript:alert(0)">', array('img'));
  906. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- rare attribute.'));
  907. $f = filter_xss('<table background="javascript:alert(0)">', array('table'));
  908. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- another tag.'));
  909. $f = filter_xss('<base href="javascript:alert(0);//">', array('base'));
  910. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- one more attribute and tag.'));
  911. $f = filter_xss('<img src="jaVaSCriPt:alert(0)">', array('img'));
  912. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- varying case.'));
  913. $f = filter_xss('<img src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#48;&#41;>', array('img'));
  914. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 decimal encoding.'));
  915. $f = filter_xss('<img src=&#00000106&#0000097&#00000118&#0000097&#00000115&#0000099&#00000114&#00000105&#00000112&#00000116&#0000058&#0000097&#00000108&#00000101&#00000114&#00000116&#0000040&#0000048&#0000041>', array('img'));
  916. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- long UTF-8 encoding.'));
  917. $f = filter_xss('<img src=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x30&#x29>', array('img'));
  918. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 hex encoding.'));
  919. $f = filter_xss("<img src=\"jav\tascript:alert(0)\">", array('img'));
  920. $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an embedded tab.'));
  921. $f = filter_xss('<img src="jav&#x09;ascript:alert(0)">', array('img'));
  922. $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded tab.'));
  923. $f = filter_xss('<img src="jav&#x000000A;ascript:alert(0)">', array('img'));
  924. $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded newline.'));
  925. // With &#xD; this test would fail, but the entity gets turned into
  926. // &amp;#xD;, so it's OK.
  927. $f = filter_xss('<img src="jav&#x0D;ascript:alert(0)">', array('img'));
  928. $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded carriage return.'));
  929. $f = filter_xss("<img src=\"\n\n\nj\na\nva\ns\ncript:alert(0)\">", array('img'));
  930. $this->assertNoNormalized($f, 'cript', t('HTML scheme clearing evasion -- broken into many lines.'));
  931. $f = filter_xss("<img src=\"jav\0a\0\0cript:alert(0)\">", array('img'));
  932. $this->assertNoNormalized($f, 'cript', t('HTML scheme clearing evasion -- embedded nulls.'));
  933. $f = filter_xss('<img src=" &#14; javascript:alert(0)">', array('img'));
  934. $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- spaces and metacharacters before scheme.'));
  935. $f = filter_xss('<img src="vbscript:msgbox(0)">', array('img'));
  936. $this->assertNoNormalized($f, 'vbscript', t('HTML scheme clearing evasion -- another scheme.'));
  937. $f = filter_xss('<img src="nosuchscheme:notice(0)">', array('img'));
  938. $this->assertNoNormalized($f, 'nosuchscheme', t('HTML scheme clearing evasion -- unknown scheme.'));
  939. // Netscape 4.x javascript entities.
  940. $f = filter_xss('<br size="&{alert(0)}">', array('br'));
  941. $this->assertNoNormalized($f, 'alert', t('Netscape 4.x javascript entities.'));
  942. // DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with
  943. // Internet Explorer 6.
  944. $f = filter_xss("<p arg=\"\xe0\">\" style=\"background-image: url(javascript:alert(0));\"\xe0<p>", array('p'));
  945. $this->assertNoNormalized($f, 'style', t('HTML filter -- invalid UTF-8.'));
  946. $f = filter_xss("\xc0aaa");
  947. $this->assertEqual($f, '', t('HTML filter -- overlong UTF-8 sequences.'));
  948. $f = filter_xss("Who&#039;s Online");
  949. $this->assertNormalized($f, "who's online", t('HTML filter -- html entity number'));
  950. $f = filter_xss("Who&amp;#039;s Online");
  951. $this->assertNormalized($f, "who&#039;s online", t('HTML filter -- encoded html entity number'));
  952. $f = filter_xss("Who&amp;amp;#039; Online");
  953. $this->assertNormalized($f, "who&amp;#039; online", t('HTML filter -- double encoded html entity number'));
  954. }
  955. /**
  956. * Tests filter settings, defaults, access restrictions and similar.
  957. *
  958. * @todo This is for functions like filter_filter and check_markup, whose
  959. * functionality is not completely focused on filtering. Some ideas:
  960. * restricting formats according to user permissions, proper cache
  961. * handling, defaults -- allowed tags/attributes/protocols.
  962. *
  963. * @todo It is possible to add script, iframe etc. to allowed tags, but this
  964. * makes HTML filter completely ineffective.
  965. *
  966. * @todo Class, id, name and xmlns should be added to disallowed attributes,
  967. * or better a whitelist approach should be used for that too.
  968. */
  969. function testHtmlFilter() {
  970. // Setup dummy filter object.
  971. $filter = new stdClass();
  972. $filter->settings = array(
  973. 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
  974. 'filter_html_help' => 1,
  975. 'filter_html_nofollow' => 0,
  976. );
  977. // HTML filter is not able to secure some tags, these should never be
  978. // allowed.
  979. $f = _filter_html('<script />', $filter);
  980. $this->assertNoNormalized($f, 'script', t('HTML filter should always remove script tags.'));
  981. $f = _filter_html('<iframe />', $filter);
  982. $this->assertNoNormalized($f, 'iframe', t('HTML filter should always remove iframe tags.'));
  983. $f = _filter_html('<object />', $filter);
  984. $this->assertNoNormalized($f, 'object', t('HTML filter should always remove object tags.'));
  985. $f = _filter_html('<style />', $filter);
  986. $this->assertNoNormalized($f, 'style', t('HTML filter should always remove style tags.'));
  987. // Some tags make CSRF attacks easier, let the user take the risk herself.
  988. $f = _filter_html('<img />', $filter);
  989. $this->assertNoNormalized($f, 'img', t('HTML filter should remove img tags on default.'));
  990. $f = _filter_html('<input />', $filter);
  991. $this->assertNoNormalized($f, 'img', t('HTML filter should remove input tags on default.'));
  992. // Filtering content of some attributes is infeasible, these shouldn't be
  993. // allowed too.
  994. $f = _filter_html('<p style="display: none;" />', $filter);
  995. $this->assertNoNormalized($f, 'style', t('HTML filter should remove style attribute on default.'));
  996. $f = _filter_html('<p onerror="alert(0);" />', $filter);
  997. $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.'));
  998. $f = _filter_html('<code onerror>&nbsp;</code>', $filter);
  999. $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove empty on* attributes on default.'));
  1000. }
  1001. /**
  1002. * Tests the spam deterrent.
  1003. */
  1004. function testNoFollowFilter() {
  1005. // Setup dummy filter object.
  1006. $filter = new stdClass();
  1007. $filter->settings = array(
  1008. 'allowed_html' => '<a>',
  1009. 'filter_html_help' => 1,
  1010. 'filter_html_nofollow' => 1,
  1011. );
  1012. // Test if the rel="nofollow" attribute is added, even if we try to prevent
  1013. // it.
  1014. $f = _filter_html('<a href="http://www.example.com/">text</a>', $filter);
  1015. $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent -- no evasion.'));
  1016. $f = _filter_html('<A href="http://www.example.com/">text</a>', $filter);
  1017. $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- capital A.'));
  1018. $f = _filter_html("<a/href=\"http://www.example.com/\">text</a>", $filter);
  1019. $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- non whitespace character after tag name.'));
  1020. $f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text</a>", $filter);
  1021. $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- some nulls.'));
  1022. $f = _filter_html('<a href="http://www.example.com/" rel="follow">text</a>', $filter);
  1023. $this->assertNoNormalized($f, 'rel="follow"', t('Spam deterrent evasion -- with rel set - rel="follow" removed.'));
  1024. $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- with rel set - rel="nofollow" added.'));
  1025. }
  1026. /**
  1027. * Tests the loose, admin HTML filter.
  1028. */
  1029. function testFilterXSSAdmin() {
  1030. // DRUPAL-SA-2008-044
  1031. $f = filter_xss_admin('<object />');
  1032. $this->assertNoNormalized($f, 'object', t('Admin HTML filter -- should not allow object tag.'));
  1033. $f = filter_xss_admin('<script />');
  1034. $this->assertNoNormalized($f, 'script', t('Admin HTML filter -- should not allow script tag.'));
  1035. $f = filter_xss_admin('<style /><iframe /><frame /><frameset /><meta /><link /><embed /><applet /><param /><layer />');
  1036. $this->assertEqual($f, '', t('Admin HTML filter -- should never allow some tags.'));
  1037. }
  1038. /**
  1039. * Tests the HTML escaping filter.
  1040. *
  1041. * check_plain() is not tested here.
  1042. */
  1043. function testHtmlEscapeFilter() {
  1044. // Setup dummy filter object.
  1045. $filter = new stdClass();
  1046. $filter->callback = '_filter_html_escape';
  1047. $tests = array(
  1048. " One. <!-- \"comment\" --> Two'.\n<p>Three.</p>\n " => array(
  1049. "One. &lt;!-- &quot;comment&quot; --&gt; Two&#039;.\n&lt;p&gt;Three.&lt;/p&gt;" => TRUE,
  1050. ' One.' => FALSE,
  1051. "</p>\n " => FALSE,
  1052. ),
  1053. );
  1054. $this->assertFilteredString($filter, $tests);
  1055. }
  1056. /**
  1057. * Tests the URL filter.
  1058. */
  1059. function testUrlFilter() {
  1060. // Setup dummy filter object.
  1061. $filter = new stdClass();
  1062. $filter->callback = '_filter_url';
  1063. $filter->settings = array(
  1064. 'filter_url_length' => 496,
  1065. );
  1066. // @todo Possible categories:
  1067. // - absolute, mail, partial
  1068. // - characters/encoding, surrounding markup, security
  1069. // Create a e-mail that is too long.
  1070. $long_email = str_repeat('a', 254) . '@example.com';
  1071. $too_long_email = str_repeat('b', 255) . '@example.com';
  1072. // Filter selection/pattern matching.
  1073. $tests = array(
  1074. // HTTP URLs.
  1075. '
  1076. http://example.com or www.example.com
  1077. ' => array(
  1078. '<a href="http://example.com">http://example.com</a>' => TRUE,
  1079. '<a href="http://www.example.com">www.example.com</a>' => TRUE,
  1080. ),
  1081. // MAILTO URLs.
  1082. '
  1083. person@example.com or mailto:person2@example.com or ' . $long_email . ' but not ' . $too_long_email . '
  1084. ' => array(
  1085. '<a href="mailto:person@example.com">person@example.com</a>' => TRUE,
  1086. '<a href="mailto:person2@example.com">mailto:person2@example.com</a>' => TRUE,
  1087. '<a href="mailto:' . $long_email . '">' . $long_email . '</a>' => TRUE,
  1088. '<a href="mailto:' . $too_long_email . '">' . $too_long_email . '</a>' => FALSE,
  1089. ),
  1090. // URI parts and special characters.
  1091. '
  1092. http://trailingslash.com/ or www.trailingslash.com/
  1093. http://host.com/some/path?query=foo&bar[baz]=beer#fragment or www.host.com/some/path?query=foo&bar[baz]=beer#fragment
  1094. http://twitter.com/#!/example/status/22376963142324226
  1095. ftp://user:pass@ftp.example.com/~home/dir1
  1096. sftp://user@nonstandardport:222/dir
  1097. ssh://192.168.0.100/srv/git/drupal.git
  1098. ' => array(
  1099. '<a href="http://trailingslash.com/">http://trailingslash.com/</a>' => TRUE,
  1100. '<a href="http://www.trailingslash.com/">www.trailingslash.com/</a>' => TRUE,
  1101. '<a href="http://host.com/some/path?query=foo&amp;bar[baz]=beer#fragment">http://host.com/some/path?query=foo&amp;bar[baz]=beer#fragment</a>' => TRUE,
  1102. '<a href="http://www.host.com/some/path?query=foo&amp;bar[baz]=beer#fragment">www.host.com/some/path?query=foo&amp;bar[baz]=beer#fragment</a>' => TRUE,
  1103. '<a href="http://twitter.com/#!/example/status/22376963142324226">http://twitter.com/#!/example/status/22376963142324226</a>' => TRUE,
  1104. '<a href="ftp://user:pass@ftp.example.com/~home/dir1">ftp://user:pass@ftp.example.com/~home/dir1</a>' => TRUE,
  1105. '<a href="sftp://user@nonstandardport:222/dir">sftp://user@nonstandardport:222/dir</a>' => TRUE,
  1106. '<a href="ssh://192.168.0.100/srv/git/drupal.git">ssh://192.168.0.100/srv/git/drupal.git</a>' => TRUE,
  1107. ),
  1108. // Encoding.
  1109. '
  1110. http://ampersand.com/?a=1&b=2
  1111. http://encoded.com/?a=1&amp;b=2
  1112. ' => array(
  1113. '<a href="http://ampersand.com/?a=1&amp;b=2">http://ampersand.com/?a=1&amp;b=2</a>' => TRUE,
  1114. '<a href="http://encoded.com/?a=1&amp;b=2">http://encoded.com/?a=1&amp;b=2</a>' => TRUE,
  1115. ),
  1116. // Domain name length.
  1117. '
  1118. www.ex.ex or www.example.example or www.toolongdomainexampledomainexampledomainexampledomainexampledomain or
  1119. me@me.tv
  1120. ' => array(
  1121. '<a href="http://www.ex.ex">www.ex.ex</a>' => TRUE,
  1122. '<a href="http://www.example.example">www.example.example</a>' => TRUE,
  1123. 'http://www.toolong' => FALSE,
  1124. '<a href="mailto:me@me.tv">me@me.tv</a>' => TRUE,
  1125. ),
  1126. // Absolute URL protocols.
  1127. // The list to test is found in the beginning of _filter_url() at
  1128. // $protocols = variable_get('filter_allowed_protocols'... (approx line 1325).
  1129. '
  1130. https://example.com,
  1131. ftp://ftp.example.com,
  1132. news://example.net,
  1133. telnet://example,
  1134. irc://example.host,
  1135. ssh://odd.geek,
  1136. sftp://secure.host?,
  1137. webcal://calendar,
  1138. rtsp://127.0.0.1,
  1139. not foo://disallowed.com.
  1140. ' => array(
  1141. 'href="https://example.com"' => TRUE,
  1142. 'href="ftp://ftp.example.com"' => TRUE,
  1143. 'href="news://example.net"' => TRUE,
  1144. 'href="telnet://example"' => TRUE,
  1145. 'href="irc://example.host"' => TRUE,
  1146. 'href="ssh://odd.geek"' => TRUE,
  1147. 'href="sftp://secure.host"' => TRUE,
  1148. 'href="webcal://calendar"' => TRUE,
  1149. 'href="rtsp://127.0.0.1"' => TRUE,
  1150. 'href="foo://disallowed.com"' => FALSE,
  1151. 'not foo://disallowed.com.' => TRUE,
  1152. ),
  1153. );
  1154. $this->assertFilteredString($filter, $tests);
  1155. // Surrounding text/punctuation.
  1156. $tests = array(
  1157. '
  1158. Partial URL with trailing period www.partial.com.
  1159. E-mail with trailing comma person@example.com,
  1160. Absolute URL with trailing question http://www.absolute.com?
  1161. Query string with trailing exclamation www.query.com/index.php?a=!
  1162. Partial URL with 3 trailing www.partial.periods...
  1163. E-mail with 3 trailing exclamations@example.com!!!
  1164. Absolute URL and query string with 2 different punctuation characters (http://www.example.com/q=abc).
  1165. ' => array(
  1166. 'period <a href="http://www.partial.com">www.partial.com</a>.' => TRUE,
  1167. 'comma <a href="mailto:person@example.com">person@example.com</a>,' => TRUE,
  1168. 'question <a href="http://www.absolute.com">http://www.absolute.com</a>?' => TRUE,
  1169. 'exclamation <a href="http://www.query.com/index.php?a=">www.query.com/index.php?a=</a>!' => TRUE,
  1170. 'trailing <a href="http://www.partial.periods">www.partial.periods</a>...' => TRUE,
  1171. 'trailing <a href="mailto:exclamations@example.com">exclamations@example.com</a>!!!' => TRUE,
  1172. 'characters (<a href="http://www.example.com/q=abc">http://www.example.com/q=abc</a>).' => TRUE,
  1173. ),
  1174. '
  1175. (www.parenthesis.com/dir?a=1&b=2#a)
  1176. ' => array(
  1177. '(<a href="http://www.parenthesis.com/dir?a=1&amp;b=2#a">www.parenthesis.com/dir?a=1&amp;b=2#a</a>)' => TRUE,
  1178. ),
  1179. );
  1180. $this->assertFilteredString($filter, $tests);
  1181. // Surrounding markup.
  1182. $tests = array(
  1183. '
  1184. <p xmlns="www.namespace.com" />
  1185. <p xmlns="http://namespace.com">
  1186. An <a href="http://example.com" title="Read more at www.example.info...">anchor</a>.
  1187. </p>
  1188. ' => array(
  1189. '<p xmlns="www.namespace.com" />' => TRUE,
  1190. '<p xmlns="http://namespace.com">' => TRUE,
  1191. 'href="http://www.namespace.com"' => FALSE,
  1192. 'href="http://namespace.com"' => FALSE,
  1193. 'An <a href="http://example.com" title="Read more at www.example.info...">anchor</a>.' => TRUE,
  1194. ),
  1195. '
  1196. Not <a href="foo">www.relative.com</a> or <a href="http://absolute.com">www.absolute.com</a>
  1197. but <strong>http://www.strong.net</strong> or <em>www.emphasis.info</em>
  1198. ' => array(
  1199. '<a href="foo">www.relative.com</a>' => TRUE,
  1200. 'href="http://www.relative.com"' => FALSE,
  1201. '<a href="http://absolute.com">www.absolute.com</a>' => TRUE,
  1202. '<strong><a href="http://www.strong.net">http://www.strong.net</a></strong>' => TRUE,
  1203. '<em><a href="http://www.emphasis.info">www.emphasis.info</a></em>' => TRUE,
  1204. ),
  1205. '
  1206. Test <code>using www.example.com the code tag</code>.
  1207. ' => array(
  1208. 'href' => FALSE,
  1209. 'http' => FALSE,
  1210. ),
  1211. '
  1212. Intro.
  1213. <blockquote>
  1214. Quoted text linking to www.example.com, written by person@example.com, originating from http://origin.example.com. <code>@see www.usage.example.com or <em>www.example.info</em> bla bla</code>.
  1215. </blockquote>
  1216. Outro.
  1217. ' => array(
  1218. 'href="http://www.example.com"' => TRUE,
  1219. 'href="mailto:person@example.com"' => TRUE,
  1220. 'href="http://origin.example.com"' => TRUE,
  1221. 'http://www.usage.example.com' => FALSE,
  1222. 'http://www.example.info' => FALSE,
  1223. 'Intro.' => TRUE,
  1224. 'Outro.' => TRUE,
  1225. ),
  1226. '
  1227. Unknown tag <x>containing x and www.example.com</x>? And a tag <pooh>beginning with p and containing www.example.pooh with p?</pooh>
  1228. ' => array(
  1229. 'href="http://www.example.com"' => TRUE,
  1230. 'href="http://www.example.pooh"' => TRUE,
  1231. ),
  1232. '
  1233. <p>Test &lt;br/&gt;: This is a www.example17.com example <strong>with</strong> various http://www.example18.com tags. *<br/>
  1234. It is important www.example19.com to *<br/>test different URLs and http://www.example20.com in the same paragraph. *<br>
  1235. HTML www.example21.com soup by person@example22.com can litererally http://www.example23.com contain *img*<img> anything. Just a www.example24.com with http://www.example25.com thrown in. www.example26.com from person@example27.com with extra http://www.example28.com.
  1236. ' => array(
  1237. 'href="http://www.example17.com"' => TRUE,
  1238. 'href="http://www.example18.com"' => TRUE,
  1239. 'href="http://www.example19.com"' => TRUE,
  1240. 'href="http://www.example20.com"' => TRUE,
  1241. 'href="http://www.example21.com"' => TRUE,
  1242. 'href="mailto:person@example22.com"' => TRUE,
  1243. 'href="http://www.example23.com"' => TRUE,
  1244. 'href="http://www.example24.com"' => TRUE,
  1245. 'href="http://www.example25.com"' => TRUE,
  1246. 'href="http://www.example26.com"' => TRUE,
  1247. 'href="mailto:person@example27.com"' => TRUE,
  1248. 'href="http://www.example28.com"' => TRUE,
  1249. ),
  1250. '
  1251. <script>
  1252. <!--
  1253. // @see www.example.com
  1254. var exampleurl = "http://example.net";
  1255. -->
  1256. <!--//--><![CDATA[//><!--
  1257. // @see www.example.com
  1258. var exampleurl = "http://example.net";
  1259. //--><!]]>
  1260. </script>
  1261. ' => array(
  1262. 'href="http://www.example.com"' => FALSE,
  1263. 'href="http://example.net"' => FALSE,
  1264. ),
  1265. '
  1266. <style>body {
  1267. background: url(http://example.com/pixel.gif);
  1268. }</style>
  1269. ' => array(
  1270. 'href' => FALSE,
  1271. ),
  1272. '
  1273. <!-- Skip any URLs like www.example.com in comments -->
  1274. ' => array(
  1275. 'href' => FALSE,
  1276. ),
  1277. '
  1278. <!-- Skip any URLs like
  1279. www.example.com with a newline in comments -->
  1280. ' => array(
  1281. 'href' => FALSE,
  1282. ),
  1283. '
  1284. <!-- Skip any URLs like www.comment.com in comments. <p>Also ignore http://commented.out/markup.</p> -->
  1285. ' => array(
  1286. 'href' => FALSE,
  1287. ),
  1288. '
  1289. <dl>
  1290. <dt>www.example.com</dt>
  1291. <dd>http://example.com</dd>
  1292. <dd>person@example.com</dd>
  1293. <dt>Check www.example.net</dt>
  1294. <dd>Some text around http://www.example.info by person@example.info?</dd>
  1295. </dl>
  1296. ' => array(
  1297. 'href="http://www.example.com"' => TRUE,
  1298. 'href="http://example.com"' => TRUE,
  1299. 'href="mailto:person@example.com"' => TRUE,
  1300. 'href="http://www.example.net"' => TRUE,
  1301. 'href="http://www.example.info"' => TRUE,
  1302. 'href="mailto:person@example.info"' => TRUE,
  1303. ),
  1304. '
  1305. <div>www.div.com</div>
  1306. <ul>
  1307. <li>http://listitem.com</li>
  1308. <li class="odd">www.class.listitem.com</li>
  1309. </ul>
  1310. ' => array(
  1311. '<div><a href="http://www.div.com">www.div.com</a></div>' => TRUE,
  1312. '<li><a href="http://listitem.com">http://listitem.com</a></li>' => TRUE,
  1313. '<li class="odd"><a href="http://www.class.listitem.com">www.class.listitem.com</a></li>' => TRUE,
  1314. ),
  1315. );
  1316. $this->assertFilteredString($filter, $tests);
  1317. // URL trimming.
  1318. $filter->settings['filter_url_length'] = 20;
  1319. $tests = array(
  1320. 'www.trimmed.com/d/ff.ext?a=1&b=2#a1' => array(
  1321. '<a href="http://www.trimmed.com/d/ff.ext?a=1&amp;b=2#a1">www.trimmed.com/d/ff...</a>' => TRUE,
  1322. ),
  1323. );
  1324. $this->assertFilteredString($filter, $tests);
  1325. }
  1326. /**
  1327. * Asserts multiple filter output expectations for multiple input strings.
  1328. *
  1329. * @param $filter
  1330. * A input filter object.
  1331. * @param $tests
  1332. * An associative array, whereas each key is an arbitrary input string and
  1333. * each value is again an associative array whose keys are filter output
  1334. * strings and whose values are Booleans indicating whether the output is
  1335. * expected or not.
  1336. *
  1337. * For example:
  1338. * @code
  1339. * $tests = array(
  1340. * 'Input string' => array(
  1341. * '<p>Input string</p>' => TRUE,
  1342. * 'Input string<br' => FALSE,
  1343. * ),
  1344. * );
  1345. * @endcode
  1346. */
  1347. function assertFilteredString($filter, $tests) {
  1348. foreach ($tests as $source => $tasks) {
  1349. $function = $filter->callback;
  1350. $result = $function($source, $filter);
  1351. foreach ($tasks as $value => $is_expected) {
  1352. // Not using assertIdentical, since combination with strpos() is hard to grok.
  1353. if ($is_expected) {
  1354. $success = $this->assertTrue(strpos($result, $value) !== FALSE, t('@source: @value found.', array(
  1355. '@source' => var_export($source, TRUE),
  1356. '@value' => var_export($value, TRUE),
  1357. )));
  1358. }
  1359. else {
  1360. $success = $this->assertTrue(strpos($result, $value) === FALSE, t('@source: @value not found.', array(
  1361. '@source' => var_export($source, TRUE),
  1362. '@value' => var_export($value, TRUE),
  1363. )));
  1364. }
  1365. if (!$success) {
  1366. $this->verbose('Source:<pre>' . check_plain(var_export($source, TRUE)) . '</pre>'
  1367. . '<hr />' . 'Result:<pre>' . check_plain(var_export($result, TRUE)) . '</pre>'
  1368. . '<hr />' . ($is_expected ? 'Expected:' : 'Not expected:')
  1369. . '<pre>' . check_plain(var_export($value, TRUE)) . '</pre>'
  1370. );
  1371. }
  1372. }
  1373. }
  1374. }
  1375. /**
  1376. * Tests URL filter on longer content.
  1377. *
  1378. * Filters based on regular expressions should also be tested with a more
  1379. * complex content than just isolated test lines.
  1380. * The most common errors are:
  1381. * - accidental '*' (greedy) match instead of '*?' (minimal) match.
  1382. * - only matching first occurrence instead of all.
  1383. * - newlines not matching '.*'.
  1384. *
  1385. * This test covers:
  1386. * - Document with multiple newlines and paragraphs (two newlines).
  1387. * - Mix of several HTML tags, invalid non-HTML tags, tags to ignore and HTML
  1388. * comments.
  1389. * - Empty HTML tags (BR, IMG).
  1390. * - Mix of absolute and partial URLs, and e-mail addresses in one content.
  1391. */
  1392. function testUrlFilterContent() {
  1393. // Setup dummy filter object.
  1394. $filter = new stdClass();
  1395. $filter->settings = array(
  1396. 'filter_url_length' => 496,
  1397. );
  1398. $path = drupal_get_path('module', 'filter') . '/tests';
  1399. $input = file_get_contents($path . '/filter.url-input.txt');
  1400. $expected = file_get_contents($path . '/filter.url-output.txt');
  1401. $result = _filter_url($input, $filter);
  1402. $this->assertIdentical($result, $expected, 'Complex HTML document was correctly processed.');
  1403. }
  1404. /**
  1405. * Tests the HTML corrector filter.
  1406. *
  1407. * @todo This test could really use some validity checking function.
  1408. */
  1409. function testHtmlCorrectorFilter() {
  1410. // Tag closing.
  1411. $f = _filter_htmlcorrector('<p>text');
  1412. $this->assertEqual($f, '<p>text</p>', t('HTML corrector -- tag closing at the end of input.'));
  1413. $f = _filter_htmlcorrector('<p>text<p><p>text');
  1414. $this->assertEqual($f, '<p>text</p><p></p><p>text</p>', t('HTML corrector -- tag closing.'));
  1415. $f = _filter_htmlcorrector("<ul><li>e1<li>e2");
  1416. $this->assertEqual($f, "<ul><li>e1</li><li>e2</li></ul>", t('HTML corrector -- unclosed list tags.'));
  1417. $f = _filter_htmlcorrector('<div id="d">content');
  1418. $this->assertEqual($f, '<div id="d">content</div>', t('HTML corrector -- unclosed tag with attribute.'));
  1419. // XHTML slash for empty elements.
  1420. $f = _filter_htmlcorrector('<hr><br>');
  1421. $this->assertEqual($f, '<hr /><br />', t('HTML corrector -- XHTML closing slash.'));
  1422. $f = _filter_htmlcorrector('<P>test</P>');
  1423. $this->assertEqual($f, '<p>test</p>', t('HTML corrector -- Convert uppercased tags to proper lowercased ones.'));
  1424. $f = _filter_htmlcorrector('<P>test</p>');
  1425. $this->assertEqual($f, '<p>test</p>', t('HTML corrector -- Convert uppercased tags to proper lowercased ones.'));
  1426. $f = _filter_htmlcorrector('test<hr />');
  1427. $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through.'));
  1428. $f = _filter_htmlcorrector('test<hr/>');
  1429. $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through, but ensure there is a single space before the closing slash.'));
  1430. $f = _filter_htmlcorrector('test<hr />');
  1431. $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through, but ensure there are not too many spaces before the closing slash.'));
  1432. $f = _filter_htmlcorrector('<span class="test" />');
  1433. $this->assertEqual($f, '<span class="test"></span>', t('HTML corrector -- Convert XHTML that is properly formed but that would not be compatible with typical HTML user agents.'));
  1434. $f = _filter_htmlcorrector('test1<br class="test">test2');
  1435. $this->assertEqual($f, 'test1<br class="test" />test2', t('HTML corrector -- Automatically close single tags.'));
  1436. $f = _filter_htmlcorrector('line1<hr>line2');
  1437. $this->assertEqual($f, 'line1<hr />line2', t('HTML corrector -- Automatically close single tags.'));
  1438. $f = _filter_htmlcorrector('line1<HR>line2');
  1439. $this->assertEqual($f, 'line1<hr />line2', t('HTML corrector -- Automatically close single tags.'));
  1440. $f = _filter_htmlcorrector('<img src="http://example.com/test.jpg">test</img>');
  1441. $this->assertEqual($f, '<img src="http://example.com/test.jpg" />test', t('HTML corrector -- Automatically close single tags.'));
  1442. $f = _filter_htmlcorrector('<br></br>');
  1443. $this->assertEqual($f, '<br />', t("HTML corrector -- Transform empty tags to a single closed tag if the tag's content model is EMPTY."));
  1444. $f = _filter_htmlcorrector('<div></div>');
  1445. $this->assertEqual($f, '<div></div>', t("HTML corrector -- Do not transform empty tags to a single closed tag if the tag's content model is not EMPTY."));
  1446. $f = _filter_htmlcorrector('<p>line1<br/><hr/>line2</p>');
  1447. $this->assertEqual($f, '<p>line1<br /></p><hr />line2', t('HTML corrector -- Move non-inline elements outside of inline containers.'));
  1448. $f = _filter_htmlcorrector('<p>line1<div>line2</div></p>');
  1449. $this->assertEqual($f, '<p>line1</p><div>line2</div>', t('HTML corrector -- Move non-inline elements outside of inline containers.'));
  1450. $f = _filter_htmlcorrector('<p>test<p>test</p>\n');
  1451. $this->assertEqual($f, '<p>test</p><p>test</p>\n', t('HTML corrector -- Auto-close improperly nested tags.'));
  1452. $f = _filter_htmlcorrector('<p>Line1<br><STRONG>bold stuff</b>');
  1453. $this->assertEqual($f, '<p>Line1<br /><strong>bold stuff</strong></p>', t('HTML corrector -- Properly close unclosed tags, and remove useless closing tags.'));
  1454. $f = _filter_htmlcorrector('test <!-- this is a comment -->');
  1455. $this->assertEqual($f, 'test <!-- this is a comment -->', t('HTML corrector -- Do not touch HTML comments.'));
  1456. $f = _filter_htmlcorrector('test <!--this is a comment-->');
  1457. $this->assertEqual($f, 'test <!--this is a comment-->', t('HTML corrector -- Do not touch HTML comments.'));
  1458. $f = _filter_htmlcorrector('test <!-- comment <p>another
  1459. <strong>multiple</strong> line
  1460. comment</p> -->');
  1461. $this->assertEqual($f, 'test <!-- comment <p>another
  1462. <strong>multiple</strong> line
  1463. comment</p> -->', t('HTML corrector -- Do not touch HTML comments.'));
  1464. $f = _filter_htmlcorrector('test <!-- comment <p>another comment</p> -->');
  1465. $this->assertEqual($f, 'test <!-- comment <p>another comment</p> -->', t('HTML corrector -- Do not touch HTML comments.'));
  1466. $f = _filter_htmlcorrector('test <!--break-->');
  1467. $this->assertEqual($f, 'test <!--break-->', t('HTML corrector -- Do not touch HTML comments.'));
  1468. $f = _filter_htmlcorrector('<p>test\n</p>\n');
  1469. $this->assertEqual($f, '<p>test\n</p>\n', t('HTML corrector -- New-lines are accepted and kept as-is.'));
  1470. $f = _filter_htmlcorrector('<p>دروبال');
  1471. $this->assertEqual($f, '<p>دروبال</p>', t('HTML corrector -- Encoding is correctly kept.'));
  1472. $f = _filter_htmlcorrector('<script type="text/javascript">alert("test")</script>');
  1473. $this->assertEqual($f, '<script type="text/javascript">
  1474. <!--//--><![CDATA[// ><!--
  1475. alert("test")
  1476. //--><!]]>
  1477. </script>', t('HTML corrector -- CDATA added to script element'));
  1478. $f = _filter_htmlcorrector('<p><script type="text/javascript">alert("test")</script></p>');
  1479. $this->assertEqual($f, '<p><script type="text/javascript">
  1480. <!--//--><![CDATA[// ><!--
  1481. alert("test")
  1482. //--><!]]>
  1483. </script></p>', t('HTML corrector -- CDATA added to a nested script element'));
  1484. $f = _filter_htmlcorrector('<p><style> /* Styling */ body {color:red}</style></p>');
  1485. $this->assertEqual($f, '<p><style>
  1486. <!--/*--><![CDATA[/* ><!--*/
  1487. /* Styling */ body {color:red}
  1488. /*--><!]]>*/
  1489. </style></p>', t('HTML corrector -- CDATA added to a style element.'));
  1490. $filtered_data = _filter_htmlcorrector('<p><style>
  1491. /*<![CDATA[*/
  1492. /* Styling */
  1493. body {color:red}
  1494. /*]]>*/
  1495. </style></p>');
  1496. $this->assertEqual($filtered_data, '<p><style>
  1497. <!--/*--><![CDATA[/* ><!--*/
  1498. /*<![CDATA[*/
  1499. /* Styling */
  1500. body {color:red}
  1501. /*]]]]><![CDATA[>*/
  1502. /*--><!]]>*/
  1503. </style></p>',
  1504. t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '/*<![CDATA[*/'))
  1505. );
  1506. $filtered_data = _filter_htmlcorrector('<p><style>
  1507. <!--/*--><![CDATA[/* ><!--*/
  1508. /* Styling */
  1509. body {color:red}
  1510. /*--><!]]>*/
  1511. </style></p>');
  1512. $this->assertEqual($filtered_data, '<p><style>
  1513. <!--/*--><![CDATA[/* ><!--*/
  1514. <!--/*--><![CDATA[/* ><!--*/
  1515. /* Styling */
  1516. body {color:red}
  1517. /*--><!]]]]><![CDATA[>*/
  1518. /*--><!]]>*/
  1519. </style></p>',
  1520. t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '<!--/*--><![CDATA[/* ><!--*/'))
  1521. );
  1522. $filtered_data = _filter_htmlcorrector('<p><script type="text/javascript">
  1523. <!--//--><![CDATA[// ><!--
  1524. alert("test");
  1525. //--><!]]>
  1526. </script></p>');
  1527. $this->assertEqual($filtered_data, '<p><script type="text/javascript">
  1528. <!--//--><![CDATA[// ><!--
  1529. <!--//--><![CDATA[// ><!--
  1530. alert("test");
  1531. //--><!]]]]><![CDATA[>
  1532. //--><!]]>
  1533. </script></p>',
  1534. t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '<!--//--><![CDATA[// ><!--'))
  1535. );
  1536. $filtered_data = _filter_htmlcorrector('<p><script type="text/javascript">
  1537. // <![CDATA[
  1538. alert("test");
  1539. // ]]>
  1540. </script></p>');
  1541. $this->assertEqual($filtered_data, '<p><script type="text/javascript">
  1542. <!--//--><![CDATA[// ><!--
  1543. // <![CDATA[
  1544. alert("test");
  1545. // ]]]]><![CDATA[>
  1546. //--><!]]>
  1547. </script></p>',
  1548. t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '// <![CDATA['))
  1549. );
  1550. }
  1551. /**
  1552. * Asserts that a text transformed to lowercase with HTML entities decoded does contains a given string.
  1553. *
  1554. * Otherwise fails the test with a given message, similar to all the
  1555. * SimpleTest assert* functions.
  1556. *
  1557. * Note that this does not remove nulls, new lines and other characters that
  1558. * could be used to obscure a tag or an attribute name.
  1559. *
  1560. * @param $haystack
  1561. * Text to look in.
  1562. * @param $needle
  1563. * Lowercase, plain text to look for.
  1564. * @param $message
  1565. * (optional) Message to display if failed. Defaults to an empty string.
  1566. * @param $group
  1567. * (optional) The group this message belongs to. Defaults to 'Other'.
  1568. * @return
  1569. * TRUE on pass, FALSE on fail.
  1570. */
  1571. function assertNormalized($haystack, $needle, $message = '', $group = 'Other') {
  1572. return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) !== FALSE, $message, $group);
  1573. }
  1574. /**
  1575. * Asserts that text transformed to lowercase with HTML entities decoded does not contain a given string.
  1576. *
  1577. * Otherwise fails the test with a given message, similar to all the
  1578. * SimpleTest assert* functions.
  1579. *
  1580. * Note that this does not remove nulls, new lines, and other character that
  1581. * could be used to obscure a tag or an attribute name.
  1582. *
  1583. * @param $haystack
  1584. * Text to look in.
  1585. * @param $needle
  1586. * Lowercase, plain text to look for.
  1587. * @param $message
  1588. * (optional) Message to display if failed. Defaults to an empty string.
  1589. * @param $group
  1590. * (optional) The group this message belongs to. Defaults to 'Other'.
  1591. * @return
  1592. * TRUE on pass, FALSE on fail.
  1593. */
  1594. function assertNoNormalized($haystack, $needle, $message = '', $group = 'Other') {
  1595. return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) === FALSE, $message, $group);
  1596. }
  1597. }
  1598. /**
  1599. * Tests for Filter's hook invocations.
  1600. */
  1601. class FilterHooksTestCase extends DrupalWebTestCase {
  1602. public static function getInfo() {
  1603. return array(
  1604. 'name' => 'Filter format hooks',
  1605. 'description' => 'Test hooks for text formats insert/update/disable.',
  1606. 'group' => 'Filter',
  1607. );
  1608. }
  1609. function setUp() {
  1610. parent::setUp('block', 'filter_test');
  1611. $admin_user = $this->drupalCreateUser(array('administer filters', 'administer blocks'));
  1612. $this->drupalLogin($admin_user);
  1613. }
  1614. /**
  1615. * Tests hooks on format management.
  1616. *
  1617. * Tests that hooks run correctly on creating, editing, and deleting a text
  1618. * format.
  1619. */
  1620. function testFilterHooks() {
  1621. // Add a text format.
  1622. $name = $this->randomName();
  1623. $edit = array();
  1624. $edit['format'] = drupal_strtolower($this->randomName());
  1625. $edit['name'] = $name;
  1626. $edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 1;
  1627. $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
  1628. $this->assertRaw(t('Added text format %format.', array('%format' => $name)), t('New format created.'));
  1629. $this->assertText('hook_filter_format_insert invoked.', t('hook_filter_format_insert was invoked.'));
  1630. $format_id = $edit['format'];
  1631. // Update text format.
  1632. $edit = array();
  1633. $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1;
  1634. $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration'));
  1635. $this->assertRaw(t('The text format %format has been updated.', array('%format' => $name)), t('Format successfully updated.'));
  1636. $this->assertText('hook_filter_format_update invoked.', t('hook_filter_format_update() was invoked.'));
  1637. // Add a new custom block.
  1638. $custom_block = array();
  1639. $custom_block['info'] = $this->randomName(8);
  1640. $custom_block['title'] = $this->randomName(8);
  1641. $custom_block['body[value]'] = $this->randomName(32);
  1642. // Use the format created.
  1643. $custom_block['body[format]'] = $format_id;
  1644. $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
  1645. $this->assertText(t('The block has been created.'), t('New block successfully created.'));
  1646. // Verify the new block is in the database.
  1647. $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
  1648. $this->assertNotNull($bid, t('New block found in database'));
  1649. // Disable the text format.
  1650. $this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable'));
  1651. $this->assertRaw(t('Disabled text format %format.', array('%format' => $name)), t('Format successfully disabled.'));
  1652. $this->assertText('hook_filter_format_disable invoked.', t('hook_filter_format_disable() was invoked.'));
  1653. }
  1654. }
  1655. /**
  1656. * Tests filter settings.
  1657. */
  1658. class FilterSettingsTestCase extends DrupalWebTestCase {
  1659. /**
  1660. * The installation profile to use with this test class.
  1661. *
  1662. * @var string
  1663. */
  1664. protected $profile = 'testing';
  1665. public static function getInfo() {
  1666. return array(
  1667. 'name' => 'Filter settings',
  1668. 'description' => 'Tests filter settings.',
  1669. 'group' => 'Filter',
  1670. );
  1671. }
  1672. /**
  1673. * Tests explicit and implicit default settings for filters.
  1674. */
  1675. function testFilterDefaults() {
  1676. $filter_info = filter_filter_info();
  1677. $filters = array_fill_keys(array_keys($filter_info), array());
  1678. // Create text format using filter default settings.
  1679. $filter_defaults_format = (object) array(
  1680. 'format' => 'filter_defaults',
  1681. 'name' => 'Filter defaults',
  1682. 'filters' => $filters,
  1683. );
  1684. filter_format_save($filter_defaults_format);
  1685. // Verify that default weights defined in hook_filter_info() were applied.
  1686. $saved_settings = array();
  1687. foreach ($filter_defaults_format->filters as $name => $settings) {
  1688. $expected_weight = (isset($filter_info[$name]['weight']) ? $filter_info[$name]['weight'] : 0);
  1689. $this->assertEqual($settings['weight'], $expected_weight, format_string('@name filter weight %saved equals %default', array(
  1690. '@name' => $name,
  1691. '%saved' => $settings['weight'],
  1692. '%default' => $expected_weight,
  1693. )));
  1694. $saved_settings[$name]['weight'] = $expected_weight;
  1695. }
  1696. // Re-save the text format.
  1697. filter_format_save($filter_defaults_format);
  1698. // Reload it from scratch.
  1699. filter_formats_reset();
  1700. $filter_defaults_format = filter_format_load($filter_defaults_format->format);
  1701. $filter_defaults_format->filters = filter_list_format($filter_defaults_format->format);
  1702. // Verify that saved filter settings have not been changed.
  1703. foreach ($filter_defaults_format->filters as $name => $settings) {
  1704. $this->assertEqual($settings->weight, $saved_settings[$name]['weight'], format_string('@name filter weight %saved equals %previous', array(
  1705. '@name' => $name,
  1706. '%saved' => $settings->weight,
  1707. '%previous' => $saved_settings[$name]['weight'],
  1708. )));
  1709. }
  1710. }
  1711. }