diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/aggregator.module | 7 | ||||
-rw-r--r-- | modules/aggregator/aggregator.module | 7 | ||||
-rw-r--r-- | modules/block.module | 50 | ||||
-rw-r--r-- | modules/block/block.module | 50 | ||||
-rw-r--r-- | modules/blog.module | 4 | ||||
-rw-r--r-- | modules/blog/blog.module | 4 | ||||
-rw-r--r-- | modules/blogapi.module | 1 | ||||
-rw-r--r-- | modules/blogapi/blogapi.module | 1 | ||||
-rw-r--r-- | modules/book.module | 49 | ||||
-rw-r--r-- | modules/book/book.module | 49 | ||||
-rw-r--r-- | modules/comment.module | 20 | ||||
-rw-r--r-- | modules/comment/comment.module | 20 | ||||
-rw-r--r-- | modules/filter.module | 893 | ||||
-rw-r--r-- | modules/filter/filter.module | 893 | ||||
-rw-r--r-- | modules/forum.module | 2 | ||||
-rw-r--r-- | modules/forum/forum.module | 2 | ||||
-rw-r--r-- | modules/node.module | 33 | ||||
-rw-r--r-- | modules/node/node.module | 33 | ||||
-rw-r--r-- | modules/page.module | 51 | ||||
-rw-r--r-- | modules/page/page.module | 51 | ||||
-rw-r--r-- | modules/statistics.module | 2 | ||||
-rw-r--r-- | modules/statistics/statistics.module | 2 | ||||
-rw-r--r-- | modules/story.module | 2 | ||||
-rw-r--r-- | modules/story/story.module | 2 | ||||
-rw-r--r-- | modules/system.module | 2 | ||||
-rw-r--r-- | modules/system/system.module | 2 |
26 files changed, 1684 insertions, 548 deletions
diff --git a/modules/aggregator.module b/modules/aggregator.module index 6a2d53540..7d68ddccb 100644 --- a/modules/aggregator.module +++ b/modules/aggregator.module @@ -470,7 +470,12 @@ function aggregator_parse_feed(&$data, $feed) { // Prepare the item: foreach ($item as $key => $value) { - $item[$key] = filter_default(strtr(trim($value), $tt)); + // TODO: Make handling of aggregated HTML more flexible/configurable. + $value = strtr(trim($value), $tt); + $value = strip_tags($value, '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'); + $value = preg_replace('/\Wstyle\s*=[^>]+?>/i', '>', $value); + $value = preg_replace('/\Won[a-z]+\s*=[^>]+?>/i', '>', $value); + $item[$key] = $value; } /* diff --git a/modules/aggregator/aggregator.module b/modules/aggregator/aggregator.module index 6a2d53540..7d68ddccb 100644 --- a/modules/aggregator/aggregator.module +++ b/modules/aggregator/aggregator.module @@ -470,7 +470,12 @@ function aggregator_parse_feed(&$data, $feed) { // Prepare the item: foreach ($item as $key => $value) { - $item[$key] = filter_default(strtr(trim($value), $tt)); + // TODO: Make handling of aggregated HTML more flexible/configurable. + $value = strtr(trim($value), $tt); + $value = strip_tags($value, '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'); + $value = preg_replace('/\Wstyle\s*=[^>]+?>/i', '>', $value); + $value = preg_replace('/\Won[a-z]+\s*=[^>]+?>/i', '>', $value); + $item[$key] = $value; } /* diff --git a/modules/block.module b/modules/block.module index d6aced3ca..564f4225b 100644 --- a/modules/block.module +++ b/modules/block.module @@ -8,7 +8,7 @@ function block_help($section) { switch ($section) { case 'admin/help#block': return t(" -<p>Blocks are the boxes visible in the sidebar(s) of your web site. These are usually generated automatically by modules (e.g. recent forum topics), but you can also create your own blocks using either static HTML or dynamic PHP content.</p> +<p>Blocks are the boxes visible in the sidebar(s) of your web site. These are usually generated automatically by modules (e.g. recent forum topics), but you can also create your own blocks.</p> <p>The sidebar each block appears in depends on both which theme you're using (some are left-only, some right, some both), and on the settings in block management.</p><p>Whether a block is visible in the first place depends on four things:</p><ul><li>It must have its \"enabled\" box checked in block management.</li><li>If it has its \"custom\" box checked in block management, the user must have chosen to display it in their user preferences.</li><li>If the \"path\" field in block management is set, the visitor must be on a page that matches the path specification (more on this later).</li><li>If the block has its throttle box checked, the user will only see the block if the site throttle level is low.</li></ul> <p>The block management screen also lets you specify the vertical sort-order of the blocks within a sidebar. You do this by assigning a <strong>weight</strong> to each block. Lighter blocks (smaller weight) \"float up\" towards the top of the sidebar. Heavier ones \"sink down\" towards the bottom of it.</p> <p>The path setting lets you define the pages on which a specific block is visible. If you leave the path blank it will appear on all pages. The path uses a regular expression syntax so remember to escape special characters! The path expression is matched against the relative URL of a Drupal page, e.g. <code>book</code>, <code>node/12</code>, <code>admin</code>.</p> @@ -16,26 +16,7 @@ function block_help($section) { <p>However, for basic tasks it is sufficient to look at the following examples:</p> <p>If the block should only show up on blog pages, use <^blog>. To display on all node views use <^node>. The angular brackets are used as delimiters of the regular expression. To show up on either forum or book pages use <^(forum|book)>. The round brackets form a group of expressions, divided by the | character. It matches if any of the expressions in it match. A more complicated example is <^node/add/(story|blog|image)>. Blocks which have their paths set to this expression will show up on story, block, or image composition pages. If you want to show a block an all pages, but not the search page, use <^(?!search)>.</p> <h3>Administrator Defined Blocks</h3> -<p>An administrator defined block contains HTML, text or PHP content supplied by you (as opposed to being generated automatically by a module). Each admin-defined block consists of a title, a description, and a body containing text, HTML, or PHP code which can be as long as you wish. The Drupal engine will 'render' the content of the block.</p> -<h4>PHP in admin-defined blocks</h4> -<p>If you know how to script in PHP, Drupal gives you the power to embed any script you like inside a block. It will be executed when the page is viewed and dynamically embedded into the page. This gives you amazing flexibility and power, but of course with that comes danger and insecurity if you don't write good code. If you are not familiar with PHP, SQL or with the site engine, avoid experimenting with PHP blocks because you can corrupt your database or render your site insecure or even unusable! If you don't plan to do fancy stuff with your blocks then you're probably better off with straight HTML.</p> -<p>Remember that the code within each PHP block must be valid PHP code - including things like correctly terminating statements with a semicolon so that the parser won't die. It is highly recommended that you develop your blocks separately using a simple test script on top of a test database before migrating to your production environment.</p> -<p>Notes:</p><ul><li>You can use global variables, such as configuration parameters, within the scope of a PHP box but remember that variables which have been given values in a PHP box will retain these values in the engine or module afterwards.</li><li>register_globals is now set to <strong>off</strong> by default. If you need form information you need to get it from the \"superglobals\" \$_POST, \$_GET, etc.</li><li>You should use the <code>return</code> statement to return the actual content for your block.</li></ul> -<p>A basic example:</p> -<blockquote><p>You want to have a box with the title \"Welcome\" that you use to greet your visitors. The content for this box could be created by going:</p> -<pre> - return t(\"Welcome visitor, ... welcome message goes here ...\"); -</pre> -<p>If we are however dealing with a registered user, we can customize the message by using:</p> -<pre> - if (\$user->uid) { - return t(\"Welcome \$user->name, ... welcome message goes here ...\"); - } - else { - return t(\"Welcome visitor, ... welcome message goes here ...\"); - } -</pre></blockquote> -<p>For more in-depth examples, we recommend that you check the existing boxes and use them as a starting point.</p>", array('%pcre' => 'http://php.net/pcre/')); +<p>An administrator defined block contains content supplied by you (as opposed to being generated automatically by a module). Each admin-defined block consists of a title, a description, and a body which can be as long as you wish. The Drupal engine will 'render' the content of the block.</p>", array('%pcre' => 'http://php.net/pcre/')); case 'admin/modules#description': return t('Controls the boxes that are displayed around the main content.'); case 'admin/block': @@ -101,7 +82,7 @@ function block_block($op = 'list', $delta = 0) { else { $block = db_fetch_object(db_query('SELECT * FROM {boxes} WHERE bid = %d', $delta)); $data['subject'] = $block->title; - $data['content'] = ($block->type == 1) ? eval($block->body) : $block->body; + $data['content'] = check_output($block->body, $block->format); return $data; } } @@ -179,11 +160,20 @@ function block_admin_display() { $blocks = _block_rehash(); + // Fetch input formats used by admin-defined boxes. + $formats = array(); + $result = db_query('SELECT bid, format FROM {boxes}'); + while ($box = db_fetch_object($result)) { + $formats[$box->bid] = $box->format; + } + $header = array(t('block'), t('enabled'), t('custom'), t('throttle'), t('weight'), t('region'), t('path'), array('data' => t('operations'), 'colspan' => 2)); foreach ($blocks as $block) { if ($block['module'] == 'block') { - $edit = l(t('edit'), 'admin/block/edit/'. $block['delta']); + if (filter_access($formats[$block['delta']])) { + $edit = l(t('edit'), 'admin/block/edit/'. $block['delta']); + } $delete = l(t('delete'), 'admin/block/delete/'. $block['delta']); } else { @@ -232,14 +222,10 @@ function block_box_edit($bid = 0) { } function block_box_form($edit = array()) { - $type = array(0 => 'HTML', 1 => 'PHP'); - $group = form_textfield(t('Block title'), 'title', $edit['title'], 50, 64, t('The title of the block as shown to the user.')); + $group .= filter_form('format', $edit['format']); $group .= form_textarea(t('Block body'), 'body', $edit['body'], 70, 10, t('The content of the block as shown to the user.')); $group .= form_textfield(t('Block description'), 'info', $edit['info'], 50, 64, t('A brief description of your block. Used on the <a href="%overview">block overview page</a>.', array('%overview' => url('admin/block')))); - if (user_access('create php content')) { - $group .= form_radios(t('Block type'), 'type', $edit['type'], $type, t("If you would like to use PHP code inside your block, set the above option to 'PHP' instead of 'HTML'.")); - } if ($edit['bid']) { $group .= form_hidden('bid', $edit['bid']); @@ -252,16 +238,16 @@ function block_box_form($edit = array()) { } function block_box_save($edit) { - if (!user_access('create php content')) { - $edit['type'] = 0; + if (!filter_access($edit['format'])) { + $edit['format'] = FILTER_FORMAT_DEFAULT; } if ($edit['bid']) { - db_query("UPDATE {boxes} SET title = '%s', body = '%s', info = '%s', type = %d WHERE bid = %d", $edit['title'], $edit['body'], $edit['info'], $edit['type'], $edit['bid']); + db_query("UPDATE {boxes} SET title = '%s', body = '%s', info = '%s', format = %d WHERE bid = %d", $edit['title'], $edit['body'], $edit['info'], $edit['format'], $edit['bid']); return t('the block has been updated.'); } else { - db_query("INSERT INTO {boxes} (title, body, info, type) VALUES ('%s', '%s', '%s', %d)", $edit['title'], $edit['body'], $edit['info'], $edit['type']); + db_query("INSERT INTO {boxes} (title, body, info, format) VALUES ('%s', '%s', '%s', %d)", $edit['title'], $edit['body'], $edit['info'], $edit['format']); return t('the new block has been added.'); } } diff --git a/modules/block/block.module b/modules/block/block.module index d6aced3ca..564f4225b 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -8,7 +8,7 @@ function block_help($section) { switch ($section) { case 'admin/help#block': return t(" -<p>Blocks are the boxes visible in the sidebar(s) of your web site. These are usually generated automatically by modules (e.g. recent forum topics), but you can also create your own blocks using either static HTML or dynamic PHP content.</p> +<p>Blocks are the boxes visible in the sidebar(s) of your web site. These are usually generated automatically by modules (e.g. recent forum topics), but you can also create your own blocks.</p> <p>The sidebar each block appears in depends on both which theme you're using (some are left-only, some right, some both), and on the settings in block management.</p><p>Whether a block is visible in the first place depends on four things:</p><ul><li>It must have its \"enabled\" box checked in block management.</li><li>If it has its \"custom\" box checked in block management, the user must have chosen to display it in their user preferences.</li><li>If the \"path\" field in block management is set, the visitor must be on a page that matches the path specification (more on this later).</li><li>If the block has its throttle box checked, the user will only see the block if the site throttle level is low.</li></ul> <p>The block management screen also lets you specify the vertical sort-order of the blocks within a sidebar. You do this by assigning a <strong>weight</strong> to each block. Lighter blocks (smaller weight) \"float up\" towards the top of the sidebar. Heavier ones \"sink down\" towards the bottom of it.</p> <p>The path setting lets you define the pages on which a specific block is visible. If you leave the path blank it will appear on all pages. The path uses a regular expression syntax so remember to escape special characters! The path expression is matched against the relative URL of a Drupal page, e.g. <code>book</code>, <code>node/12</code>, <code>admin</code>.</p> @@ -16,26 +16,7 @@ function block_help($section) { <p>However, for basic tasks it is sufficient to look at the following examples:</p> <p>If the block should only show up on blog pages, use <^blog>. To display on all node views use <^node>. The angular brackets are used as delimiters of the regular expression. To show up on either forum or book pages use <^(forum|book)>. The round brackets form a group of expressions, divided by the | character. It matches if any of the expressions in it match. A more complicated example is <^node/add/(story|blog|image)>. Blocks which have their paths set to this expression will show up on story, block, or image composition pages. If you want to show a block an all pages, but not the search page, use <^(?!search)>.</p> <h3>Administrator Defined Blocks</h3> -<p>An administrator defined block contains HTML, text or PHP content supplied by you (as opposed to being generated automatically by a module). Each admin-defined block consists of a title, a description, and a body containing text, HTML, or PHP code which can be as long as you wish. The Drupal engine will 'render' the content of the block.</p> -<h4>PHP in admin-defined blocks</h4> -<p>If you know how to script in PHP, Drupal gives you the power to embed any script you like inside a block. It will be executed when the page is viewed and dynamically embedded into the page. This gives you amazing flexibility and power, but of course with that comes danger and insecurity if you don't write good code. If you are not familiar with PHP, SQL or with the site engine, avoid experimenting with PHP blocks because you can corrupt your database or render your site insecure or even unusable! If you don't plan to do fancy stuff with your blocks then you're probably better off with straight HTML.</p> -<p>Remember that the code within each PHP block must be valid PHP code - including things like correctly terminating statements with a semicolon so that the parser won't die. It is highly recommended that you develop your blocks separately using a simple test script on top of a test database before migrating to your production environment.</p> -<p>Notes:</p><ul><li>You can use global variables, such as configuration parameters, within the scope of a PHP box but remember that variables which have been given values in a PHP box will retain these values in the engine or module afterwards.</li><li>register_globals is now set to <strong>off</strong> by default. If you need form information you need to get it from the \"superglobals\" \$_POST, \$_GET, etc.</li><li>You should use the <code>return</code> statement to return the actual content for your block.</li></ul> -<p>A basic example:</p> -<blockquote><p>You want to have a box with the title \"Welcome\" that you use to greet your visitors. The content for this box could be created by going:</p> -<pre> - return t(\"Welcome visitor, ... welcome message goes here ...\"); -</pre> -<p>If we are however dealing with a registered user, we can customize the message by using:</p> -<pre> - if (\$user->uid) { - return t(\"Welcome \$user->name, ... welcome message goes here ...\"); - } - else { - return t(\"Welcome visitor, ... welcome message goes here ...\"); - } -</pre></blockquote> -<p>For more in-depth examples, we recommend that you check the existing boxes and use them as a starting point.</p>", array('%pcre' => 'http://php.net/pcre/')); +<p>An administrator defined block contains content supplied by you (as opposed to being generated automatically by a module). Each admin-defined block consists of a title, a description, and a body which can be as long as you wish. The Drupal engine will 'render' the content of the block.</p>", array('%pcre' => 'http://php.net/pcre/')); case 'admin/modules#description': return t('Controls the boxes that are displayed around the main content.'); case 'admin/block': @@ -101,7 +82,7 @@ function block_block($op = 'list', $delta = 0) { else { $block = db_fetch_object(db_query('SELECT * FROM {boxes} WHERE bid = %d', $delta)); $data['subject'] = $block->title; - $data['content'] = ($block->type == 1) ? eval($block->body) : $block->body; + $data['content'] = check_output($block->body, $block->format); return $data; } } @@ -179,11 +160,20 @@ function block_admin_display() { $blocks = _block_rehash(); + // Fetch input formats used by admin-defined boxes. + $formats = array(); + $result = db_query('SELECT bid, format FROM {boxes}'); + while ($box = db_fetch_object($result)) { + $formats[$box->bid] = $box->format; + } + $header = array(t('block'), t('enabled'), t('custom'), t('throttle'), t('weight'), t('region'), t('path'), array('data' => t('operations'), 'colspan' => 2)); foreach ($blocks as $block) { if ($block['module'] == 'block') { - $edit = l(t('edit'), 'admin/block/edit/'. $block['delta']); + if (filter_access($formats[$block['delta']])) { + $edit = l(t('edit'), 'admin/block/edit/'. $block['delta']); + } $delete = l(t('delete'), 'admin/block/delete/'. $block['delta']); } else { @@ -232,14 +222,10 @@ function block_box_edit($bid = 0) { } function block_box_form($edit = array()) { - $type = array(0 => 'HTML', 1 => 'PHP'); - $group = form_textfield(t('Block title'), 'title', $edit['title'], 50, 64, t('The title of the block as shown to the user.')); + $group .= filter_form('format', $edit['format']); $group .= form_textarea(t('Block body'), 'body', $edit['body'], 70, 10, t('The content of the block as shown to the user.')); $group .= form_textfield(t('Block description'), 'info', $edit['info'], 50, 64, t('A brief description of your block. Used on the <a href="%overview">block overview page</a>.', array('%overview' => url('admin/block')))); - if (user_access('create php content')) { - $group .= form_radios(t('Block type'), 'type', $edit['type'], $type, t("If you would like to use PHP code inside your block, set the above option to 'PHP' instead of 'HTML'.")); - } if ($edit['bid']) { $group .= form_hidden('bid', $edit['bid']); @@ -252,16 +238,16 @@ function block_box_form($edit = array()) { } function block_box_save($edit) { - if (!user_access('create php content')) { - $edit['type'] = 0; + if (!filter_access($edit['format'])) { + $edit['format'] = FILTER_FORMAT_DEFAULT; } if ($edit['bid']) { - db_query("UPDATE {boxes} SET title = '%s', body = '%s', info = '%s', type = %d WHERE bid = %d", $edit['title'], $edit['body'], $edit['info'], $edit['type'], $edit['bid']); + db_query("UPDATE {boxes} SET title = '%s', body = '%s', info = '%s', format = %d WHERE bid = %d", $edit['title'], $edit['body'], $edit['info'], $edit['format'], $edit['bid']); return t('the block has been updated.'); } else { - db_query("INSERT INTO {boxes} (title, body, info, type) VALUES ('%s', '%s', '%s', %d)", $edit['title'], $edit['body'], $edit['info'], $edit['type']); + db_query("INSERT INTO {boxes} (title, body, info, format) VALUES ('%s', '%s', '%s', %d)", $edit['title'], $edit['body'], $edit['info'], $edit['format']); return t('the new block has been added.'); } } diff --git a/modules/blog.module b/modules/blog.module index 0401a98fc..a9343c7fc 100644 --- a/modules/blog.module +++ b/modules/blog.module @@ -197,7 +197,7 @@ function blog_form(&$node) { if ($iid && $item = db_fetch_object(db_query('SELECT i.*, f.title as ftitle, f.link as flink FROM {aggregator_item} i, {aggregator_feed} f WHERE i.iid = %d AND i.fid = f.fid', $iid))) { $node->title = $item->title; - $node->body = "<a href=\"$item->link\">$item->title</a> - <i>". check_output($item->description) ."</i> [<a href=\"$item->flink\">$item->ftitle</a>]\n"; + $node->body = "<a href=\"$item->link\">$item->title</a> - <i>". $item->description ."</i> [<a href=\"$item->flink\">$item->ftitle</a>]\n"; } } @@ -205,7 +205,7 @@ function blog_form(&$node) { $output .= implode('', taxonomy_node_form('blog', $node)); } - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 15, filter_tips_short(), NULL, TRUE); + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 15, '', NULL, TRUE); return $output; } diff --git a/modules/blog/blog.module b/modules/blog/blog.module index 0401a98fc..a9343c7fc 100644 --- a/modules/blog/blog.module +++ b/modules/blog/blog.module @@ -197,7 +197,7 @@ function blog_form(&$node) { if ($iid && $item = db_fetch_object(db_query('SELECT i.*, f.title as ftitle, f.link as flink FROM {aggregator_item} i, {aggregator_feed} f WHERE i.iid = %d AND i.fid = f.fid', $iid))) { $node->title = $item->title; - $node->body = "<a href=\"$item->link\">$item->title</a> - <i>". check_output($item->description) ."</i> [<a href=\"$item->flink\">$item->ftitle</a>]\n"; + $node->body = "<a href=\"$item->link\">$item->title</a> - <i>". $item->description ."</i> [<a href=\"$item->flink\">$item->ftitle</a>]\n"; } } @@ -205,7 +205,7 @@ function blog_form(&$node) { $output .= implode('', taxonomy_node_form('blog', $node)); } - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 15, filter_tips_short(), NULL, TRUE); + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 15, '', NULL, TRUE); return $output; } diff --git a/modules/blogapi.module b/modules/blogapi.module index 41f7eebde..8a3801495 100644 --- a/modules/blogapi.module +++ b/modules/blogapi.module @@ -130,6 +130,7 @@ function blogapi_new_post($req_params) { 'promote' => $promote, 'comment' => $comment, 'moderate' => $moderate, + 'format' => FILTER_DEFAULT_FORMAT, 'revision' => $revision )); diff --git a/modules/blogapi/blogapi.module b/modules/blogapi/blogapi.module index 41f7eebde..8a3801495 100644 --- a/modules/blogapi/blogapi.module +++ b/modules/blogapi/blogapi.module @@ -130,6 +130,7 @@ function blogapi_new_post($req_params) { 'promote' => $promote, 'comment' => $comment, 'moderate' => $moderate, + 'format' => FILTER_DEFAULT_FORMAT, 'revision' => $revision )); diff --git a/modules/book.module b/modules/book.module index a56d2bc8b..a2111a00f 100644 --- a/modules/book.module +++ b/modules/book.module @@ -39,10 +39,11 @@ function book_access($op, $node) { // Only registered users can update book pages. Given the nature // of the book module this is considered to be a good/safe idea. // One can only update a book page if there are no suggested updates - // of that page waiting for approval, it is not a PHP page, and - // the "create new revision" bit is set. That is, only updates that - // don't overwrite the current or pending information are allowed. - return user_access('maintain books') && !$node->moderate && !$node->format && $node->revision; + // of that page waiting for approval and as long as the "create new + // revision"-bit is set. That is, only updates that don't overwrite + // the current or pending information are allowed. + + return user_access('maintain books') && !$node->moderate && $node->revision; } } @@ -139,7 +140,7 @@ function book_block($op = 'list', $delta = 0) { function book_load($node) { global $user; - $book = db_fetch_object(db_query('SELECT format, parent, weight, log FROM {book} WHERE nid = %d', $node->nid)); + $book = db_fetch_object(db_query('SELECT parent, weight, log FROM {book} WHERE nid = %d', $node->nid)); if (arg(1) == 'edit' && !user_access('administer nodes')) { // If a user is about to update a book page, we overload some @@ -166,14 +167,14 @@ function book_load($node) { * Implementation of hook_insert(). */ function book_insert($node) { - db_query("INSERT INTO {book} (nid, format, parent, weight, log) VALUES (%d, %d, %d, %d, '%s')", $node->nid, $node->format, $node->parent, $node->weight, $node->log); + db_query("INSERT INTO {book} (nid, parent, weight, log) VALUES (%d, %d, %d, '%s')", $node->nid, $node->parent, $node->weight, $node->log); } /** * Implementation of hook_update(). */ function book_update($node) { - db_query("UPDATE {book} SET format = %d, parent = %d, weight = %d, log = '%s' WHERE nid = %d", $node->format, $node->parent, $node->weight, $node->log, $node->nid); + db_query("UPDATE {book} SET parent = %d, weight = %d, log = '%s' WHERE nid = %d", $node->parent, $node->weight, $node->log, $node->nid); } /** @@ -187,17 +188,8 @@ function book_delete(&$node) { * Implementation of hook_validate(). */ function book_validate(&$node) { - if ($node->format && user_access('create php content')) { - // Do not filter PHP code. Do not auto-extract a teaser. - $node->teaser = $node->body; - } - else { - $node->format = 0; - } - // Set default values for non-administrators. if (!user_access('administer nodes')) { - $node->format = 0; $node->weight = 0; $node->revision = 1; } @@ -216,14 +208,11 @@ function book_form(&$node) { $output .= implode('', taxonomy_node_form('book', $node)); } - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, filter_tips_short(), NULL, TRUE); + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE); $output .= form_textarea(t('Log message'), 'log', $node->log, 60, 5, t('An explanation of the additions or updates being made to help the group understand your motivations.')); if (user_access('administer nodes')) { $output .= form_weight(t('Weight'), 'weight', $node->weight, 15, t('The heavier pages will sink and the lighter pages will be positioned nearer the top.')); - if (user_access('create php content')) { - $output .= form_radios(t('Type'), 'format', $node->format, array(0 => 'HTML / text', 1 => 'PHP')); - } } else { // If a regular user updates a book page, we create a new revision @@ -401,24 +390,8 @@ function book_content($node, $teaser = FALSE) { } } - // Extract the page body. If body is dynamic (using PHP code), the body - // will be generated. - if ($node->format == 1) { - // Make sure only authorized users can preview PHP pages. - if ($op == t('Preview') && !user_access('create php content')) { - return; - } - - ob_start(); - eval($node->body); - $node->body = ob_get_contents(); - ob_end_clean(); - $node->teaser = node_teaser($node->body); - $node->readmore = (strlen($node->teaser) < strlen($node->body)); - } - else { - $node = node_prepare($node, $teaser); - } + // Extract the page body. + $node = node_prepare($node, $teaser); return $node; } diff --git a/modules/book/book.module b/modules/book/book.module index a56d2bc8b..a2111a00f 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -39,10 +39,11 @@ function book_access($op, $node) { // Only registered users can update book pages. Given the nature // of the book module this is considered to be a good/safe idea. // One can only update a book page if there are no suggested updates - // of that page waiting for approval, it is not a PHP page, and - // the "create new revision" bit is set. That is, only updates that - // don't overwrite the current or pending information are allowed. - return user_access('maintain books') && !$node->moderate && !$node->format && $node->revision; + // of that page waiting for approval and as long as the "create new + // revision"-bit is set. That is, only updates that don't overwrite + // the current or pending information are allowed. + + return user_access('maintain books') && !$node->moderate && $node->revision; } } @@ -139,7 +140,7 @@ function book_block($op = 'list', $delta = 0) { function book_load($node) { global $user; - $book = db_fetch_object(db_query('SELECT format, parent, weight, log FROM {book} WHERE nid = %d', $node->nid)); + $book = db_fetch_object(db_query('SELECT parent, weight, log FROM {book} WHERE nid = %d', $node->nid)); if (arg(1) == 'edit' && !user_access('administer nodes')) { // If a user is about to update a book page, we overload some @@ -166,14 +167,14 @@ function book_load($node) { * Implementation of hook_insert(). */ function book_insert($node) { - db_query("INSERT INTO {book} (nid, format, parent, weight, log) VALUES (%d, %d, %d, %d, '%s')", $node->nid, $node->format, $node->parent, $node->weight, $node->log); + db_query("INSERT INTO {book} (nid, parent, weight, log) VALUES (%d, %d, %d, '%s')", $node->nid, $node->parent, $node->weight, $node->log); } /** * Implementation of hook_update(). */ function book_update($node) { - db_query("UPDATE {book} SET format = %d, parent = %d, weight = %d, log = '%s' WHERE nid = %d", $node->format, $node->parent, $node->weight, $node->log, $node->nid); + db_query("UPDATE {book} SET parent = %d, weight = %d, log = '%s' WHERE nid = %d", $node->parent, $node->weight, $node->log, $node->nid); } /** @@ -187,17 +188,8 @@ function book_delete(&$node) { * Implementation of hook_validate(). */ function book_validate(&$node) { - if ($node->format && user_access('create php content')) { - // Do not filter PHP code. Do not auto-extract a teaser. - $node->teaser = $node->body; - } - else { - $node->format = 0; - } - // Set default values for non-administrators. if (!user_access('administer nodes')) { - $node->format = 0; $node->weight = 0; $node->revision = 1; } @@ -216,14 +208,11 @@ function book_form(&$node) { $output .= implode('', taxonomy_node_form('book', $node)); } - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, filter_tips_short(), NULL, TRUE); + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE); $output .= form_textarea(t('Log message'), 'log', $node->log, 60, 5, t('An explanation of the additions or updates being made to help the group understand your motivations.')); if (user_access('administer nodes')) { $output .= form_weight(t('Weight'), 'weight', $node->weight, 15, t('The heavier pages will sink and the lighter pages will be positioned nearer the top.')); - if (user_access('create php content')) { - $output .= form_radios(t('Type'), 'format', $node->format, array(0 => 'HTML / text', 1 => 'PHP')); - } } else { // If a regular user updates a book page, we create a new revision @@ -401,24 +390,8 @@ function book_content($node, $teaser = FALSE) { } } - // Extract the page body. If body is dynamic (using PHP code), the body - // will be generated. - if ($node->format == 1) { - // Make sure only authorized users can preview PHP pages. - if ($op == t('Preview') && !user_access('create php content')) { - return; - } - - ob_start(); - eval($node->body); - $node->body = ob_get_contents(); - ob_end_clean(); - $node->teaser = node_teaser($node->body); - $node->readmore = (strlen($node->teaser) < strlen($node->body)); - } - else { - $node = node_prepare($node, $teaser); - } + // Extract the page body. + $node = node_prepare($node, $teaser); return $node; } diff --git a/modules/comment.module b/modules/comment.module index 349635bc1..740bc1131 100644 --- a/modules/comment.module +++ b/modules/comment.module @@ -319,7 +319,7 @@ function comment_update_index() { function comment_user($type, $edit, &$user, $category = NULL) { if ($type == 'form' && $category == 'account') { // when user tries to edit his own data - return array(array('title' => t('Comment settings'), 'data' => form_textarea(t('Signature'), 'signature', $user->signature, 64, 3, t('Your signature will be publicly displayed at the end of your comments.') .'<br />'. filter_tips_short()), 'weight' => 2)); + return array(array('title' => t('Comment settings'), 'data' => form_textarea(t('Signature'), 'signature', $user->signature, 64, 3, t('Your signature will be publicly displayed at the end of your comments.')), 'weight' => 2)); } if ($type == 'validate') { // validate user data editing @@ -456,6 +456,13 @@ function comment_validate_form($edit) { } /* + ** Validate filter format + */ + if (!filter_access($edit['format'])) { + form_set_error('format', t('The supplied input format is invalid.')); + } + + /* ** Check validity of name, mail and homepage (if given) */ @@ -953,7 +960,7 @@ function comment_admin_edit($cid) { if ($comment) { $form .= form_item(t('Author'), format_name($comment)); $form .= form_textfield(t('Subject'), 'subject', $comment->subject, 70, 128); - $form .= form_textarea(t('Comment'), 'comment', $comment->comment, 70, 15, filter_tips_short()); + $form .= form_textarea(t('Comment'), 'comment', $comment->comment, 70, 15, ''); $form .= form_radios(t('Status'), 'status', $comment->status, array('published', 'not published')); $form .= form_hidden('cid', $cid); $form .= form_submit(t('Submit')); @@ -986,7 +993,7 @@ function comment_delete($cid) { // Print a confirmation. else if ($comment->cid) { drupal_set_message(t('do you want to delete this comment and all its replies?')); - $comment->comment = check_output($comment->comment); + $comment->comment = check_output($comment->comment, $comment->format); $output = theme('comment', $comment); $output .= form_submit(t('Delete comment')); } @@ -1406,8 +1413,11 @@ function theme_comment_form($edit, $title) { $form .= form_textfield(t('Subject'), 'subject', $edit['subject'], 50, 64); } + // format selector + $form .= filter_form('format', $edit['format']); + // comment field: - $form .= form_textarea(t('Comment'), 'comment', $edit['comment'] ? $edit['comment'] : $user->signature, 70, 10, filter_tips_short(), NULL, TRUE); + $form .= form_textarea(t('Comment'), 'comment', $edit['comment'] ? $edit['comment'] : $user->signature, 70, 10, '', NULL, TRUE); // preview button: $form .= form_hidden('cid', $edit['cid']); @@ -1438,7 +1448,7 @@ function theme_comment_view($comment, $links = '', $visible = 1) { // Switch to folded/unfolded view of the comment if ($visible) { - $comment->comment = check_output($comment->comment); + $comment->comment = check_output($comment->comment, $comment->format); $output .= theme('comment', $comment, $links); } else { diff --git a/modules/comment/comment.module b/modules/comment/comment.module index 349635bc1..740bc1131 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -319,7 +319,7 @@ function comment_update_index() { function comment_user($type, $edit, &$user, $category = NULL) { if ($type == 'form' && $category == 'account') { // when user tries to edit his own data - return array(array('title' => t('Comment settings'), 'data' => form_textarea(t('Signature'), 'signature', $user->signature, 64, 3, t('Your signature will be publicly displayed at the end of your comments.') .'<br />'. filter_tips_short()), 'weight' => 2)); + return array(array('title' => t('Comment settings'), 'data' => form_textarea(t('Signature'), 'signature', $user->signature, 64, 3, t('Your signature will be publicly displayed at the end of your comments.')), 'weight' => 2)); } if ($type == 'validate') { // validate user data editing @@ -456,6 +456,13 @@ function comment_validate_form($edit) { } /* + ** Validate filter format + */ + if (!filter_access($edit['format'])) { + form_set_error('format', t('The supplied input format is invalid.')); + } + + /* ** Check validity of name, mail and homepage (if given) */ @@ -953,7 +960,7 @@ function comment_admin_edit($cid) { if ($comment) { $form .= form_item(t('Author'), format_name($comment)); $form .= form_textfield(t('Subject'), 'subject', $comment->subject, 70, 128); - $form .= form_textarea(t('Comment'), 'comment', $comment->comment, 70, 15, filter_tips_short()); + $form .= form_textarea(t('Comment'), 'comment', $comment->comment, 70, 15, ''); $form .= form_radios(t('Status'), 'status', $comment->status, array('published', 'not published')); $form .= form_hidden('cid', $cid); $form .= form_submit(t('Submit')); @@ -986,7 +993,7 @@ function comment_delete($cid) { // Print a confirmation. else if ($comment->cid) { drupal_set_message(t('do you want to delete this comment and all its replies?')); - $comment->comment = check_output($comment->comment); + $comment->comment = check_output($comment->comment, $comment->format); $output = theme('comment', $comment); $output .= form_submit(t('Delete comment')); } @@ -1406,8 +1413,11 @@ function theme_comment_form($edit, $title) { $form .= form_textfield(t('Subject'), 'subject', $edit['subject'], 50, 64); } + // format selector + $form .= filter_form('format', $edit['format']); + // comment field: - $form .= form_textarea(t('Comment'), 'comment', $edit['comment'] ? $edit['comment'] : $user->signature, 70, 10, filter_tips_short(), NULL, TRUE); + $form .= form_textarea(t('Comment'), 'comment', $edit['comment'] ? $edit['comment'] : $user->signature, 70, 10, '', NULL, TRUE); // preview button: $form .= form_hidden('cid', $edit['cid']); @@ -1438,7 +1448,7 @@ function theme_comment_view($comment, $links = '', $visible = 1) { // Switch to folded/unfolded view of the comment if ($visible) { - $comment->comment = check_output($comment->comment); + $comment->comment = check_output($comment->comment, $comment->format); $output .= theme('comment', $comment, $links); } else { diff --git a/modules/filter.module b/modules/filter.module index dbb2eab99..f5e0834c1 100644 --- a/modules/filter.module +++ b/modules/filter.module @@ -1,7 +1,8 @@ <?php // $Id$ -define('FILTER_HTML_DONOTHING', 0); +define('FILTER_FORMAT_DEFAULT', 0); + define('FILTER_HTML_STRIP', 1); define('FILTER_HTML_ESCAPE', 2); @@ -12,52 +13,135 @@ define('FILTER_STYLE_STRIP', 1); * Implementation of hook_help(). */ function filter_help($section) { + // Get rid of variable numbers in the URL + $section = preg_replace('/[0-9]+/', '#', $section); + switch ($section) { case 'admin/modules#description': return t('Framework for handling filtering of content.'); + case 'admin/filters': - return t(" -<p>Filters fit between the raw text in posts and comments, and the HTML output. They allow you to replace text selectively. Uses include automatic conversion of emoticons into graphics and filtering HTML content from users' submissions.</p> -<p>If you notice some filters are causing conflicts in the output, you can <a href=\"%url\">rearrange them</a>.</p>", array('%url' => url('admin/filters/order'))); - case 'admin/filters/order': - return t(" + return t(' +<p><i>Input formats</i> define a way of processing user-supplied text in Drupal. Every input format has its own settings of which <i>filters</i> to apply. Possible filters include stripping out malicious HTML and making URLs clickable.</p> +<p>Users can choose between the available input formats when submitting content.</p> +<p>Below you can configure which input formats are available to which roles, as well as choose a default input format (used for imported content, for example).</p>'); + + case 'admin/filters/#': + return t(' +<p>Every <i>filter</i> performs one particular change on the user input, for example stripping out malicious HTML or making URLs clickable. Choose which filters you want to apply to text in this input format.</p> +<p>If you notice some filters are causing conflicts in the output, you can <a href="%order">rearrange them</a>.', array('%configure' => url('admin/filters/'. arg(2) .'/configure'), '%order' => url('admin/filters/'. arg(2) .'/order'))); + + case 'admin/filters/#/configure': + return t(' +<p>If you cannot find the settings for a certain filter, make sure you\'ve enabled it on the <a href="%url">list filters</a> tab first.</p>', array('%url' => url('admin/filters/'. arg(2) .'/list'))); + + case 'admin/filters/#/order': + return t(' <p>Because of the flexible filtering system, you might encounter a situation where one filter prevents another from doing its job. For example: a word in an URL gets converted into a glossary term, before the URL can be converted in a clickable link. When this happens, you will need to rearrange the order in which filters get executed.</p> -<p>Filters are executed from top-to-bottom. You can use the weight column to rearrange them: heavier filters 'sink' to the bottom. Standard HTML filtering is always run first.</p>"); - case 'filter#long-tip': - case 'filter#short-tip': - switch (variable_get('filter_html', FILTER_HTML_DONOTHING)) { - case 0: - return t('All HTML tags allowed'); - break; - case 1: - if ($allowed_html = variable_get('allowed_html', '<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>')) { +<p>Filters are executed from top-to-bottom. You can use the weight column to rearrange them: heavier filters \'sink\' to the bottom.</p>'); + } +} + +/** + * Implementation of hook_filter_tips. + */ +function filter_filter_tips($delta, $format, $type = false) { + switch ($delta) { + case 0: + switch (variable_get("filter_html_$format", FILTER_HTML_STRIP)) { + + case FILTER_HTML_STRIP: + if ($allowed_html = variable_get("allowed_html_$format", '<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>')) { return t('Allowed HTML tags') .': '. htmlspecialchars($allowed_html); - } else { + } + else { return t('No HTML tags allowed'); } - break; - case 2: + + case FILTER_STYLE_STRIP: return t('No HTML tags allowed'); - break; } break; + + case 1: + switch ($type) { + case 0: + return t('You may post PHP code. You should include <?php ?> tags.'); + case 1: + return t(' +<h4>Using custom PHP code</h4> +<p>If you know how to script in PHP, Drupal gives you the power to embed any script you like. It will be executed when the page is viewed and dynamically embedded into the page. This gives you amazing flexibility and power, but of course with that comes danger and insecurity if you don\'t write good code. If you are not familiar with PHP, SQL or with the site engine, avoid experimenting with PHP because you can corrupt your database or render your site insecure or even unusable! If you don\'t plan to do fancy stuff with your content then you\'re probably better off with straight HTML.</p> +<p>Remember that the code within each PHP item must be valid PHP code - including things like correctly terminating statements with a semicolon. It is highly recommended that you develop your code separately using a simple test script on top of a test database before migrating to your production environment.</p> +<p>Notes:</p><ul><li>You can use global variables, such as configuration parameters, within the scope of your PHP code but remember that global variables which have been given values in your code will retain these values in the engine afterwards.</li><li>register_globals is now set to <strong>off</strong> by default. If you need form information you need to get it from the "superglobals" $_POST, $_GET, etc.</li><li>You can either use the <code>print</code> or <code>return</code> statement to output the actual content for your item.</li></ul> +<p>A basic example:</p> +<blockquote><p>You want to have a box with the title "Welcome" that you use to greet your visitors. The content for this box could be created by going:</p> +<pre> + print t("Welcome visitor, ... welcome message goes here ..."); +</pre> +<p>If we are however dealing with a registered user, we can customize the message by using:</p> +<pre> + global $user; + if ($user->uid) { + print t("Welcome $user->name, ... welcome message goes here ..."); + } + else { + print t("Welcome visitor, ... welcome message goes here ..."); + } +</pre></blockquote> +<p>For more in-depth examples, we recommend that you check the existing Drupal code and use it as a starting point, especially for sidebar boxes.</p>'); + } + + case 3: + return t('Lines and paragraphs break automatically.'); + break; } } + /** * Implementation of hook_menu(). */ function filter_menu() { $items = array(); - $items[] = array('path' => 'admin/filters', 'title' => t('filters'), - 'callback' => 'filter_admin_settings', - 'access' => user_access('administer site configuration')); - $items[] = array('path' => 'admin/filters/configure', 'title' => t('configure'), - 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10); - $items[] = array('path' => 'admin/filters/order', 'title' => t('rearrange'), - 'callback' => 'filter_admin_order', - 'access' => user_access('administer site configuration'), - 'type' => MENU_LOCAL_TASK); + + $items[] = array('path' => 'admin/filters', 'title' => t('input formats'), + 'callback' => 'filter_admin_overview', + 'access' => user_access('administer filters')); + + $items[] = array('path' => 'admin/filters/delete', 'title' => t('delete input format'), + 'callback' => 'filter_admin_delete', + 'type' => MENU_CALLBACK, + 'access' => user_access('administer filters')); + + if (arg(0) == 'admin' && arg(1) == 'filters' && is_numeric(arg(2))) { + $formats = filter_formats(); + + if (isset($formats[arg(2)])) { + $items[] = array('path' => 'admin/filters/'. arg(2), 'title' => t("'%format' input format", array('%format' => $formats[arg(2)]->name)), + 'callback' => 'filter_admin_filters', + 'type' => MENU_CALLBACK, + 'access' => user_access('administer filters')); + + $items[] = array('path' => 'admin/filters/'. arg(2) .'/list', 'title' => t('list filters'), + 'callback' => 'filter_admin_filters', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 0, + 'access' => user_access('administer filters')); + + $items[] = array('path' => 'admin/filters/'. arg(2) .'/configure', 'title' => t('configure filters'), + 'callback' => 'filter_admin_configure', + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + 'access' => user_access('administer filters')); + + $items[] = array('path' => 'admin/filters/'. arg(2) .'/order', 'title' => t('rearrange filters'), + 'callback' => 'filter_admin_order', + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + 'access' => user_access('administer filters')); + } + } + $items[] = array('path' => 'filter/tips', 'title' => t('compose tips'), 'callback' => 'filter_tips_long', 'access' => TRUE, 'type' => MENU_SUGGESTED_ITEM); @@ -65,30 +149,271 @@ function filter_menu() { } /** - * Menu callback; allows administrators to change the filter ordering. + * Implementation of hook_perm() */ -function filter_admin_order() { +function filter_perm() { + return array('administer filters'); +} + +/** + * Menu callback: allows administrators to setup input formats + */ +function filter_admin_overview() { + // Process form submission + switch ($_POST['op']) { + case t('Save input formats'): + filter_admin_save(); + break; + case t('Add input format'): + filter_admin_add(); + break; + } + + // Overview of all formats + $formats = filter_formats(); + $roles = user_roles(); + $error = false; + + $header = array(t('name'), t('default')); + foreach ($roles as $name) { + $header[] = $name; + } + $header[] = array('data' => t('operations'), 'colspan' => 2); + + $rows = array(); + foreach ($formats as $id => $format) { + $row = array(); + $default = ($id == variable_get('filter_default_format', 1)); + + $row[] = form_textfield('', "name][$id", $format->name, 16, 255); + $row[] = form_radio('', 'default', $id, $default); + + foreach ($roles as $rid => $name) { + $checked = strstr($format->roles, ",$rid,"); + + if ($default && !$checked && !$error) { + form_set_error("roles][$id][$rid", t('The default input format must be accessible to every role.')); + $error = true; + } + + $row[] = form_checkbox('', "roles][$id][$rid", 1, $checked); + } + + $row[] = l('configure', 'admin/filters/'. $id); + $row[] = $default ? '' : l('delete', 'admin/filters/delete/'. $id); + + $rows[] = $row; + } + + $group = theme('table', $header, $rows); + $group .= form_submit(t('Save input formats')); + $output = '<h2>'. t('Permissions and settings') . '</h2>' . form($group); + + // Form to add a new format + $group = t("<p>To add a new input format, type its name here. After it has been added, you can configure its options.</p>"); + $form = form_textfield(t('Name'), 'name', '', 40, 255); + $form .= form_submit(t('Add input format')); + $group .= form($form); + $output .= '<h2>'. t('Add new input format') .'</h2>'. $group; + + print theme('page', $output); +} + +/** + * Save input formats on overview page + */ +function filter_admin_save() { $edit = $_POST['edit']; - $op = $_POST['op']; - if ($op == t('Save configuration')) { - foreach ($edit as $module => $filter) { - db_query("UPDATE {filters} SET weight = %d WHERE module = '%s'", $filter['weight'], $module); + + variable_set('filter_default_format', $edit['default']); + + foreach ($edit['name'] as $id => $name) { + $name = trim($name); + + if (strlen($name) == 0) { + drupal_set_message(t('You must enter a name for this input format.')); + drupal_goto('admin/filters'); + } + else { + db_query("UPDATE {filter_formats} SET name='%s' WHERE format = %d", $name, $id); + } + } + + // We store the roles as a string for ease of use. + // We use leading and trailing comma's to allow easy substring matching. + foreach ($edit['roles'] as $id => $format) { + $roles = ','; + foreach ($format as $rid => $value) { + if ($value) { + $roles .= $rid .','; + } } + db_query("UPDATE {filter_formats} SET roles = '%s' WHERE format = %d", $roles, $id); + } + + drupal_set_message(t('The input format settings have been updated.')); + drupal_goto('admin/filters'); +} + +/** + * Add new input format + */ +function filter_admin_add() { + $edit = $_POST['edit']; + + $name = trim($edit['name']); + + if (strlen($name) == 0) { + drupal_set_message(t('You must enter a name for this input format.')); + drupal_goto('admin/filters'); + } + else { + db_query("INSERT INTO {filter_formats} (name) VALUES ('%s')", $name); + } + + drupal_set_message(t("Added input format '%format'", array('%format' => $edit['name']))); + drupal_goto('admin/filters'); +} + +/** + * Menu callback: confirm deletion of a format + */ +function filter_admin_delete() { + $edit = $_POST['edit']; + if ($_POST['op'] == t('Confirm deletion')) { + if ($edit['format'] != variable_get('filter_default_format', 1)) { + db_query("DELETE FROM {filter_formats} WHERE format = %d", $edit['format']); + db_query("DELETE FROM {filters} WHERE format = %d", $edit['format']); + + $default = variable_get('filter_default_format', 1); + db_query("UPDATE {node} SET format = %d WHERE format = %d", $default, $edit['format']); + db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $edit['format']); + db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $edit['format']); + + cache_clear_all('filter:'. $edit['format'], true); + + drupal_set_message(t("Deleted input format '%format'", array('%format' => $edit['name']))); + } + drupal_goto('admin/filters'); + } + + $format = arg(3); + $format = db_fetch_object(db_query('SELECT * FROM {filter_formats} WHERE format = %d', $format)); + + $form .= form_hidden('format', $format->format); + $form .= form_hidden('name', $format->name); + $form .= '<p>'. t("Are you sure you want to delete the input format '%format'? If you have any content left in this input format, it will be switched to the default input format.", array('%format' => $format->name)) .'</p>'; + $form .= form_submit(t('Confirm deletion')); + print theme('page', form($form)); +} + +/** + * Ask for confirmation before deleting a format + */ +function filter_admin_confirm() { + $edit = $_POST['edit']['format']; + + + return form($form); +} + +/** + * Menu callback: configure the filters for a format. + */ +function filter_admin_filters() { + $format = arg(2); + + // Handle saving of weights + if ($_POST['op']) { + filter_admin_filters_save($format, $_POST['edit']); + } + + $all = filter_list_all(); + $enabled = filter_list_format($format); + + /* + ** Table with filters + */ + $header = array('enabled', 'name', 'description'); + $rows = array(); + foreach ($all as $id => $filter) { + $row = array(); + $row[] = form_checkbox('', $id, 1, isset($enabled[$id])); + $row[] = $filter->name; + $row[] = module_invoke($filter->module, 'filter', 'description', $filter->delta); + + $rows[] = $row; + } + $form = theme('table', $header, $rows); + if (!$empty) { + $form .= form_submit(t('Save configuration')); + } + + $output .= '<h2>'. t('Filters') .'</h2>'. form($form); + + /* + ** Compose tips (guidelines) + */ + $tips = _filter_tips($format, false); + $extra = l(t('More information about formatting options'), 'filter/tips'); + $tiplist = theme('filter_tips', $tips, $extra); + if (!$tiplist) { + $tiplist = t('<p>No guidelines available.</p>'); + } + $group = t('<p>These are the guidelines that users will see for posting in this input format. They are automatically generated from the filter settings.</p>'); + $group .= $tiplist; + $output .= '<h2>'. t('Formatting guidelines') .'</h2>'. $group; + + print theme('page', $output); +} + +/** + * Save enabled/disabled status for filters in a format. + */ +function filter_admin_filters_save($format, $toggles) { + $current = filter_list_format($format); + + $cache = true; + + db_query("DELETE FROM {filters} WHERE format = %d", $format); + foreach ($toggles as $id => $checked) { + if ($checked) { + list($module, $delta) = explode('/', $id); + // Add new filters to the bottom + $weight = isset($current[$id]->weight) ? $current[$id]->weight : 10; + db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", $format, $module, $delta, $weight); + + // Check if there are any 'no cache' filters + $cache &= !module_invoke($module, 'filter', 'no cache', $delta); + } + } + + // Update the format's 'no cache' flag. + db_query('UPDATE {filter_formats} SET cache = %d WHERE format = %d', (int)$cache, $format); + + cache_clear_all('filter:'. $format, true); + + drupal_set_message(t('The input format has been updated.')); + drupal_goto('admin/filters/'. arg(2) .'/list'); +} + +/** + * Menu callback: display form for ordering filters for a format. + */ +function filter_admin_order() { + $format = arg(2); + if ($_POST['op']) { + filter_admin_order_save($format, $_POST['edit']); } // Get list (with forced refresh) - filter_refresh(); - $filters = filter_list(); + $filters = filter_list_format($format); $header = array(t('name'), t('weight')); $rows = array(); - // Standard HTML filters are always run first, we add a dummy row to indicate this - $rows[] = array(t('HTML filtering'), array('data' => t('locked'))); - - foreach ($filters as $module => $filter) { - $name = module_invoke($module, 'filter', 'name'); - $rows[] = array($name, array('data' => form_weight(NULL, $module .'][weight', $filter['weight']))); + foreach ($filters as $id => $filter) { + $rows[] = array($filter->name, form_weight('', $id, $filter->weight)); } $form = theme('table', $header, $rows); @@ -99,93 +424,184 @@ function filter_admin_order() { } /** - * Menu callback; displays settings defined by filters. + * Save the weights of filters in a format. + */ +function filter_admin_order_save($format, $weights) { + foreach ($weights as $id => $weight) { + list($module, $delta) = explode('/', $id); + db_query("UPDATE filters SET weight = %d WHERE format = %d AND module = '%s' AND delta = %d", $weight, $format, $module, $delta); + } + drupal_set_message(t('The filter weights have been saved.')); + + cache_clear_all('filter:'. $format, true); + + drupal_goto('admin/filters/'. arg(2) .'/order'); +} + +/** + * Menu callback: display settings defined by filters. */ -function filter_admin_settings() { +function filter_admin_configure() { + $format = arg(2); + system_settings_save(); - filter_refresh(); + $list = filter_list_format($format); + $form = ""; + foreach ($list as $filter) { + $form .= module_invoke($filter->module, 'filter', 'settings', $filter->delta, $format); + } - $form = filter_default_settings(); - $form .= implode("\n", module_invoke_all('filter', 'settings')); - $output = system_settings_form($form); + if (trim($form) != '') { + $output = system_settings_form($form); + } + else { + $output = t('No settings are available.'); + } print theme('page', $output); } /** - * Search through all modules for the filters they implement. + * Retrieve a list of input formats. */ -function filter_refresh() { - $modules = module_list(); - $filters = filter_list(); +function filter_formats() { + global $user; + static $formats; + + // Administrators can always use all input formats. + $all = user_access('administer filters'); + + if (!isset($formats)) { + $formats = array(); + + $query = array('SELECT * FROM {filter_formats}'); - // Update list in database - db_query('DELETE FROM {filters}'); - foreach ($modules as $module) { - if (module_hook($module, 'filter')) { - $weight = $filters[$module]['weight']; + // Build query for selecting the format(s) based on the user's roles + if (!$all) { + $where = array(); + foreach ($user->roles as $rid => $role) { + $where[] = "roles LIKE '%%,%d,%%'"; + $query[] = $rid; + } + $query[0] .= ' WHERE '. implode(' OR ', $where); + } + + $result = call_user_func_array('db_query', $query); + while ($format = db_fetch_object($result)) { + $formats[$format->format] = $format; + } + } + return $formats; +} - db_query("INSERT INTO {filters} (module, weight) VALUES ('%s', %d)", $module, $weight); +/** + * Build a list of all filters. + */ +function filter_list_all() { + $filters = array(); + + foreach (module_list() as $module) { + $list = module_invoke($module, 'filter', 'list'); + if (is_array($list)) { + foreach ($list as $delta => $name) { + $filters[$module .'/'. $delta] = (object)array('module' => $module, 'delta' => $delta, 'name' => $name); + } } } - filter_list(1); + uasort($filters, '_filter_list_cmp'); + + return $filters; +} + +/** + * Helper function for sorting the filter list by filter name. + */ +function _filter_list_cmp($a, $b) { + return strcmp($a->name, $b->name); } /** - * Retrieve a list of all filters from the database. + * Check if text in a certain input format is allowed to be cached. */ -function filter_list($force = 0) { - static $filters; +function filter_format_allowcache($format) { + static $cache = array(); - if (!is_array($filters) || $force) { - $filters = array(); - $result = db_query('SELECT * FROM {filters} ORDER BY weight ASC'); - while ($filter = db_fetch_array($result)) { - // Fail-safe in case a module was deleted/changed without disabling it - if (module_hook($filter['module'], 'filter')) { - $filters[$filter['module']] = $filter; + if (!isset($cache[$format])) { + $cache[$format] = db_result(db_query('SELECT cache FROM {filter_formats} WHERE format = %d', $format)); + } + return $cache[$format]; +} + +/** + * Retrieve a list of filters for a certain format. + */ +function filter_list_format($format) { + static $filters = array(); + + if (!is_array($filters[$format])) { + $filters[$format] = array(); + $result = db_query("SELECT * FROM {filters} WHERE format = %d ORDER BY weight ASC", $format); + while ($filter = db_fetch_object($result)) { + $list = module_invoke($filter->module, 'filter', 'list'); + if (is_array($list) && isset($list[$filter->delta])) { + $filter->name = $list[$filter->delta]; + $filters[$format][$filter->module .'/'. $filter->delta] = $filter; } } } - return $filters; + return $filters[$format]; } /** + * @name Filtering functions + * + * Modules which need to have content filtered can use these functions to + * interact with the filter system. + * + * @{ + */ + +/** * Run all the enabled filters on a piece of text. */ -function check_output($text) { +function check_output($text, $format = FILTER_FORMAT_DEFAULT) { if (isset($text)) { + if ($format == FILTER_FORMAT_DEFAULT) { + $format = variable_get('filter_default_format', 1); + } + + // Check for a cached version of this piece of text + $id = 'filter:'. $format .':'. md5($text); + if ($cached = cache_get($id)) { + return $cached->data; + } + + // See if caching is allowed for this format + $cache = filter_format_allowcache($format); // Convert all Windows and Mac newlines to a single newline, // so filters only need to deal with this one $text = str_replace(array("\r\n", "\r"), "\n", $text); // Get complete list of filters ordered properly - $filters = filter_list(); + $filters = filter_list_format($format); // Give filters the chance to escape HTML-like data such as code or formulas. - // From this point on, the input can be treated as HTML. - if (variable_get('filter_html', FILTER_HTML_DONOTHING) != FILTER_HTML_ESCAPE) { - foreach ($filters as $module => $filter) { - $text = module_invoke($module, 'filter', 'prepare', $text); - } + foreach ($filters as $filter) { + $text = module_invoke($filter->module, 'filter', 'prepare', $filter->delta, $format, $text); } - // HTML handling is done before all regular filtering activities. - $text = filter_default($text); - - // Regular filtering. - foreach ($filters as $module => $filter) { - $text = module_invoke($module, 'filter', 'process', $text); + // Perform filtering + foreach ($filters as $filter) { + $text = module_invoke($filter->module, 'filter', 'process', $filter->delta, $format, $text); } - // If only inline elements are used and no block level elements, we - // replace all newlines with HTML line breaks. - if (strip_tags($text, '<a><br><span><bdo><map><object><img><tt><i><b><u><big><small><em><strong><dfn><code><q><samp><kbd><var><cite><abbr><acronym><sub><sup><input><select><textarea><label><button><ins><del><script>') == $text) { - $text = nl2br($text); + // Store in cache + if ($cache) { + cache_set($id, $text, 1); } } else { @@ -196,60 +612,256 @@ function check_output($text) { } /** - * Perform the default filters, preventing malicious HTML from being displayed. + * Generate selector for choosing a format in a form. + * + * @param $name + * The internal name used to refer to the selector. + * + * @param $value + * The id of the format that is currently selected. + * + * @return + * HTML for the selector. */ -function filter_default($text) { - if (variable_get('filter_html', FILTER_HTML_DONOTHING) == FILTER_HTML_STRIP) { - // Allow users to enter HTML, but filter it - $text = strip_tags($text, variable_get('allowed_html', '')); - if (variable_get('filter_style', FILTER_STYLE_STRIP)) { - $text = preg_replace('/\Wstyle\s*=[^>]+?>/i', '>', $text); +function filter_form($name, $value = FILTER_FORMAT_DEFAULT) { + if ($value == FILTER_FORMAT_DEFAULT) { + $value = variable_get('filter_default_format', 1); + } + $formats = filter_formats(); + + $extra = l(t('More information about formatting options'), 'filter/tips'); + + if (count($formats) > 1) { + // Multiple formats available: display radio buttons with tips. + $output = ''; + foreach ($formats as $format) { + $tips = _filter_tips($format->format, false); + + // TODO: get support for block-level radios so the <br /> is not output? + $output .= '<div>'; + $output .= '<label class="option"><input type="radio" class="form-radio" name="edit['. $name .']" value="'. $format->format .'"'. ($format->format == $value ? ' checked="checked"' : '') .' /> '. $format->name .'</label>'; + $output .= theme('filter_tips', $tips); + $output .= '</div>'; } - $text = preg_replace('/\Won[a-z]+\s*=[^>]+?>/i', '>', $text); + return theme('form_element', t('Input format'), $output, $extra, $name, _form_get_error($name)); } + else { + // Only one format available: use a hidden form item and only show tips. + $format = array_shift($formats); + $output = form_hidden($name, $format->format); + $tips = _filter_tips(variable_get('filter_default_format', 0), false); + $output .= form_item(t('Formatting guidelines'), theme('filter_tips', $tips, $extra), $extra); + return $output; + } +} - if (variable_get('filter_html', FILTER_HTML_DONOTHING) == FILTER_HTML_ESCAPE) { - // Escape HTML - $text = htmlspecialchars($text); +/** + * Returns true if the user is allowed to access this format. + */ +function filter_access($format) { + if (user_access('administer filters')) { + return true; } + else { + $formats = filter_formats(); + return isset($formats[$format]); + } +} +/* @} */ - return trim($text); +/** + * Menu callback: show page with long filter tips + */ +function filter_tips_long() { + $format = arg(2); + if ($format) { + $output = theme('filter_tips', _filter_tips($format, true)); + } + else { + $output = theme('filter_tips', _filter_tips(-1, true)); + } + print theme('page', $output, t('Compose Tips')); +} + +/** + * Helper function for fetching filter tips. + */ +function _filter_tips($format, $long = false) { + if ($format == -1) { + $formats = filter_formats(); + } + else { + $formats = array(db_fetch_object(db_query("SELECT * FROM {filter_formats} WHERE format = %d", $format))); + } + + $tips = array(); + + foreach ($formats as $format) { + $filters = filter_list_format($format->format); + + $tips[$format->name] = array(); + foreach ($filters as $id => $filter) { + if ($tip = module_invoke($filter->module, 'filter_tips', $filter->delta, $format->format, $long)) { + $tips[$format->name][] = array('tip' => $tip, 'id' => $id); + } + } + } + + return $tips; } /** - * Settings for the filter system's built-in HTML handling. + * @addtogroup themeable + * @{ */ -function filter_default_settings() { - $group = form_radios(t('Filter HTML tags'), 'filter_html', variable_get('filter_html', FILTER_HTML_DONOTHING), array(FILTER_HTML_DONOTHING => t('Do not filter'), FILTER_HTML_STRIP => t('Strip tags'), FILTER_HTML_ESCAPE => t('Escape tags')), t('How to deal with HTML and PHP tags in user-contributed content. If set to "Strip tags", dangerous tags are removed (see below). If set to "Escape tags", all HTML is escaped and presented as it was typed.')); - $group .= form_textfield(t('Allowed HTML tags'), 'allowed_html', variable_get('allowed_html', '<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>'), 64, 255, t('If "Strip tags" is selected, optionally specify tags which should not be stripped. "ON*" attributes are always stripped.')); - $group .= form_radios(t('HTML style attributes'), 'filter_style', variable_get('filter_style', FILTER_STYLE_STRIP), array(FILTER_STYLE_ALLOW => t('Allowed'), FILTER_STYLE_STRIP => t('Removed')), t('If "Strip tags" is selected, you can choose whether "STYLE" attributes are allowed or removed from input.')); - $output .= form_group(t('HTML filtering'), $group); + +/** + * Format filter tips + * + * @param $tips + * @param $long + * @param $extra + */ +function theme_filter_tips($tips, $long = false, $extra = '') { + $output = ''; + + $multiple = count($tips) > 1; + if ($multiple) { + $output = t('Input formats') .':'; + } + + if (count($tips)) { + if ($multiple) { + $output .= '<ul>'; + } + foreach ($tips as $name => $tiplist) { + if ($multiple) { + $output .= '<li>'; + $output .= '<strong>'. $name .'</strong>:<br />'; + } + + $output .= '<ul class="tips">'; + + foreach ($tiplist as $tip) { + $output .= '<li'. ($long ? ' id="'. $tip['id'] .'">' : '>') . $tip['tip'] . '</li>'; + } + + $output .= '</ul>'; + if ($multiple) { + $output .= '</li>'; + } + } + if ($multiple) { + $output .= '</ul>'; + } + } return $output; } +/** @} End of addtogroup themeable */ + /** - * Implementation of hook_filter(). Handles URL upgrades from Drupal 4.1. + * @name Standard filters + * + * Filters implemented by the filter.module. + * @{ */ -function filter_filter($op, $text = '') { + +/** + * Implementation of hook_filter(). Contains a basic set of essential filters: + * - HTML filter: transform/validate user-supplied HTML + * - PHP evaluator: execute PHP code + * - Legacy filter: handle URL upgrades from Drupal 4.1. + * - Line break convertor: convert newlines into paragraph and break tags + */ +function filter_filter($op, $delta = 0, $format = -1, $text = '') { switch ($op) { - case 'name': - return t('Legacy filtering'); + case 'list': + return array(0 => t('HTML filter'), 1 => t('PHP evaluator'), 2 => t('Legacy filter'), 3 => t('Line break convertor')); + + case 'no cache': + return $delta == 1; // No caching for the PHP evaluator + + case 'description': + switch ($delta) { + case 0: + return t('Allows you to restrict if users can post HTML and which tags to filter out.'); + case 1: + return t('Runs a piece of PHP code. The usage of this filter should be restricted to administrators only!'); + case 2: + return t('Replaces URLs from Drupal 4.1 (and lower) with updated equivalents.'); + case 3: + return t('Converts line breaks into HTML (i.e. <br> and <p> tags).'); + default: + return; + } + case 'process': - if (variable_get('rewrite_old_urls', 0)) { - $text = filter_old_urls($text); + switch ($delta) { + case 0: + return _filter_html($text, $format); + case 1: + return drupal_eval($text); + case 2: + return _filter_old_urls($text, $format); + case 3: + return _filter_autop($text, $format); + default: + return $text; } - return $text; + case 'settings': - $group = form_radios(t('Rewrite old URLs'), 'rewrite_old_urls', variable_get('rewrite_old_urls', 0), array(t('Disabled'), t('Enabled')), t('The introduction of "clean URLs" in Drupal 4.2.0 breaks internal URLs that date back from Drupal 4.1.0 and before. If enabled, this filter will attempt to rewrite the old style URLs to avoid broken links. If <code>mod_rewrite</code> is available on your system, use the rewrite rules in Drupal\'s <code>.htaccess</code> file instead as these will also correct external referrers.')); - $output .= form_group(t('Legacy filtering'), $group); - return $output; + switch ($delta) { + case 0: + return _filter_html_settings($format); + default: + return; + } + default: return $text; } } /** + * Settings for the HTML filter + */ +function _filter_html_settings($format) { + $group = form_radios(t('Filter HTML tags'), "filter_html_$format", variable_get("filter_html_$format", FILTER_HTML_STRIP), array(FILTER_HTML_STRIP => t('Strip tags'), FILTER_HTML_ESCAPE => t('Escape tags')), t('How to deal with HTML tags in user-contributed content. If set to "Strip tags", dangerous tags are removed (see below). If set to "Escape tags", all HTML is escaped and presented as it was typed.')); + $group .= form_textfield(t('Allowed HTML tags'), "allowed_html_$format", variable_get("allowed_html_$format", '<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>'), 64, 255, t('If "Strip tags" is selected, optionally specify tags which should not be stripped. Javascript event attributes are always stripped.')); + $group .= form_radios(t('HTML style attributes'), "filter_style_$format", variable_get("filter_style_$format", FILTER_STYLE_STRIP), array(FILTER_STYLE_ALLOW => t('Allowed'), FILTER_STYLE_STRIP => t('Removed')), t('If "Strip tags" is selected, you can choose whether "STYLE" attributes are allowed or removed from input.')); + $output .= form_group(t('HTML filter'), $group); + + return $output; +} + +/** + * HTML filter: provides filtering of input into accepted HTML + */ +function _filter_html($text, $format) { + if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_STRIP) { + // Allow users to enter HTML, but filter it + $text = strip_tags($text, variable_get("allowed_html_$format", '')); + if (variable_get("filter_style_$format", FILTER_STYLE_STRIP)) { + $text = preg_replace('/\Wstyle\s*=[^>]+?>/i', '>', $text); + } + $text = preg_replace('/\Won[a-z]+\s*=[^>]+?>/i', '>', $text); + } + + if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_ESCAPE) { + // Escape HTML + $text = htmlspecialchars($text); + } + + if (variable_get("filter_nl2br_$format", true)) { + $text = _filter_autop($text, true); + } + + return trim($text); +} + +/** * Rewrite legacy URLs. * * This is a *temporary* filter to rewrite old-style URLs to new-style @@ -258,7 +870,11 @@ function filter_filter($op, $text = '') { * enough, we will use them to permanently rewrite the links in node * and comment bodies. */ -function filter_old_urls($text) { +function _filter_old_urls($text) { + if (!variable_get('rewrite_old_urls', 0)) { + return $text; + } + global $base_url; $end = substr($base_url, 12); @@ -310,31 +926,30 @@ function filter_old_urls($text) { } /** - * Fetch full filter help texts defined by modules. + * Convert line breaks into <p> and <br> in an intelligent fashion. + * From: http://photomatt.net/scripts/autop */ -function filter_tips_long() { - $tiplist = ''; - foreach (module_list() as $name) { - if ($tip = module_invoke($name, 'help', 'filter#long-tip')) { - $tiplist .= "<li id=\"filter-$name\">$tip</li>\n"; - } - } - $output = "<ul class=\"filter-tips-long\">\n$tiplist\n</ul>\n"; - print theme('page', $output, t('Compose Tips')); -} +function _filter_autop($text, $br = 1) { + $text = preg_replace('|\n*$|', '', $text) ."\n\n"; // just to make things a little easier, pad the end + $text = preg_replace('|<br />\s*<br />|', "\n\n", $text); + $text = preg_replace('!(<(?:table|ul|ol|li|pre|form|blockquote|h[1-6])[^>]*>)!', "\n$1", $text); // Space things out a little + $text = preg_replace('!(</(?:table|ul|ol|li|pre|form|blockquote|h[1-6])>)!', "$1\n", $text); // Space things out a little + $text = preg_replace("/\n\n+/", "\n\n", $text); // take care of duplicates + $text = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "\t<p>$1</p>\n", $text); // make paragraphs, including one at the end + $text = preg_replace('|<p>\s*?</p>|', '', $text); // under certain strange conditions it could create a P of entirely whitespace + $text = preg_replace("|<p>(<li.+?)</p>|", "$1", $text); // problem with nested lists + $text = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $text); + $text = str_replace('</blockquote></p>', '</p></blockquote>', $text); + $text = preg_replace('!<p>\s*(</?(?:table|tr|td|th|div|ul|ol|li|pre|select|form|blockquote|p|h[1-6])[^>]*>)!', "$1", $text); + $text = preg_replace('!(</?(?:table|tr|td|th|div|ul|ol|li|pre|select|form|blockquote|p|h[1-6])[^>]*>)\s*</p>!', "$1", $text); + if ($br) $text = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $text); // optionally make line breaks + $text = preg_replace('!(</?(?:table|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|blockquote|p|h[1-6])[^>]*>)\s*<br />!', "$1", $text); + $text = preg_replace('!<br />(\s*</?(?:p|li|div|th|pre|td|ul|ol)>)!', '$1', $text); + $text = preg_replace('/&([^#])(?![a-z]{1,8};)/', '&$1', $text); -/** - * Fetch abbreviated filter help texts defined by modules. - */ -function filter_tips_short() { - $tiplist = ''; - foreach (module_list() as $name) { - if ($tip = module_invoke($name, 'help', 'filter#short-tip')) { - $tiplist .= "<li>$tip</li>\n"; - } - } - $tiplist .= '<li class="more-tips">' . l(t('More information on formatting options'), 'filter/tips') . '</li>'; - return "<ul class=\"filter-tips-short\">\n$tiplist\n</ul>\n"; + return $text; } +/* @} */ + ?> diff --git a/modules/filter/filter.module b/modules/filter/filter.module index dbb2eab99..f5e0834c1 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -1,7 +1,8 @@ <?php // $Id$ -define('FILTER_HTML_DONOTHING', 0); +define('FILTER_FORMAT_DEFAULT', 0); + define('FILTER_HTML_STRIP', 1); define('FILTER_HTML_ESCAPE', 2); @@ -12,52 +13,135 @@ define('FILTER_STYLE_STRIP', 1); * Implementation of hook_help(). */ function filter_help($section) { + // Get rid of variable numbers in the URL + $section = preg_replace('/[0-9]+/', '#', $section); + switch ($section) { case 'admin/modules#description': return t('Framework for handling filtering of content.'); + case 'admin/filters': - return t(" -<p>Filters fit between the raw text in posts and comments, and the HTML output. They allow you to replace text selectively. Uses include automatic conversion of emoticons into graphics and filtering HTML content from users' submissions.</p> -<p>If you notice some filters are causing conflicts in the output, you can <a href=\"%url\">rearrange them</a>.</p>", array('%url' => url('admin/filters/order'))); - case 'admin/filters/order': - return t(" + return t(' +<p><i>Input formats</i> define a way of processing user-supplied text in Drupal. Every input format has its own settings of which <i>filters</i> to apply. Possible filters include stripping out malicious HTML and making URLs clickable.</p> +<p>Users can choose between the available input formats when submitting content.</p> +<p>Below you can configure which input formats are available to which roles, as well as choose a default input format (used for imported content, for example).</p>'); + + case 'admin/filters/#': + return t(' +<p>Every <i>filter</i> performs one particular change on the user input, for example stripping out malicious HTML or making URLs clickable. Choose which filters you want to apply to text in this input format.</p> +<p>If you notice some filters are causing conflicts in the output, you can <a href="%order">rearrange them</a>.', array('%configure' => url('admin/filters/'. arg(2) .'/configure'), '%order' => url('admin/filters/'. arg(2) .'/order'))); + + case 'admin/filters/#/configure': + return t(' +<p>If you cannot find the settings for a certain filter, make sure you\'ve enabled it on the <a href="%url">list filters</a> tab first.</p>', array('%url' => url('admin/filters/'. arg(2) .'/list'))); + + case 'admin/filters/#/order': + return t(' <p>Because of the flexible filtering system, you might encounter a situation where one filter prevents another from doing its job. For example: a word in an URL gets converted into a glossary term, before the URL can be converted in a clickable link. When this happens, you will need to rearrange the order in which filters get executed.</p> -<p>Filters are executed from top-to-bottom. You can use the weight column to rearrange them: heavier filters 'sink' to the bottom. Standard HTML filtering is always run first.</p>"); - case 'filter#long-tip': - case 'filter#short-tip': - switch (variable_get('filter_html', FILTER_HTML_DONOTHING)) { - case 0: - return t('All HTML tags allowed'); - break; - case 1: - if ($allowed_html = variable_get('allowed_html', '<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>')) { +<p>Filters are executed from top-to-bottom. You can use the weight column to rearrange them: heavier filters \'sink\' to the bottom.</p>'); + } +} + +/** + * Implementation of hook_filter_tips. + */ +function filter_filter_tips($delta, $format, $type = false) { + switch ($delta) { + case 0: + switch (variable_get("filter_html_$format", FILTER_HTML_STRIP)) { + + case FILTER_HTML_STRIP: + if ($allowed_html = variable_get("allowed_html_$format", '<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>')) { return t('Allowed HTML tags') .': '. htmlspecialchars($allowed_html); - } else { + } + else { return t('No HTML tags allowed'); } - break; - case 2: + + case FILTER_STYLE_STRIP: return t('No HTML tags allowed'); - break; } break; + + case 1: + switch ($type) { + case 0: + return t('You may post PHP code. You should include <?php ?> tags.'); + case 1: + return t(' +<h4>Using custom PHP code</h4> +<p>If you know how to script in PHP, Drupal gives you the power to embed any script you like. It will be executed when the page is viewed and dynamically embedded into the page. This gives you amazing flexibility and power, but of course with that comes danger and insecurity if you don\'t write good code. If you are not familiar with PHP, SQL or with the site engine, avoid experimenting with PHP because you can corrupt your database or render your site insecure or even unusable! If you don\'t plan to do fancy stuff with your content then you\'re probably better off with straight HTML.</p> +<p>Remember that the code within each PHP item must be valid PHP code - including things like correctly terminating statements with a semicolon. It is highly recommended that you develop your code separately using a simple test script on top of a test database before migrating to your production environment.</p> +<p>Notes:</p><ul><li>You can use global variables, such as configuration parameters, within the scope of your PHP code but remember that global variables which have been given values in your code will retain these values in the engine afterwards.</li><li>register_globals is now set to <strong>off</strong> by default. If you need form information you need to get it from the "superglobals" $_POST, $_GET, etc.</li><li>You can either use the <code>print</code> or <code>return</code> statement to output the actual content for your item.</li></ul> +<p>A basic example:</p> +<blockquote><p>You want to have a box with the title "Welcome" that you use to greet your visitors. The content for this box could be created by going:</p> +<pre> + print t("Welcome visitor, ... welcome message goes here ..."); +</pre> +<p>If we are however dealing with a registered user, we can customize the message by using:</p> +<pre> + global $user; + if ($user->uid) { + print t("Welcome $user->name, ... welcome message goes here ..."); + } + else { + print t("Welcome visitor, ... welcome message goes here ..."); + } +</pre></blockquote> +<p>For more in-depth examples, we recommend that you check the existing Drupal code and use it as a starting point, especially for sidebar boxes.</p>'); + } + + case 3: + return t('Lines and paragraphs break automatically.'); + break; } } + /** * Implementation of hook_menu(). */ function filter_menu() { $items = array(); - $items[] = array('path' => 'admin/filters', 'title' => t('filters'), - 'callback' => 'filter_admin_settings', - 'access' => user_access('administer site configuration')); - $items[] = array('path' => 'admin/filters/configure', 'title' => t('configure'), - 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10); - $items[] = array('path' => 'admin/filters/order', 'title' => t('rearrange'), - 'callback' => 'filter_admin_order', - 'access' => user_access('administer site configuration'), - 'type' => MENU_LOCAL_TASK); + + $items[] = array('path' => 'admin/filters', 'title' => t('input formats'), + 'callback' => 'filter_admin_overview', + 'access' => user_access('administer filters')); + + $items[] = array('path' => 'admin/filters/delete', 'title' => t('delete input format'), + 'callback' => 'filter_admin_delete', + 'type' => MENU_CALLBACK, + 'access' => user_access('administer filters')); + + if (arg(0) == 'admin' && arg(1) == 'filters' && is_numeric(arg(2))) { + $formats = filter_formats(); + + if (isset($formats[arg(2)])) { + $items[] = array('path' => 'admin/filters/'. arg(2), 'title' => t("'%format' input format", array('%format' => $formats[arg(2)]->name)), + 'callback' => 'filter_admin_filters', + 'type' => MENU_CALLBACK, + 'access' => user_access('administer filters')); + + $items[] = array('path' => 'admin/filters/'. arg(2) .'/list', 'title' => t('list filters'), + 'callback' => 'filter_admin_filters', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 0, + 'access' => user_access('administer filters')); + + $items[] = array('path' => 'admin/filters/'. arg(2) .'/configure', 'title' => t('configure filters'), + 'callback' => 'filter_admin_configure', + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + 'access' => user_access('administer filters')); + + $items[] = array('path' => 'admin/filters/'. arg(2) .'/order', 'title' => t('rearrange filters'), + 'callback' => 'filter_admin_order', + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + 'access' => user_access('administer filters')); + } + } + $items[] = array('path' => 'filter/tips', 'title' => t('compose tips'), 'callback' => 'filter_tips_long', 'access' => TRUE, 'type' => MENU_SUGGESTED_ITEM); @@ -65,30 +149,271 @@ function filter_menu() { } /** - * Menu callback; allows administrators to change the filter ordering. + * Implementation of hook_perm() */ -function filter_admin_order() { +function filter_perm() { + return array('administer filters'); +} + +/** + * Menu callback: allows administrators to setup input formats + */ +function filter_admin_overview() { + // Process form submission + switch ($_POST['op']) { + case t('Save input formats'): + filter_admin_save(); + break; + case t('Add input format'): + filter_admin_add(); + break; + } + + // Overview of all formats + $formats = filter_formats(); + $roles = user_roles(); + $error = false; + + $header = array(t('name'), t('default')); + foreach ($roles as $name) { + $header[] = $name; + } + $header[] = array('data' => t('operations'), 'colspan' => 2); + + $rows = array(); + foreach ($formats as $id => $format) { + $row = array(); + $default = ($id == variable_get('filter_default_format', 1)); + + $row[] = form_textfield('', "name][$id", $format->name, 16, 255); + $row[] = form_radio('', 'default', $id, $default); + + foreach ($roles as $rid => $name) { + $checked = strstr($format->roles, ",$rid,"); + + if ($default && !$checked && !$error) { + form_set_error("roles][$id][$rid", t('The default input format must be accessible to every role.')); + $error = true; + } + + $row[] = form_checkbox('', "roles][$id][$rid", 1, $checked); + } + + $row[] = l('configure', 'admin/filters/'. $id); + $row[] = $default ? '' : l('delete', 'admin/filters/delete/'. $id); + + $rows[] = $row; + } + + $group = theme('table', $header, $rows); + $group .= form_submit(t('Save input formats')); + $output = '<h2>'. t('Permissions and settings') . '</h2>' . form($group); + + // Form to add a new format + $group = t("<p>To add a new input format, type its name here. After it has been added, you can configure its options.</p>"); + $form = form_textfield(t('Name'), 'name', '', 40, 255); + $form .= form_submit(t('Add input format')); + $group .= form($form); + $output .= '<h2>'. t('Add new input format') .'</h2>'. $group; + + print theme('page', $output); +} + +/** + * Save input formats on overview page + */ +function filter_admin_save() { $edit = $_POST['edit']; - $op = $_POST['op']; - if ($op == t('Save configuration')) { - foreach ($edit as $module => $filter) { - db_query("UPDATE {filters} SET weight = %d WHERE module = '%s'", $filter['weight'], $module); + + variable_set('filter_default_format', $edit['default']); + + foreach ($edit['name'] as $id => $name) { + $name = trim($name); + + if (strlen($name) == 0) { + drupal_set_message(t('You must enter a name for this input format.')); + drupal_goto('admin/filters'); + } + else { + db_query("UPDATE {filter_formats} SET name='%s' WHERE format = %d", $name, $id); + } + } + + // We store the roles as a string for ease of use. + // We use leading and trailing comma's to allow easy substring matching. + foreach ($edit['roles'] as $id => $format) { + $roles = ','; + foreach ($format as $rid => $value) { + if ($value) { + $roles .= $rid .','; + } } + db_query("UPDATE {filter_formats} SET roles = '%s' WHERE format = %d", $roles, $id); + } + + drupal_set_message(t('The input format settings have been updated.')); + drupal_goto('admin/filters'); +} + +/** + * Add new input format + */ +function filter_admin_add() { + $edit = $_POST['edit']; + + $name = trim($edit['name']); + + if (strlen($name) == 0) { + drupal_set_message(t('You must enter a name for this input format.')); + drupal_goto('admin/filters'); + } + else { + db_query("INSERT INTO {filter_formats} (name) VALUES ('%s')", $name); + } + + drupal_set_message(t("Added input format '%format'", array('%format' => $edit['name']))); + drupal_goto('admin/filters'); +} + +/** + * Menu callback: confirm deletion of a format + */ +function filter_admin_delete() { + $edit = $_POST['edit']; + if ($_POST['op'] == t('Confirm deletion')) { + if ($edit['format'] != variable_get('filter_default_format', 1)) { + db_query("DELETE FROM {filter_formats} WHERE format = %d", $edit['format']); + db_query("DELETE FROM {filters} WHERE format = %d", $edit['format']); + + $default = variable_get('filter_default_format', 1); + db_query("UPDATE {node} SET format = %d WHERE format = %d", $default, $edit['format']); + db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $edit['format']); + db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $edit['format']); + + cache_clear_all('filter:'. $edit['format'], true); + + drupal_set_message(t("Deleted input format '%format'", array('%format' => $edit['name']))); + } + drupal_goto('admin/filters'); + } + + $format = arg(3); + $format = db_fetch_object(db_query('SELECT * FROM {filter_formats} WHERE format = %d', $format)); + + $form .= form_hidden('format', $format->format); + $form .= form_hidden('name', $format->name); + $form .= '<p>'. t("Are you sure you want to delete the input format '%format'? If you have any content left in this input format, it will be switched to the default input format.", array('%format' => $format->name)) .'</p>'; + $form .= form_submit(t('Confirm deletion')); + print theme('page', form($form)); +} + +/** + * Ask for confirmation before deleting a format + */ +function filter_admin_confirm() { + $edit = $_POST['edit']['format']; + + + return form($form); +} + +/** + * Menu callback: configure the filters for a format. + */ +function filter_admin_filters() { + $format = arg(2); + + // Handle saving of weights + if ($_POST['op']) { + filter_admin_filters_save($format, $_POST['edit']); + } + + $all = filter_list_all(); + $enabled = filter_list_format($format); + + /* + ** Table with filters + */ + $header = array('enabled', 'name', 'description'); + $rows = array(); + foreach ($all as $id => $filter) { + $row = array(); + $row[] = form_checkbox('', $id, 1, isset($enabled[$id])); + $row[] = $filter->name; + $row[] = module_invoke($filter->module, 'filter', 'description', $filter->delta); + + $rows[] = $row; + } + $form = theme('table', $header, $rows); + if (!$empty) { + $form .= form_submit(t('Save configuration')); + } + + $output .= '<h2>'. t('Filters') .'</h2>'. form($form); + + /* + ** Compose tips (guidelines) + */ + $tips = _filter_tips($format, false); + $extra = l(t('More information about formatting options'), 'filter/tips'); + $tiplist = theme('filter_tips', $tips, $extra); + if (!$tiplist) { + $tiplist = t('<p>No guidelines available.</p>'); + } + $group = t('<p>These are the guidelines that users will see for posting in this input format. They are automatically generated from the filter settings.</p>'); + $group .= $tiplist; + $output .= '<h2>'. t('Formatting guidelines') .'</h2>'. $group; + + print theme('page', $output); +} + +/** + * Save enabled/disabled status for filters in a format. + */ +function filter_admin_filters_save($format, $toggles) { + $current = filter_list_format($format); + + $cache = true; + + db_query("DELETE FROM {filters} WHERE format = %d", $format); + foreach ($toggles as $id => $checked) { + if ($checked) { + list($module, $delta) = explode('/', $id); + // Add new filters to the bottom + $weight = isset($current[$id]->weight) ? $current[$id]->weight : 10; + db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", $format, $module, $delta, $weight); + + // Check if there are any 'no cache' filters + $cache &= !module_invoke($module, 'filter', 'no cache', $delta); + } + } + + // Update the format's 'no cache' flag. + db_query('UPDATE {filter_formats} SET cache = %d WHERE format = %d', (int)$cache, $format); + + cache_clear_all('filter:'. $format, true); + + drupal_set_message(t('The input format has been updated.')); + drupal_goto('admin/filters/'. arg(2) .'/list'); +} + +/** + * Menu callback: display form for ordering filters for a format. + */ +function filter_admin_order() { + $format = arg(2); + if ($_POST['op']) { + filter_admin_order_save($format, $_POST['edit']); } // Get list (with forced refresh) - filter_refresh(); - $filters = filter_list(); + $filters = filter_list_format($format); $header = array(t('name'), t('weight')); $rows = array(); - // Standard HTML filters are always run first, we add a dummy row to indicate this - $rows[] = array(t('HTML filtering'), array('data' => t('locked'))); - - foreach ($filters as $module => $filter) { - $name = module_invoke($module, 'filter', 'name'); - $rows[] = array($name, array('data' => form_weight(NULL, $module .'][weight', $filter['weight']))); + foreach ($filters as $id => $filter) { + $rows[] = array($filter->name, form_weight('', $id, $filter->weight)); } $form = theme('table', $header, $rows); @@ -99,93 +424,184 @@ function filter_admin_order() { } /** - * Menu callback; displays settings defined by filters. + * Save the weights of filters in a format. + */ +function filter_admin_order_save($format, $weights) { + foreach ($weights as $id => $weight) { + list($module, $delta) = explode('/', $id); + db_query("UPDATE filters SET weight = %d WHERE format = %d AND module = '%s' AND delta = %d", $weight, $format, $module, $delta); + } + drupal_set_message(t('The filter weights have been saved.')); + + cache_clear_all('filter:'. $format, true); + + drupal_goto('admin/filters/'. arg(2) .'/order'); +} + +/** + * Menu callback: display settings defined by filters. */ -function filter_admin_settings() { +function filter_admin_configure() { + $format = arg(2); + system_settings_save(); - filter_refresh(); + $list = filter_list_format($format); + $form = ""; + foreach ($list as $filter) { + $form .= module_invoke($filter->module, 'filter', 'settings', $filter->delta, $format); + } - $form = filter_default_settings(); - $form .= implode("\n", module_invoke_all('filter', 'settings')); - $output = system_settings_form($form); + if (trim($form) != '') { + $output = system_settings_form($form); + } + else { + $output = t('No settings are available.'); + } print theme('page', $output); } /** - * Search through all modules for the filters they implement. + * Retrieve a list of input formats. */ -function filter_refresh() { - $modules = module_list(); - $filters = filter_list(); +function filter_formats() { + global $user; + static $formats; + + // Administrators can always use all input formats. + $all = user_access('administer filters'); + + if (!isset($formats)) { + $formats = array(); + + $query = array('SELECT * FROM {filter_formats}'); - // Update list in database - db_query('DELETE FROM {filters}'); - foreach ($modules as $module) { - if (module_hook($module, 'filter')) { - $weight = $filters[$module]['weight']; + // Build query for selecting the format(s) based on the user's roles + if (!$all) { + $where = array(); + foreach ($user->roles as $rid => $role) { + $where[] = "roles LIKE '%%,%d,%%'"; + $query[] = $rid; + } + $query[0] .= ' WHERE '. implode(' OR ', $where); + } + + $result = call_user_func_array('db_query', $query); + while ($format = db_fetch_object($result)) { + $formats[$format->format] = $format; + } + } + return $formats; +} - db_query("INSERT INTO {filters} (module, weight) VALUES ('%s', %d)", $module, $weight); +/** + * Build a list of all filters. + */ +function filter_list_all() { + $filters = array(); + + foreach (module_list() as $module) { + $list = module_invoke($module, 'filter', 'list'); + if (is_array($list)) { + foreach ($list as $delta => $name) { + $filters[$module .'/'. $delta] = (object)array('module' => $module, 'delta' => $delta, 'name' => $name); + } } } - filter_list(1); + uasort($filters, '_filter_list_cmp'); + + return $filters; +} + +/** + * Helper function for sorting the filter list by filter name. + */ +function _filter_list_cmp($a, $b) { + return strcmp($a->name, $b->name); } /** - * Retrieve a list of all filters from the database. + * Check if text in a certain input format is allowed to be cached. */ -function filter_list($force = 0) { - static $filters; +function filter_format_allowcache($format) { + static $cache = array(); - if (!is_array($filters) || $force) { - $filters = array(); - $result = db_query('SELECT * FROM {filters} ORDER BY weight ASC'); - while ($filter = db_fetch_array($result)) { - // Fail-safe in case a module was deleted/changed without disabling it - if (module_hook($filter['module'], 'filter')) { - $filters[$filter['module']] = $filter; + if (!isset($cache[$format])) { + $cache[$format] = db_result(db_query('SELECT cache FROM {filter_formats} WHERE format = %d', $format)); + } + return $cache[$format]; +} + +/** + * Retrieve a list of filters for a certain format. + */ +function filter_list_format($format) { + static $filters = array(); + + if (!is_array($filters[$format])) { + $filters[$format] = array(); + $result = db_query("SELECT * FROM {filters} WHERE format = %d ORDER BY weight ASC", $format); + while ($filter = db_fetch_object($result)) { + $list = module_invoke($filter->module, 'filter', 'list'); + if (is_array($list) && isset($list[$filter->delta])) { + $filter->name = $list[$filter->delta]; + $filters[$format][$filter->module .'/'. $filter->delta] = $filter; } } } - return $filters; + return $filters[$format]; } /** + * @name Filtering functions + * + * Modules which need to have content filtered can use these functions to + * interact with the filter system. + * + * @{ + */ + +/** * Run all the enabled filters on a piece of text. */ -function check_output($text) { +function check_output($text, $format = FILTER_FORMAT_DEFAULT) { if (isset($text)) { + if ($format == FILTER_FORMAT_DEFAULT) { + $format = variable_get('filter_default_format', 1); + } + + // Check for a cached version of this piece of text + $id = 'filter:'. $format .':'. md5($text); + if ($cached = cache_get($id)) { + return $cached->data; + } + + // See if caching is allowed for this format + $cache = filter_format_allowcache($format); // Convert all Windows and Mac newlines to a single newline, // so filters only need to deal with this one $text = str_replace(array("\r\n", "\r"), "\n", $text); // Get complete list of filters ordered properly - $filters = filter_list(); + $filters = filter_list_format($format); // Give filters the chance to escape HTML-like data such as code or formulas. - // From this point on, the input can be treated as HTML. - if (variable_get('filter_html', FILTER_HTML_DONOTHING) != FILTER_HTML_ESCAPE) { - foreach ($filters as $module => $filter) { - $text = module_invoke($module, 'filter', 'prepare', $text); - } + foreach ($filters as $filter) { + $text = module_invoke($filter->module, 'filter', 'prepare', $filter->delta, $format, $text); } - // HTML handling is done before all regular filtering activities. - $text = filter_default($text); - - // Regular filtering. - foreach ($filters as $module => $filter) { - $text = module_invoke($module, 'filter', 'process', $text); + // Perform filtering + foreach ($filters as $filter) { + $text = module_invoke($filter->module, 'filter', 'process', $filter->delta, $format, $text); } - // If only inline elements are used and no block level elements, we - // replace all newlines with HTML line breaks. - if (strip_tags($text, '<a><br><span><bdo><map><object><img><tt><i><b><u><big><small><em><strong><dfn><code><q><samp><kbd><var><cite><abbr><acronym><sub><sup><input><select><textarea><label><button><ins><del><script>') == $text) { - $text = nl2br($text); + // Store in cache + if ($cache) { + cache_set($id, $text, 1); } } else { @@ -196,60 +612,256 @@ function check_output($text) { } /** - * Perform the default filters, preventing malicious HTML from being displayed. + * Generate selector for choosing a format in a form. + * + * @param $name + * The internal name used to refer to the selector. + * + * @param $value + * The id of the format that is currently selected. + * + * @return + * HTML for the selector. */ -function filter_default($text) { - if (variable_get('filter_html', FILTER_HTML_DONOTHING) == FILTER_HTML_STRIP) { - // Allow users to enter HTML, but filter it - $text = strip_tags($text, variable_get('allowed_html', '')); - if (variable_get('filter_style', FILTER_STYLE_STRIP)) { - $text = preg_replace('/\Wstyle\s*=[^>]+?>/i', '>', $text); +function filter_form($name, $value = FILTER_FORMAT_DEFAULT) { + if ($value == FILTER_FORMAT_DEFAULT) { + $value = variable_get('filter_default_format', 1); + } + $formats = filter_formats(); + + $extra = l(t('More information about formatting options'), 'filter/tips'); + + if (count($formats) > 1) { + // Multiple formats available: display radio buttons with tips. + $output = ''; + foreach ($formats as $format) { + $tips = _filter_tips($format->format, false); + + // TODO: get support for block-level radios so the <br /> is not output? + $output .= '<div>'; + $output .= '<label class="option"><input type="radio" class="form-radio" name="edit['. $name .']" value="'. $format->format .'"'. ($format->format == $value ? ' checked="checked"' : '') .' /> '. $format->name .'</label>'; + $output .= theme('filter_tips', $tips); + $output .= '</div>'; } - $text = preg_replace('/\Won[a-z]+\s*=[^>]+?>/i', '>', $text); + return theme('form_element', t('Input format'), $output, $extra, $name, _form_get_error($name)); } + else { + // Only one format available: use a hidden form item and only show tips. + $format = array_shift($formats); + $output = form_hidden($name, $format->format); + $tips = _filter_tips(variable_get('filter_default_format', 0), false); + $output .= form_item(t('Formatting guidelines'), theme('filter_tips', $tips, $extra), $extra); + return $output; + } +} - if (variable_get('filter_html', FILTER_HTML_DONOTHING) == FILTER_HTML_ESCAPE) { - // Escape HTML - $text = htmlspecialchars($text); +/** + * Returns true if the user is allowed to access this format. + */ +function filter_access($format) { + if (user_access('administer filters')) { + return true; } + else { + $formats = filter_formats(); + return isset($formats[$format]); + } +} +/* @} */ - return trim($text); +/** + * Menu callback: show page with long filter tips + */ +function filter_tips_long() { + $format = arg(2); + if ($format) { + $output = theme('filter_tips', _filter_tips($format, true)); + } + else { + $output = theme('filter_tips', _filter_tips(-1, true)); + } + print theme('page', $output, t('Compose Tips')); +} + +/** + * Helper function for fetching filter tips. + */ +function _filter_tips($format, $long = false) { + if ($format == -1) { + $formats = filter_formats(); + } + else { + $formats = array(db_fetch_object(db_query("SELECT * FROM {filter_formats} WHERE format = %d", $format))); + } + + $tips = array(); + + foreach ($formats as $format) { + $filters = filter_list_format($format->format); + + $tips[$format->name] = array(); + foreach ($filters as $id => $filter) { + if ($tip = module_invoke($filter->module, 'filter_tips', $filter->delta, $format->format, $long)) { + $tips[$format->name][] = array('tip' => $tip, 'id' => $id); + } + } + } + + return $tips; } /** - * Settings for the filter system's built-in HTML handling. + * @addtogroup themeable + * @{ */ -function filter_default_settings() { - $group = form_radios(t('Filter HTML tags'), 'filter_html', variable_get('filter_html', FILTER_HTML_DONOTHING), array(FILTER_HTML_DONOTHING => t('Do not filter'), FILTER_HTML_STRIP => t('Strip tags'), FILTER_HTML_ESCAPE => t('Escape tags')), t('How to deal with HTML and PHP tags in user-contributed content. If set to "Strip tags", dangerous tags are removed (see below). If set to "Escape tags", all HTML is escaped and presented as it was typed.')); - $group .= form_textfield(t('Allowed HTML tags'), 'allowed_html', variable_get('allowed_html', '<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>'), 64, 255, t('If "Strip tags" is selected, optionally specify tags which should not be stripped. "ON*" attributes are always stripped.')); - $group .= form_radios(t('HTML style attributes'), 'filter_style', variable_get('filter_style', FILTER_STYLE_STRIP), array(FILTER_STYLE_ALLOW => t('Allowed'), FILTER_STYLE_STRIP => t('Removed')), t('If "Strip tags" is selected, you can choose whether "STYLE" attributes are allowed or removed from input.')); - $output .= form_group(t('HTML filtering'), $group); + +/** + * Format filter tips + * + * @param $tips + * @param $long + * @param $extra + */ +function theme_filter_tips($tips, $long = false, $extra = '') { + $output = ''; + + $multiple = count($tips) > 1; + if ($multiple) { + $output = t('Input formats') .':'; + } + + if (count($tips)) { + if ($multiple) { + $output .= '<ul>'; + } + foreach ($tips as $name => $tiplist) { + if ($multiple) { + $output .= '<li>'; + $output .= '<strong>'. $name .'</strong>:<br />'; + } + + $output .= '<ul class="tips">'; + + foreach ($tiplist as $tip) { + $output .= '<li'. ($long ? ' id="'. $tip['id'] .'">' : '>') . $tip['tip'] . '</li>'; + } + + $output .= '</ul>'; + if ($multiple) { + $output .= '</li>'; + } + } + if ($multiple) { + $output .= '</ul>'; + } + } return $output; } +/** @} End of addtogroup themeable */ + /** - * Implementation of hook_filter(). Handles URL upgrades from Drupal 4.1. + * @name Standard filters + * + * Filters implemented by the filter.module. + * @{ */ -function filter_filter($op, $text = '') { + +/** + * Implementation of hook_filter(). Contains a basic set of essential filters: + * - HTML filter: transform/validate user-supplied HTML + * - PHP evaluator: execute PHP code + * - Legacy filter: handle URL upgrades from Drupal 4.1. + * - Line break convertor: convert newlines into paragraph and break tags + */ +function filter_filter($op, $delta = 0, $format = -1, $text = '') { switch ($op) { - case 'name': - return t('Legacy filtering'); + case 'list': + return array(0 => t('HTML filter'), 1 => t('PHP evaluator'), 2 => t('Legacy filter'), 3 => t('Line break convertor')); + + case 'no cache': + return $delta == 1; // No caching for the PHP evaluator + + case 'description': + switch ($delta) { + case 0: + return t('Allows you to restrict if users can post HTML and which tags to filter out.'); + case 1: + return t('Runs a piece of PHP code. The usage of this filter should be restricted to administrators only!'); + case 2: + return t('Replaces URLs from Drupal 4.1 (and lower) with updated equivalents.'); + case 3: + return t('Converts line breaks into HTML (i.e. <br> and <p> tags).'); + default: + return; + } + case 'process': - if (variable_get('rewrite_old_urls', 0)) { - $text = filter_old_urls($text); + switch ($delta) { + case 0: + return _filter_html($text, $format); + case 1: + return drupal_eval($text); + case 2: + return _filter_old_urls($text, $format); + case 3: + return _filter_autop($text, $format); + default: + return $text; } - return $text; + case 'settings': - $group = form_radios(t('Rewrite old URLs'), 'rewrite_old_urls', variable_get('rewrite_old_urls', 0), array(t('Disabled'), t('Enabled')), t('The introduction of "clean URLs" in Drupal 4.2.0 breaks internal URLs that date back from Drupal 4.1.0 and before. If enabled, this filter will attempt to rewrite the old style URLs to avoid broken links. If <code>mod_rewrite</code> is available on your system, use the rewrite rules in Drupal\'s <code>.htaccess</code> file instead as these will also correct external referrers.')); - $output .= form_group(t('Legacy filtering'), $group); - return $output; + switch ($delta) { + case 0: + return _filter_html_settings($format); + default: + return; + } + default: return $text; } } /** + * Settings for the HTML filter + */ +function _filter_html_settings($format) { + $group = form_radios(t('Filter HTML tags'), "filter_html_$format", variable_get("filter_html_$format", FILTER_HTML_STRIP), array(FILTER_HTML_STRIP => t('Strip tags'), FILTER_HTML_ESCAPE => t('Escape tags')), t('How to deal with HTML tags in user-contributed content. If set to "Strip tags", dangerous tags are removed (see below). If set to "Escape tags", all HTML is escaped and presented as it was typed.')); + $group .= form_textfield(t('Allowed HTML tags'), "allowed_html_$format", variable_get("allowed_html_$format", '<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>'), 64, 255, t('If "Strip tags" is selected, optionally specify tags which should not be stripped. Javascript event attributes are always stripped.')); + $group .= form_radios(t('HTML style attributes'), "filter_style_$format", variable_get("filter_style_$format", FILTER_STYLE_STRIP), array(FILTER_STYLE_ALLOW => t('Allowed'), FILTER_STYLE_STRIP => t('Removed')), t('If "Strip tags" is selected, you can choose whether "STYLE" attributes are allowed or removed from input.')); + $output .= form_group(t('HTML filter'), $group); + + return $output; +} + +/** + * HTML filter: provides filtering of input into accepted HTML + */ +function _filter_html($text, $format) { + if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_STRIP) { + // Allow users to enter HTML, but filter it + $text = strip_tags($text, variable_get("allowed_html_$format", '')); + if (variable_get("filter_style_$format", FILTER_STYLE_STRIP)) { + $text = preg_replace('/\Wstyle\s*=[^>]+?>/i', '>', $text); + } + $text = preg_replace('/\Won[a-z]+\s*=[^>]+?>/i', '>', $text); + } + + if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_ESCAPE) { + // Escape HTML + $text = htmlspecialchars($text); + } + + if (variable_get("filter_nl2br_$format", true)) { + $text = _filter_autop($text, true); + } + + return trim($text); +} + +/** * Rewrite legacy URLs. * * This is a *temporary* filter to rewrite old-style URLs to new-style @@ -258,7 +870,11 @@ function filter_filter($op, $text = '') { * enough, we will use them to permanently rewrite the links in node * and comment bodies. */ -function filter_old_urls($text) { +function _filter_old_urls($text) { + if (!variable_get('rewrite_old_urls', 0)) { + return $text; + } + global $base_url; $end = substr($base_url, 12); @@ -310,31 +926,30 @@ function filter_old_urls($text) { } /** - * Fetch full filter help texts defined by modules. + * Convert line breaks into <p> and <br> in an intelligent fashion. + * From: http://photomatt.net/scripts/autop */ -function filter_tips_long() { - $tiplist = ''; - foreach (module_list() as $name) { - if ($tip = module_invoke($name, 'help', 'filter#long-tip')) { - $tiplist .= "<li id=\"filter-$name\">$tip</li>\n"; - } - } - $output = "<ul class=\"filter-tips-long\">\n$tiplist\n</ul>\n"; - print theme('page', $output, t('Compose Tips')); -} +function _filter_autop($text, $br = 1) { + $text = preg_replace('|\n*$|', '', $text) ."\n\n"; // just to make things a little easier, pad the end + $text = preg_replace('|<br />\s*<br />|', "\n\n", $text); + $text = preg_replace('!(<(?:table|ul|ol|li|pre|form|blockquote|h[1-6])[^>]*>)!', "\n$1", $text); // Space things out a little + $text = preg_replace('!(</(?:table|ul|ol|li|pre|form|blockquote|h[1-6])>)!', "$1\n", $text); // Space things out a little + $text = preg_replace("/\n\n+/", "\n\n", $text); // take care of duplicates + $text = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "\t<p>$1</p>\n", $text); // make paragraphs, including one at the end + $text = preg_replace('|<p>\s*?</p>|', '', $text); // under certain strange conditions it could create a P of entirely whitespace + $text = preg_replace("|<p>(<li.+?)</p>|", "$1", $text); // problem with nested lists + $text = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $text); + $text = str_replace('</blockquote></p>', '</p></blockquote>', $text); + $text = preg_replace('!<p>\s*(</?(?:table|tr|td|th|div|ul|ol|li|pre|select|form|blockquote|p|h[1-6])[^>]*>)!', "$1", $text); + $text = preg_replace('!(</?(?:table|tr|td|th|div|ul|ol|li|pre|select|form|blockquote|p|h[1-6])[^>]*>)\s*</p>!', "$1", $text); + if ($br) $text = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $text); // optionally make line breaks + $text = preg_replace('!(</?(?:table|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|blockquote|p|h[1-6])[^>]*>)\s*<br />!', "$1", $text); + $text = preg_replace('!<br />(\s*</?(?:p|li|div|th|pre|td|ul|ol)>)!', '$1', $text); + $text = preg_replace('/&([^#])(?![a-z]{1,8};)/', '&$1', $text); -/** - * Fetch abbreviated filter help texts defined by modules. - */ -function filter_tips_short() { - $tiplist = ''; - foreach (module_list() as $name) { - if ($tip = module_invoke($name, 'help', 'filter#short-tip')) { - $tiplist .= "<li>$tip</li>\n"; - } - } - $tiplist .= '<li class="more-tips">' . l(t('More information on formatting options'), 'filter/tips') . '</li>'; - return "<ul class=\"filter-tips-short\">\n$tiplist\n</ul>\n"; + return $text; } +/* @} */ + ?> diff --git a/modules/forum.module b/modules/forum.module index d20ccb5cf..017e5e813 100644 --- a/modules/forum.module +++ b/modules/forum.module @@ -268,7 +268,7 @@ function forum_form(&$node) { $output .= form_checkbox(t('Leave shadow copy'), 'shadow', 1, $node->shadow, t('If you move this topic, you can leave a link in the old forum to the new forum.')); } - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 10, filter_tips_short()); + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 10, ''); return $output; } diff --git a/modules/forum/forum.module b/modules/forum/forum.module index d20ccb5cf..017e5e813 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -268,7 +268,7 @@ function forum_form(&$node) { $output .= form_checkbox(t('Leave shadow copy'), 'shadow', 1, $node->shadow, t('If you move this topic, you can leave a link in the old forum to the new forum.')); } - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 10, filter_tips_short()); + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 10, ''); return $output; } diff --git a/modules/node.module b/modules/node.module index 1a35d6c74..e01353ec6 100644 --- a/modules/node.module +++ b/modules/node.module @@ -168,6 +168,11 @@ function node_teaser($body) { return $body; } + // If the body contains PHP code, do not split it up to prevent parse errors. + if (strpos($body, '<?') != false) { + return $body; + } + // If a valid delimiter has been specified, use it to chop of the teaser. if ($delimiter > 0) { return substr($body, 0, $delimiter); @@ -511,10 +516,10 @@ function node_view($node, $teaser = FALSE, $page = FALSE) { function node_prepare($node, $teaser = FALSE) { $node->readmore = (strlen($node->teaser) < strlen($node->body)); if ($teaser == FALSE) { - $node->body = check_output($node->body); + $node->body = check_output($node->body, $node->format); } else { - $node->teaser = check_output($node->teaser); + $node->teaser = check_output($node->teaser, $node->format); } return $node; } @@ -999,7 +1004,7 @@ function node_feed($nodes = 0, $channel = array()) { // Load the specified node: $item = node_load(array('nid' => $node->nid)); $link = url("node/$node->nid", NULL, NULL, 1); - $items .= format_rss_item($item->title, $link, ($item->teaser ? $item->teaser : $item->body), array('pubDate' => date('r', $item->changed))); + $items .= format_rss_item($item->title, $link, check_output($item->teaser ? $item->teaser : $item->body, $item->format), array('pubDate' => date('r', $item->changed))); } $channel_defaults = array( @@ -1096,6 +1101,11 @@ function node_validate($node) { node_invoke($node, 'validate'); node_invoke_nodeapi($node, 'validate'); + // Check input format access + if (!filter_access($node->format)) { + form_set_error('format', t('The supplied input format is invalid.')); + } + $node->validated = TRUE; return $node; @@ -1156,6 +1166,10 @@ function node_form($edit) { $output .= '<div class="standard">'; $output .= form_textfield(t('Title'), 'title', $edit->title, 60, 128, NULL, NULL, TRUE); + // Add filter format selector / filter tips + + $output .= filter_form('format', $edit->format); + // Add the node-type-specific fields. $output .= $form; @@ -1516,7 +1530,7 @@ function node_nodeapi(&$node, $op, $arg = 0) { $output[t('revision')] = form_checkbox('', "node_revision_$node->type", 1, variable_get("node_revision_$node->type", 0)); return $output; case 'fields': - return array('nid', 'uid', 'type', 'title', 'teaser', 'body', 'revisions', 'status', 'promote', 'moderate', 'sticky', 'created', 'changed'); + return array('nid', 'uid', 'type', 'title', 'teaser', 'body', 'revisions', 'status', 'promote', 'moderate', 'sticky', 'created', 'changed', 'format'); } } @@ -1568,13 +1582,18 @@ function node_nodeapi(&$node, $op, $arg = 0) { * TRUE if the operation may be performed. */ function node_access($op, $node = NULL) { + // Convert the node to an object if necessary: + $node = array2object($node); + + // If the node is in a restricted format, disallow editing. + if ($op == 'update' && !filter_access($node->format)) { + return FALSE; + } + if (user_access('administer nodes')) { return TRUE; } - // Convert the node to an object if necessary: - $node = array2object($node); - // Can't use node_invoke(), because the access hook takes the $op parameter // before the $node parameter. $access = module_invoke(node_get_module_name($node), 'access', $op, $node); diff --git a/modules/node/node.module b/modules/node/node.module index 1a35d6c74..e01353ec6 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -168,6 +168,11 @@ function node_teaser($body) { return $body; } + // If the body contains PHP code, do not split it up to prevent parse errors. + if (strpos($body, '<?') != false) { + return $body; + } + // If a valid delimiter has been specified, use it to chop of the teaser. if ($delimiter > 0) { return substr($body, 0, $delimiter); @@ -511,10 +516,10 @@ function node_view($node, $teaser = FALSE, $page = FALSE) { function node_prepare($node, $teaser = FALSE) { $node->readmore = (strlen($node->teaser) < strlen($node->body)); if ($teaser == FALSE) { - $node->body = check_output($node->body); + $node->body = check_output($node->body, $node->format); } else { - $node->teaser = check_output($node->teaser); + $node->teaser = check_output($node->teaser, $node->format); } return $node; } @@ -999,7 +1004,7 @@ function node_feed($nodes = 0, $channel = array()) { // Load the specified node: $item = node_load(array('nid' => $node->nid)); $link = url("node/$node->nid", NULL, NULL, 1); - $items .= format_rss_item($item->title, $link, ($item->teaser ? $item->teaser : $item->body), array('pubDate' => date('r', $item->changed))); + $items .= format_rss_item($item->title, $link, check_output($item->teaser ? $item->teaser : $item->body, $item->format), array('pubDate' => date('r', $item->changed))); } $channel_defaults = array( @@ -1096,6 +1101,11 @@ function node_validate($node) { node_invoke($node, 'validate'); node_invoke_nodeapi($node, 'validate'); + // Check input format access + if (!filter_access($node->format)) { + form_set_error('format', t('The supplied input format is invalid.')); + } + $node->validated = TRUE; return $node; @@ -1156,6 +1166,10 @@ function node_form($edit) { $output .= '<div class="standard">'; $output .= form_textfield(t('Title'), 'title', $edit->title, 60, 128, NULL, NULL, TRUE); + // Add filter format selector / filter tips + + $output .= filter_form('format', $edit->format); + // Add the node-type-specific fields. $output .= $form; @@ -1516,7 +1530,7 @@ function node_nodeapi(&$node, $op, $arg = 0) { $output[t('revision')] = form_checkbox('', "node_revision_$node->type", 1, variable_get("node_revision_$node->type", 0)); return $output; case 'fields': - return array('nid', 'uid', 'type', 'title', 'teaser', 'body', 'revisions', 'status', 'promote', 'moderate', 'sticky', 'created', 'changed'); + return array('nid', 'uid', 'type', 'title', 'teaser', 'body', 'revisions', 'status', 'promote', 'moderate', 'sticky', 'created', 'changed', 'format'); } } @@ -1568,13 +1582,18 @@ function node_nodeapi(&$node, $op, $arg = 0) { * TRUE if the operation may be performed. */ function node_access($op, $node = NULL) { + // Convert the node to an object if necessary: + $node = array2object($node); + + // If the node is in a restricted format, disallow editing. + if ($op == 'update' && !filter_access($node->format)) { + return FALSE; + } + if (user_access('administer nodes')) { return TRUE; } - // Convert the node to an object if necessary: - $node = array2object($node); - // Can't use node_invoke(), because the access hook takes the $op parameter // before the $node parameter. $access = module_invoke(node_get_module_name($node), 'access', $op, $node); diff --git a/modules/page.module b/modules/page.module index ac3f6d4fb..4d0336172 100644 --- a/modules/page.module +++ b/modules/page.module @@ -9,7 +9,6 @@ function page_help($section) { case 'admin/help#page': return t(" <p>The page module is used when you want to create content that optionally inserts a link into your navigation system. You can also, however, create pages that don't have this link by skipping the link text field in the page form. At this time, not all themes support the link insertion behavior. Some themes, like xtemplate, provide alternative mechanisms for link creation. Pages are also unique in that they shortcut the typical lifecycle of user generated content (i.e. submit -> moderate -> post -> comment). </p> - <p>If you enable the <strong>create PHP content</strong> permission for a role, pages may consist of PHP code in addition to HTML and text.</p> <h3>User access permissions for pages</h3> <p><strong>create pages:</strong> Allows a role to create pages. They cannot edit or delete pages, even if they are the authors. You must enable this permission to in order for a role to create a page.</p> <p><strong>edit own pages:</strong> Allows a role to add/edit pages if they own the page. Use this permission if you want users to be able to edit and maintain their own pages.</p> @@ -56,14 +55,14 @@ function page_access($op, $node) { * Implementation of hook_insert(). */ function page_insert($node) { - db_query("INSERT INTO {page} (nid, format, link, description) VALUES (%d, %d, '%s', '%s')", $node->nid, $node->format, $node->link, $node->description); + db_query("INSERT INTO {page} (nid, link, description) VALUES (%d, '%s', '%s')", $node->nid, $node->link, $node->description); } /** * Implementation of hook_update(). */ function page_update($node) { - db_query("UPDATE {page} SET format = %d, link = '%s', description = '%s' WHERE nid = %d", $node->format, $node->link, $node->description, $node->nid); + db_query("UPDATE {page} SET link = '%s', description = '%s' WHERE nid = %d", $node->link, $node->description, $node->nid); } /** @@ -77,7 +76,7 @@ function page_delete(&$node) { * Implementation of hook_load(). */ function page_load($node) { - return db_fetch_object(db_query('SELECT format, link, description FROM {page} WHERE nid = %d', $node->nid)); + return db_fetch_object(db_query('SELECT link, description FROM {page} WHERE nid = %d', $node->nid)); } /** @@ -92,23 +91,9 @@ function page_menu() { /** * Implementation of hook_content(). - * - * If body is dynamic (using PHP code), the body will be generated. */ function page_content($node, $teaser = FALSE) { - if ($node->format == 1) { - // PHP type - ob_start(); - eval($node->body); - $node->body = ob_get_contents(); - ob_end_clean(); - $node->teaser = node_teaser($node->body); - $node->readmore = (strlen($node->teaser) < strlen($node->body)); - } - else { - // Assume HTML type by default - $node = node_prepare($node, $teaser); - } + $node = node_prepare($node, $teaser); return $node; } @@ -128,38 +113,12 @@ function page_form(&$node) { $output .= implode('', taxonomy_node_form('page', $node)); } - if (($node->format == 1) && (!user_access('create php content'))) { - drupal_set_message(t('the body contents of this page are written in PHP and you do not have sufficient permissions to make changes to the body. You can edit the other elements of the page.')); - $output .= form_hidden('format', $node->format); - $hide_types = true; - } - else { - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, filter_tips_short(), NULL, TRUE); - } + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE); $output .= form_textfield(t('Link name'), 'link', $node->link, 60, 64, t('To make the page show up in the navigation links, enter the name of the link. Otherwise, leave this blank.')); $output .= form_textfield(t('Link description'), 'description', $node->description, 60, 64, t("The description displayed when hovering over the page's link. Leave blank when you don't want a description.")); - $content_type = (user_access('create php content')) ? array(0 => 'HTML', 1 => 'PHP') : false; - if (!$hide_types && $content_type) { - $output .= form_radios(t('Type'), 'format', $node->format, $content_type); - } return $output; } -/** - * Implementation of hook_validate(). - */ -function page_validate(&$node) { - if ($node->format && user_access('create php content')) { - // Do not filter PHP code, do not auto-extract a teaser - $node->teaser = $node->body; - } - - if (($node->format == 1) && (!user_access('create php content'))) { - /* Overwrite the submitted node body since they don't have sufficient privileges. */ - $node->body = db_result(db_query('SELECT body FROM {node} WHERE nid = %d', $node->nid)); - } -} - ?> diff --git a/modules/page/page.module b/modules/page/page.module index ac3f6d4fb..4d0336172 100644 --- a/modules/page/page.module +++ b/modules/page/page.module @@ -9,7 +9,6 @@ function page_help($section) { case 'admin/help#page': return t(" <p>The page module is used when you want to create content that optionally inserts a link into your navigation system. You can also, however, create pages that don't have this link by skipping the link text field in the page form. At this time, not all themes support the link insertion behavior. Some themes, like xtemplate, provide alternative mechanisms for link creation. Pages are also unique in that they shortcut the typical lifecycle of user generated content (i.e. submit -> moderate -> post -> comment). </p> - <p>If you enable the <strong>create PHP content</strong> permission for a role, pages may consist of PHP code in addition to HTML and text.</p> <h3>User access permissions for pages</h3> <p><strong>create pages:</strong> Allows a role to create pages. They cannot edit or delete pages, even if they are the authors. You must enable this permission to in order for a role to create a page.</p> <p><strong>edit own pages:</strong> Allows a role to add/edit pages if they own the page. Use this permission if you want users to be able to edit and maintain their own pages.</p> @@ -56,14 +55,14 @@ function page_access($op, $node) { * Implementation of hook_insert(). */ function page_insert($node) { - db_query("INSERT INTO {page} (nid, format, link, description) VALUES (%d, %d, '%s', '%s')", $node->nid, $node->format, $node->link, $node->description); + db_query("INSERT INTO {page} (nid, link, description) VALUES (%d, '%s', '%s')", $node->nid, $node->link, $node->description); } /** * Implementation of hook_update(). */ function page_update($node) { - db_query("UPDATE {page} SET format = %d, link = '%s', description = '%s' WHERE nid = %d", $node->format, $node->link, $node->description, $node->nid); + db_query("UPDATE {page} SET link = '%s', description = '%s' WHERE nid = %d", $node->link, $node->description, $node->nid); } /** @@ -77,7 +76,7 @@ function page_delete(&$node) { * Implementation of hook_load(). */ function page_load($node) { - return db_fetch_object(db_query('SELECT format, link, description FROM {page} WHERE nid = %d', $node->nid)); + return db_fetch_object(db_query('SELECT link, description FROM {page} WHERE nid = %d', $node->nid)); } /** @@ -92,23 +91,9 @@ function page_menu() { /** * Implementation of hook_content(). - * - * If body is dynamic (using PHP code), the body will be generated. */ function page_content($node, $teaser = FALSE) { - if ($node->format == 1) { - // PHP type - ob_start(); - eval($node->body); - $node->body = ob_get_contents(); - ob_end_clean(); - $node->teaser = node_teaser($node->body); - $node->readmore = (strlen($node->teaser) < strlen($node->body)); - } - else { - // Assume HTML type by default - $node = node_prepare($node, $teaser); - } + $node = node_prepare($node, $teaser); return $node; } @@ -128,38 +113,12 @@ function page_form(&$node) { $output .= implode('', taxonomy_node_form('page', $node)); } - if (($node->format == 1) && (!user_access('create php content'))) { - drupal_set_message(t('the body contents of this page are written in PHP and you do not have sufficient permissions to make changes to the body. You can edit the other elements of the page.')); - $output .= form_hidden('format', $node->format); - $hide_types = true; - } - else { - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, filter_tips_short(), NULL, TRUE); - } + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 20, '', NULL, TRUE); $output .= form_textfield(t('Link name'), 'link', $node->link, 60, 64, t('To make the page show up in the navigation links, enter the name of the link. Otherwise, leave this blank.')); $output .= form_textfield(t('Link description'), 'description', $node->description, 60, 64, t("The description displayed when hovering over the page's link. Leave blank when you don't want a description.")); - $content_type = (user_access('create php content')) ? array(0 => 'HTML', 1 => 'PHP') : false; - if (!$hide_types && $content_type) { - $output .= form_radios(t('Type'), 'format', $node->format, $content_type); - } return $output; } -/** - * Implementation of hook_validate(). - */ -function page_validate(&$node) { - if ($node->format && user_access('create php content')) { - // Do not filter PHP code, do not auto-extract a teaser - $node->teaser = $node->body; - } - - if (($node->format == 1) && (!user_access('create php content'))) { - /* Overwrite the submitted node body since they don't have sufficient privileges. */ - $node->body = db_result(db_query('SELECT body FROM {node} WHERE nid = %d', $node->nid)); - } -} - ?> diff --git a/modules/statistics.module b/modules/statistics.module index 2d0d2937c..846dbe866 100644 --- a/modules/statistics.module +++ b/modules/statistics.module @@ -619,7 +619,7 @@ function statistics_summary($dbfield, $dbrows) { $links = link_node($content, 1); $output .= '<tr><td><strong>'. l($nid['title'], 'node/'. $nid['nid'], array('title' => t('View this posting.'))) .'</strong></td><td style="text-align: right;"><small>'. t('Submitted by %a on %b', array('%a' => format_name($content), '%b' => format_date($content->created, 'large'))) .'</small></td></tr>'; - $output .= '<tr><td colspan="2"><div style="margin-left: 20px;">'. check_output($content->teaser) .'</div></td></tr>'; + $output .= '<tr><td colspan="2"><div style="margin-left: 20px;">'. check_output($content->teaser, $content->format) .'</div></td></tr>'; $output .= '<tr><td style="text-align: right;" colspan="2">[ '. theme('links', $links) .' ]<br /><br /></td></tr>'; } diff --git a/modules/statistics/statistics.module b/modules/statistics/statistics.module index 2d0d2937c..846dbe866 100644 --- a/modules/statistics/statistics.module +++ b/modules/statistics/statistics.module @@ -619,7 +619,7 @@ function statistics_summary($dbfield, $dbrows) { $links = link_node($content, 1); $output .= '<tr><td><strong>'. l($nid['title'], 'node/'. $nid['nid'], array('title' => t('View this posting.'))) .'</strong></td><td style="text-align: right;"><small>'. t('Submitted by %a on %b', array('%a' => format_name($content), '%b' => format_date($content->created, 'large'))) .'</small></td></tr>'; - $output .= '<tr><td colspan="2"><div style="margin-left: 20px;">'. check_output($content->teaser) .'</div></td></tr>'; + $output .= '<tr><td colspan="2"><div style="margin-left: 20px;">'. check_output($content->teaser, $content->format) .'</div></td></tr>'; $output .= '<tr><td style="text-align: right;" colspan="2">[ '. theme('links', $links) .' ]<br /><br /></td></tr>'; } diff --git a/modules/story.module b/modules/story.module index 0efb933e3..a29e2efc8 100644 --- a/modules/story.module +++ b/modules/story.module @@ -113,7 +113,7 @@ function story_form(&$node) { $output .= implode('', taxonomy_node_form('story', $node)); } - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 15, filter_tips_short(), NULL, TRUE); + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 15, '', NULL, TRUE); return $output; } diff --git a/modules/story/story.module b/modules/story/story.module index 0efb933e3..a29e2efc8 100644 --- a/modules/story/story.module +++ b/modules/story/story.module @@ -113,7 +113,7 @@ function story_form(&$node) { $output .= implode('', taxonomy_node_form('story', $node)); } - $output .= form_textarea(t('Body'), 'body', $node->body, 60, 15, filter_tips_short(), NULL, TRUE); + $output .= form_textarea(t('Body'), 'body', $node->body, 60, 15, '', NULL, TRUE); return $output; } diff --git a/modules/system.module b/modules/system.module index 1fa3e93e5..8187ae08f 100644 --- a/modules/system.module +++ b/modules/system.module @@ -43,7 +43,7 @@ function system_help_page() { * Implementation of hook_perm(). */ function system_perm() { - return array('administer site configuration', 'access administration pages', 'bypass input data check', 'create php content'); + return array('administer site configuration', 'access administration pages', 'bypass input data check'); } /** diff --git a/modules/system/system.module b/modules/system/system.module index 1fa3e93e5..8187ae08f 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -43,7 +43,7 @@ function system_help_page() { * Implementation of hook_perm(). */ function system_perm() { - return array('administer site configuration', 'access administration pages', 'bypass input data check', 'create php content'); + return array('administer site configuration', 'access administration pages', 'bypass input data check'); } /** |