| 1 |
<?php
|
| 2 |
// $Id: $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* Adds the support for css on node creation.
|
| 7 |
* We add the ability for each node to have an attached css
|
| 8 |
*
|
| 9 |
* @author Fabio Varesano <fvaresano at yahoo dot it>
|
| 10 |
* @updated to Drupal 5 by Christopher Skauss <christopher skauss at gmail dot com>
|
| 11 |
* @modified drupal 5 Version by Whispero
|
| 12 |
* @updated for Drupal 6 by Joshua Chan <josh at joshuachan dot ca>
|
| 13 |
*
|
| 14 |
* To store this extra information, we need an auxiliary database table.
|
| 15 |
*
|
| 16 |
* Database definition:
|
| 17 |
* @code
|
| 18 |
CREATE TABLE css (
|
| 19 |
nid int(10) unsigned NOT NULL default '0',
|
| 20 |
css text NULL default NULL,
|
| 21 |
PRIMARY KEY (nid)
|
| 22 |
)
|
| 23 |
* @endcode
|
| 24 |
*/
|
| 25 |
|
| 26 |
/**
|
| 27 |
* Implementation of hook_help().
|
| 28 |
*/
|
| 29 |
function css_help($section) {
|
| 30 |
switch ($section) {
|
| 31 |
case 'admin/modules#description':
|
| 32 |
// This description is shown in the listing at admin/modules.
|
| 33 |
return t('A module which add customizable CSS support.');
|
| 34 |
}
|
| 35 |
}
|
| 36 |
|
| 37 |
/**
|
| 38 |
* Implementation of hook_menu()
|
| 39 |
*/
|
| 40 |
function css_menu() {
|
| 41 |
$items = array();
|
| 42 |
// defines the callback for getting the css file. we use
|
| 43 |
// css/get as path instead of only css to avoid that in some
|
| 44 |
// installs users have yet a directory called css
|
| 45 |
$items['css/get'] = array(
|
| 46 |
'title' => 'css',
|
| 47 |
'path' => 'css/get',
|
| 48 |
'page callback' => 'css_get',
|
| 49 |
'access arguments' => array('access content'),
|
| 50 |
'type' => MENU_CALLBACK,
|
| 51 |
);
|
| 52 |
return $items;
|
| 53 |
}
|
| 54 |
|
| 55 |
/**
|
| 56 |
* Implementation of hook_perm()
|
| 57 |
*/
|
| 58 |
function css_perm() {
|
| 59 |
return array('create css for nodes');
|
| 60 |
}
|
| 61 |
|
| 62 |
/**
|
| 63 |
* Implemenation of hook_form_alter()
|
| 64 |
*/
|
| 65 |
function css_form_alter(&$form, $form_state, $form_id) {
|
| 66 |
//Add a text area to the form where users will put their csses rules.
|
| 67 |
if (user_access('create css for nodes') && variable_get('css__'.$form['#node']->type, FALSE)) {
|
| 68 |
if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
|
| 69 |
$node = $form['#node'];
|
| 70 |
// _form_form code follows:
|
| 71 |
$form['css_css'] = array(
|
| 72 |
'#type' => 'textarea',
|
| 73 |
'#title' => t('CSS'),
|
| 74 |
'#default_value' => $node->css_css,
|
| 75 |
'#cols' => 60,
|
| 76 |
'#rows' => 10,
|
| 77 |
'#description' => t('Insert here the css rules for this node. You can use css defined for other nodes using <em>@import "?q=css/get/x";</em> where x is the identification number of the node which contains the css you want to use.'),
|
| 78 |
'#attributes' => NULL,
|
| 79 |
'#required' => FALSE,
|
| 80 |
);
|
| 81 |
}
|
| 82 |
}
|
| 83 |
// Create a settings on content types configuration page
|
| 84 |
// which enable to activate/deactivate css editing for a node type
|
| 85 |
if (isset($form['#node_type']) && 'node_type_form' == $form_id) {
|
| 86 |
$form['workflow']['css_'.$node->type] = array(
|
| 87 |
'#type' => 'checkbox',
|
| 88 |
'#title' => t('Enable CSS Editing.'),
|
| 89 |
'#return_value' => 1,
|
| 90 |
'#default_value' => variable_get('css__'.$form['#node_type']->type, FALSE),
|
| 91 |
);
|
| 92 |
}
|
| 93 |
}
|
| 94 |
|
| 95 |
/**
|
| 96 |
* Implementation of hook_nodeapi().
|
| 97 |
*/
|
| 98 |
function css_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
|
| 99 |
static $prev_op = NULL;
|
| 100 |
|
| 101 |
if (variable_get('css__'. $node->type, FALSE)) { // check that CSS editing is enabled for the given node type
|
| 102 |
switch ($op) {
|
| 103 |
|
| 104 |
// Controls for valid input data
|
| 105 |
case 'validate':
|
| 106 |
// Check for potentially malicious tags
|
| 107 |
$pattern = '~<\s*\/?\s*(style|script|meta)\s*.*?>~i';
|
| 108 |
if (preg_match($pattern, $node->css_css)) {
|
| 109 |
form_set_error('css_css', t('Please do not include any tags.'));
|
| 110 |
}
|
| 111 |
break;
|
| 112 |
|
| 113 |
// Now that the form has been properly completed, it is time to commit the new
|
| 114 |
// data to the database.
|
| 115 |
case 'insert':
|
| 116 |
if (!empty($node->css_css)) {
|
| 117 |
db_query("INSERT INTO {css} (nid, css) VALUES (%d, '%s')", $node->nid, $node->css_css);
|
| 118 |
}
|
| 119 |
break;
|
| 120 |
|
| 121 |
// If the form was called to edit an existing node rather than create a new
|
| 122 |
// one, this operation gets called instead. We use a DELETE then INSERT rather
|
| 123 |
// than an UPDATE just in case the rating didn't exist for some reason.
|
| 124 |
case 'update':
|
| 125 |
db_query("DELETE FROM {css} WHERE nid = %d", $node->nid);
|
| 126 |
if (!empty($node->css_css)) {
|
| 127 |
db_query("INSERT INTO {css} (nid, css) VALUES (%d, '%s')", $node->nid, $node->css_css);
|
| 128 |
}
|
| 129 |
break;
|
| 130 |
|
| 131 |
// If the node is being deleted, we need this opportunity to clean up after
|
| 132 |
// ourselves.
|
| 133 |
case 'delete':
|
| 134 |
db_query('DELETE FROM {css} WHERE nid = %d', $node->nid);
|
| 135 |
break;
|
| 136 |
|
| 137 |
// Now we need to take care of loading one of the extended nodes from the
|
| 138 |
// database. An array containing our extra field needs to be returned.
|
| 139 |
case 'load':
|
| 140 |
$object = db_fetch_object(db_query('SELECT css FROM {css} WHERE nid = %d', $node->nid));
|
| 141 |
return array('css_css' => $object->css);
|
| 142 |
break;
|
| 143 |
|
| 144 |
// Using nodeapi('view') is more appropriate than using a filter here, because
|
| 145 |
// filters transform user-supplied content, whereas we are extending it with
|
| 146 |
// additional information.
|
| 147 |
case 'view':
|
| 148 |
if ($prev_op == 'validate') {
|
| 149 |
// 'validate' immediately followed by 'view' means this is a preview
|
| 150 |
if ($node->css_css) {
|
| 151 |
$css = '<style type="text/css" media="all"> '.
|
| 152 |
css_sanitize($node->css_css, 'preview').
|
| 153 |
' </style>';
|
| 154 |
drupal_set_html_head($css, 'preview');
|
| 155 |
}
|
| 156 |
} else {
|
| 157 |
// Drupal 6 seems to check for the physical existence of CSS files
|
| 158 |
// before allowing them to be added. We have to include the virtual
|
| 159 |
// CSS file manually since it does not really exist.
|
| 160 |
if (!empty($node->css_css)) {
|
| 161 |
$link = url('css/get/'.$node->nid);
|
| 162 |
drupal_add_link(array(
|
| 163 |
'type' => 'text/css',
|
| 164 |
'rel' => 'stylesheet',
|
| 165 |
'media' => 'all',
|
| 166 |
'href' => $link,
|
| 167 |
));
|
| 168 |
}
|
| 169 |
}
|
| 170 |
break;
|
| 171 |
}
|
| 172 |
|
| 173 |
$prev_op = $op; // used to determine Preview state
|
| 174 |
}
|
| 175 |
}
|
| 176 |
|
| 177 |
/**
|
| 178 |
* Return the css attached to the node.
|
| 179 |
* Last-Modified header is set to let browsers cache the css.
|
| 180 |
*/
|
| 181 |
function css_get($nid = 0) {
|
| 182 |
if (is_numeric($nid) && $nid > 0) {
|
| 183 |
$object = db_fetch_object(db_query('SELECT css, changed FROM {css} c, {node} n WHERE n.nid = %d AND n.nid = c.nid', $nid));
|
| 184 |
if ($object) {
|
| 185 |
$date = gmdate('D, d M Y H:i:s', $object->changed) .' GMT';
|
| 186 |
header("Last-Modified: $date");
|
| 187 |
drupal_set_header('Content-Type: text/css; charset=utf-8');
|
| 188 |
print(css_sanitize($object->css));
|
| 189 |
}
|
| 190 |
}
|
| 191 |
}
|
| 192 |
|
| 193 |
/**
|
| 194 |
* Remove harmful code from CSS.
|
| 195 |
*/
|
| 196 |
function css_sanitize($css, $type = 'view') {
|
| 197 |
switch ($type) {
|
| 198 |
case 'view':
|
| 199 |
// Are there any security vulnerabilites from external CSS files?
|
| 200 |
break;
|
| 201 |
|
| 202 |
case 'preview':
|
| 203 |
// Catch potentially malicious code
|
| 204 |
$patterns = array(
|
| 205 |
'~<\s*(/?)\s*(style|script|meta)\s*>~i',
|
| 206 |
);
|
| 207 |
$css = preg_replace($patterns, '<$1FILTERED $2>', $css);
|
| 208 |
break;
|
| 209 |
|
| 210 |
default:
|
| 211 |
$css = '';
|
| 212 |
break;
|
| 213 |
}
|
| 214 |
|
| 215 |
return $css;
|
| 216 |
}
|