Index: TODO.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cpn/TODO.txt,v
retrieving revision 1.2
diff -u -r1.2 TODO.txt
--- TODO.txt	10 Jan 2011 21:57:27 -0000	1.2
+++ TODO.txt	22 Feb 2011 14:37:45 -0000
@@ -1,4 +1,4 @@
-; $Id: TODO.txt,v 1.2 2011/01/10 21:57:27 joelstein Exp $
+; $Id$
 
 TODO
 ----
@@ -7,4 +7,4 @@
     - could put a listener on the fieldset?
 - make "enabled by default" an option on a setting page
 - can we add node revision support?
-  - would we have to move the CSS & JS fields to node_revisions?
\ No newline at end of file
+  - would we have to move the CSS & JS fields to node_revisions?
Index: cpn.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cpn/cpn.admin.inc,v
retrieving revision 1.1
diff -u -r1.1 cpn.admin.inc
--- cpn.admin.inc	6 Jan 2011 20:42:47 -0000	1.1
+++ cpn.admin.inc	22 Feb 2011 14:37:45 -0000
@@ -1,22 +1,26 @@
 <?php
-// $Id: cpn.admin.inc,v 1.1 2011/01/06 20:42:47 joelstein Exp $
+// $Id$
 
 /**
  * Settings form.
  */
-function cpn_settings() {
-  $options[0] = t('None');
-
-  if (is_file('sites/all/libraries/codemirror/js/codemirror.js')) {
-    $options['codemirror'] = 'Code Mirror';
-  }
-
+function cpn_settings($form, &$form_state) {
   $form['cpn_syntax_highlighting'] = array(
     '#title' => t('Syntax Highlighting'),
     '#type' => 'radios',
-    '#options' => $options,
+    '#options' => array(
+      0 => t('None'),
+    ),
     '#default_value' => variable_get('cpn_syntax_highlighting', 0),
   );
 
+  // Add CodeMirror as a syntax highlighting option if available.
+  if (is_file('sites/all/libraries/codemirror/js/codemirror.js')) {
+    $form['cpn_syntax_highlighting']['#options']['codemirror'] = 'Code Mirror';
+  }
+  else {
+    drupal_set_message(t('Syntax highlighting requires <a href="@url">CodeMirror</a>. Download it, rename the folder "codemirror", and place it at "sites/all/libraries". Then return to this page and enable syntax highlighting.', array('@url' => 'http://codemirror.net/')), 'warning', FALSE);
+  }
+
   return system_settings_form($form);
 }
Index: cpn.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cpn/cpn.css,v
retrieving revision 1.1
diff -u -r1.1 cpn.css
--- cpn.css	6 Jan 2011 20:42:47 -0000	1.1
+++ cpn.css	22 Feb 2011 14:37:45 -0000
@@ -1,4 +1,4 @@
-/* $Id: cpn.css,v 1.1 2011/01/06 20:42:47 joelstein Exp $ */
+/* $Id$ */
 
 #edit-cpn-css,
 #edit-cpn-js {
Index: cpn.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cpn/cpn.info,v
retrieving revision 1.1
diff -u -r1.1 cpn.info
--- cpn.info	6 Jan 2011 20:42:47 -0000	1.1
+++ cpn.info	22 Feb 2011 14:37:45 -0000
@@ -1,4 +1,10 @@
-; $Id: cpn.info,v 1.1 2011/01/06 20:42:47 joelstein Exp $
+; $Id$
 name = Code per Node
-description = Manage custom CSS & Javascript per node.
-core = 6.x
+description = Manage custom CSS & Javascript per node and per block.
+core = 7.x
+
+files[] = cpn.admin.inc
+files[] = cpn.install
+files[] = cpn.module
+
+configure = admin/config/content/cpn
Index: cpn.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cpn/cpn.install,v
retrieving revision 1.2
diff -u -r1.2 cpn.install
--- cpn.install	10 Jan 2011 21:57:27 -0000	1.2
+++ cpn.install	22 Feb 2011 14:37:45 -0000
@@ -1,8 +1,8 @@
 <?php
-// $Id: cpn.install,v 1.2 2011/01/10 21:57:27 joelstein Exp $
+// $Id$
 
 /**
- * Implementation of hook_schema().
+ * Implements hook_schema().
  */
 function cpn_schema() {
   $schema['cpn'] = array(
@@ -17,69 +17,42 @@
 }
 
 /**
- * Implementation of hook_schema_alter().
+ * Implements hook_schema_alter().
  */
 function cpn_schema_alter(&$schema) {
-  $schema['blocks']['fields']['css'] = array('type' => 'text', 'not null' => FALSE);
-  $schema['blocks']['fields']['js'] = array('type' => 'text', 'not null' => FALSE);
+  $schema['block']['fields']['css'] = array('type' => 'text', 'not null' => FALSE);
+  $schema['block']['fields']['js'] = array('type' => 'text', 'not null' => FALSE);
 }
 
 /**
- * Implementation of hook_install().
+ * Implements hook_install().
  */
 function cpn_install() {
-  drupal_install_schema('cpn');
-
-  // Alter blocks table, as defined in hook_schema_alter().
+  // Alter block table, as defined in cpn_schema_alter().
   $ret = array();
   $schema = array();
   cpn_schema_alter($schema);
-  foreach ($schema['blocks']['fields'] as $name => $spec) {
-    db_add_field($ret, 'blocks', $name, $spec);
+  foreach ($schema['block']['fields'] as $name => $spec) {
+    db_add_field('block', $name, $spec);
   }
 }
 
 /**
- * Implementation of hook_uninstall().
+ * Implements hook_uninstall().
  */
 function cpn_uninstall() {
-  drupal_uninstall_schema('cpn');
   variable_del('cpn_syntax_highlighting');
 
-  // Drop fields from blocks table, as defined in hook_schema_alter().
+  // Drop fields from block table, as defined in cpn_schema_alter().
   $ret = array();
   $schema = array();
   cpn_schema_alter($schema);
-  foreach ($schema['blocks']['fields'] as $name => $spec) {
-    db_drop_field($ret, 'blocks', $name);
+  foreach ($schema['block']['fields'] as $name => $spec) {
+    db_drop_field('block', $name);
   }
 
   // Delete CSS & JS files.
-  cpn_recursive_delete(file_create_path('cpn'));
-}
-
-/**
- * Recursive delete function.
- */
-function cpn_recursive_delete($path) {
-  if (is_file($path) or is_link($path)) {
-    unlink($path);
-  }
-  elseif (is_dir($path)) {
-    $d = dir($path);
-    while (($entry = $d->read()) !== FALSE) {
-      if ($entry == '.' or $entry == '..') {
-        continue;
-      }
-      $entry_path = $path .'/'. $entry;
-      cpn_recursive_delete($entry_path);
-    }
-    $d->close();
-    rmdir($path);
-  }
-  else {
-    watchdog('cpn', 'Unknown file type(%path) stat: %stat ', array('%path' => $path,  '%stat' => print_r(stat($path),1)), WATCHDOG_ERROR);
-  }
+  file_unmanaged_delete_recursive('public://cpn');
 }
 
 /**
@@ -87,7 +60,7 @@
  */
 function cpn_update_6000() {
   $ret = array();
-  db_add_field($ret, 'blocks', 'css', array('type' => 'text', 'not null' => FALSE));
-  db_add_field($ret, 'blocks', 'js', array('type' => 'text', 'not null' => FALSE));
+  db_add_field('blocks', 'css', array('type' => 'text', 'not null' => FALSE));
+  db_add_field('blocks', 'js', array('type' => 'text', 'not null' => FALSE));
   return $ret;
 }
Index: cpn.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cpn/cpn.js,v
retrieving revision 1.1
diff -u -r1.1 cpn.js
--- cpn.js	6 Jan 2011 20:42:47 -0000	1.1
+++ cpn.js	22 Feb 2011 14:37:45 -0000
@@ -1,40 +1,42 @@
-// $Id: cpn.js,v 1.1 2011/01/06 20:42:47 joelstein Exp $
-
-Drupal.behaviors.cpnCodeMirror = function(context) {
-
-  // Append enable/disable links.
-  $('#edit-cpn-css-wrapper, #edit-cpn-js-wrapper').each(function() {
-    $('.description', this).append(' <a href="#" class="cpn-toggle">Enable syntax highlighting</a>.');
-  });
-
-  // Toggle syntax highlighting.
-  $('.cpn-toggle').click(function() {
-    var $textarea = $(this).parents('.form-item').find('textarea');
-    var $grippie = $textarea.parents('.resizable-textarea').find('.grippie');
-    var type = $textarea.attr('id').replace('edit-cpn-', '');
-
-    // Enable
-    if (!$(this).hasClass('enabled')) {
-      $grippie.hide();
-      var editor = CodeMirror.fromTextArea($textarea.attr('id'), {
-        path: Drupal.settings.basePath +'sites/all/libraries/codemirror/js/',
-        stylesheet: Drupal.settings.basePath +'sites/all/libraries/codemirror/css/'+ type +'colors.css',
-        parserfile: type == 'css' ? ['parsecss.js'] : ['tokenizejavascript.js', 'parsejavascript.js'],
-        height: $textarea.height() +'px',
-        tabMode: 'shift',
-        reindentOnLoad: true
+// $Id$
+(function ($) {
+  Drupal.behaviors.cpnCodeMirror = {
+    attach: function(context, settings) {
+
+      // Append enable/disable links.
+      $('.form-item-cpn-css, .form-item-cpn-js').each(function() {
+        $('.description', this).append(' <a href="#" class="cpn-toggle">Enable syntax highlighting</a>.');
       });
-      $(this).data('editor', editor);
-      $(this).text(Drupal.t('Disable syntax highlighting')).addClass('enabled');
-    }
 
-    // Disable
-    else {
-      $(this).data('editor').toTextArea();
-      $grippie.show();
-      $(this).text(Drupal.t('Enable syntax highlighting')).removeClass('enabled');
+      // Toggle syntax highlighting.
+      $('.cpn-toggle').click(function() {
+        var $textarea = $(this).parents('.form-item').find('textarea');
+        var $grippie = $textarea.parents('.resizable-textarea').find('.grippie');
+        var type = $textarea.attr('id').replace('edit-cpn-', '');
+
+        // Enable
+        if (!$(this).hasClass('enabled')) {
+          $grippie.hide();
+          var editor = CodeMirror.fromTextArea($textarea.attr('id'), {
+            path: Drupal.settings.basePath +'sites/all/libraries/codemirror/js/',
+            stylesheet: Drupal.settings.basePath +'sites/all/libraries/codemirror/css/'+ type +'colors.css',
+            parserfile: type == 'css' ? ['parsecss.js'] : ['tokenizejavascript.js', 'parsejavascript.js'],
+            height: $textarea.height() +'px',
+            tabMode: 'shift',
+            reindentOnLoad: true
+          });
+          $(this).data('editor', editor);
+          $(this).text(Drupal.t('Disable syntax highlighting')).addClass('enabled');
+        }
+
+        // Disable
+        else {
+          $(this).data('editor').toTextArea();
+          $grippie.show();
+          $(this).text(Drupal.t('Enable syntax highlighting')).removeClass('enabled');
+        }
+        return false;
+      });
     }
-    return false;
-  });
-
-};
+  };
+})(jQuery);
\ No newline at end of file
Index: cpn.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cpn/cpn.module,v
retrieving revision 1.2
diff -u -r1.2 cpn.module
--- cpn.module	10 Jan 2011 21:57:27 -0000	1.2
+++ cpn.module	22 Feb 2011 14:37:45 -0000
@@ -1,322 +1,356 @@
-<?php
-// $Id: cpn.module,v 1.2 2011/01/10 21:57:27 joelstein Exp $
-
-/**
- * Implementation of hook_perm().
- */
-function cpn_perm() {
-  return array(
-    'edit css per node',
-    'edit javascript per node',
-    'edit css per block',
-    'edit javascript per block',
-  );
-}
-
-/**
- * Implementation of hook_help().
- */
-function cpn_help($path, $arg) {
-  if ($path == 'admin/settings/cpn' and !is_file('sites/all/libraries/codemirror/js/codemirror.js')) {
-    return '<p>'. t('Syntax highlighting requires <a href="@url">CodeMirror</a>. Download it, rename the folder "codemirror", and place it at "sites/all/libraries". Then return to this page and enable syntax highlighting.', array('@url' => 'http://codemirror.net/')) .'</p>';
-  }
-}
-
-/**
- * Implementation of hook_menu().
- */
-function cpn_menu() {
-  $items['admin/settings/cpn'] = array(
-    'title' => 'Code per Node',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('cpn_settings'),
-    'access arguments' => array('administer site configuration'),
-    'file' => 'cpn.admin.inc',
-  );
-  return $items;
-}
-
-/**
- * Implemenation of hook_form_alter().
- */ 
-function cpn_form_alter(&$form, $form_state, $form_id) {
-
-  // Node form.
-  if (isset($form['type']) and isset($form['#node']) and $form_id == $form['type']['#value'] .'_node_form') {
-    $title = array();
-
-    // CSS.
-    if (variable_get('cpn_css_'. $form['#node']->type, FALSE) and user_access('edit css per node')) {
-      $form['cpn']['css'] = array(
-        '#type' => 'textarea',
-        '#title' => t('CSS'),
-        '#default_value' => $form['#node']->cpn['css'],
-        '#description' => t('Custom CSS rules for this node. Do not include @style tags.', array('@style' => '<style>')),
-      );
-      $title[] = 'CSS';
-    }
-
-    // JS.
-    if (variable_get('cpn_js_'. $form['#node']->type, FALSE) and user_access('edit javascript per node')) {
-      $form['cpn']['js'] = array(
-        '#type' => 'textarea',
-        '#title' => t('Javascript'),
-        '#default_value' => $form['#node']->cpn['js'],
-        '#description' => t('Custom Javascript for this node. Do not include @script tags.', array('@script' => '<script>')),
-      );
-      $title[] = 'Javascript';
-    }
-
-    // Fieldset.
-    if (isset($form['cpn'])) {
-      $form['cpn']['#type'] = 'fieldset';
-      $form['cpn']['#title'] = t(join(' & ', $title));
-      $form['cpn']['#collapsible'] = TRUE;
-      $form['cpn']['#tree'] = TRUE;
-      $form['cpn']['#collapsed'] = !drupal_strlen(trim($form['#node']->cpn['css'] . $form['#node']->cpn['js']));
-      $form['#after_build'][] = 'cpn_after_build';
-    }
-  }
-
-  // Add content type settings.
-  if ($form_id == 'node_type_form' and isset($form['#node_type'])) {
-    $form['cpn'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Code per Node settings'),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-    );
-    $form['cpn']['cpn_css'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Enable custom CSS.'),
-      '#return_value' => 1,
-      '#default_value' => variable_get('cpn_css_'. $form['#node_type']->type, FALSE),
-      '#description' => t('Users with the <em>edit node css</em> permission will be able to edit custom CSS rules per node.'),
-    );
-    $form['cpn']['cpn_js'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Enable custom Javascript.'),
-      '#return_value' => 1,
-      '#default_value' => variable_get('cpn_js_'. $form['#node_type']->type, FALSE),
-      '#description' => t('Users with the <em>edit node javascript</em> permission will be able to edit custom Javascript per node.'),
-    );
-  }
-
-  // Block form (editing any block, or creating a Block module block).
-  if ($form_id == 'block_admin_configure' or ($form_id == 'block_add_block_form' and $form['module']['#value'] == 'block')) {
-    $title = array();
-
-    // Load block CSS & JS.
-    $cpn = array('css' => '', 'js' => '');
-    if (!empty($form['delta']['#value'])) {
-      $cpn = db_fetch_array(db_query("SELECT css, js FROM {blocks} WHERE module = '%s' AND delta = '%s'", $form['module']['#value'], $form['delta']['#value']));
-    }
-
-    // CSS.
-    if (user_access('edit css per block')) {
-      $form['cpn']['css'] = array(
-        '#type' => 'textarea',
-        '#title' => t('CSS'),
-        '#default_value' => $cpn['css'],
-        '#description' => t('Custom CSS rules for this node. Do not include @style tags.', array('@style' => '<style>')),
-      );
-      $title[] = 'CSS';
-    }
-
-    // JS.
-    if (user_access('edit javascript per block')) {
-      $form['cpn']['js'] = array(
-        '#type' => 'textarea',
-        '#title' => t('Javascript'),
-        '#default_value' => $cpn['js'],
-        '#description' => t('Custom Javascript for this node. Do not include @script tags.', array('@script' => '<script>')),
-      );
-      $title[] = 'Javascript';
-    }
-
-    // Fieldset.
-    if (isset($form['cpn'])) {
-      $form['cpn']['#type'] = 'fieldset';
-      $form['cpn']['#title'] = t(join(' & ', $title));
-      $form['cpn']['#collapsible'] = TRUE;
-      $form['cpn']['#tree'] = TRUE;
-      $form['cpn']['#collapsed'] = FALSE;
-      $form['submit']['#weight'] = 5;
-      $form['#after_build'][] = 'cpn_after_build';
-      $form['#validate'][] = 'cpn_block_validate';
-      $form['#submit'][] = 'cpn_block_submit';
-    }
-  }
-
-}
-
-/**
- * "#after_build" function which adds syntax highlighting.
- * See http://drupal.org/node/322290.
- */
-function cpn_after_build($form_element, &$form_state) {
-  switch (variable_get('cpn_syntax_highlighting', 0)) {
-    case 'codemirror':
-      drupal_add_js('sites/all/libraries/codemirror/js/codemirror.js');
-      drupal_add_js(drupal_get_path('module', 'cpn') .'/cpn.js');
-      drupal_add_css(drupal_get_path('module', 'cpn') .'/cpn.css');
-      break;
-  }
-  return $form_element;
-}
-
-/**
- * Implementation of hook_nodeapi().
- */
-function cpn_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
-  static $previous_op = NULL;
-  switch ($op) {
-
-    // Validating: ensure no "style" or "script" tags are included.
-    case 'validate':
-      if (isset($node->cpn['css']) and cpn_validate($node->cpn['css'], 'css')) {
-        form_set_error('cpn][css', t('Do not include @style tags in the CSS.', array('@style' => '<style>')));
-      }
-      if (isset($node->cpn['js']) and cpn_validate($node->cpn['js'], 'js')) {
-        form_set_error('cpn][js', t('Do not include @script tags in the Javascript.', array('@script' => '<script>')));
-      }
-      break;
-
-    // Updating: delete from DB and file system, and intentionally fallthrough
-    // to "insert" op below.
-    case 'update':
-      if (isset($node->cpn)) {
-        db_query("DELETE FROM {cpn} WHERE nid = %d", $node->nid);
-        file_delete(file_create_path('cpn/'. $node->nid .'.css'));
-        file_delete(file_create_path('cpn/'. $node->nid .'.js'));
-      }
-
-    // Inserting: save in DB and file system.
-    case 'insert':
-      if (isset($node->cpn) and drupal_strlen(trim($node->cpn['css'] . $node->cpn['js']))) {
-        db_query("INSERT INTO {cpn} VALUES (%d, '%s', '%s')", $node->nid, $node->cpn['css'], $node->cpn['js']);
-        cpn_save_file($node->cpn['css'], $node->nid .'.css');
-        cpn_save_file($node->cpn['js'], $node->nid .'.js');
-      }
-      break;
-
-    // Deleting: delete from DB and file system.
-    case 'delete':
-      db_query("DELETE FROM {cpn} WHERE nid = %d", $node->nid);
-      file_delete(file_create_path('cpn/'. $node->nid .'.css'));
-      file_delete(file_create_path('cpn/'. $node->nid .'.js'));
-      break;
-
-    // Loading: add "cpn" variable to the node object.
-    case 'load':
-      return array('cpn' => db_fetch_array(db_query("SELECT css, js FROM {cpn} WHERE nid = %d", $node->nid)));
-      break;
-
-    // Viewing & Previewing.
-    case 'view':
-      // Previewing: add CSS and/or JS to the page, inline.
-      if ($previous_op == 'validate') {
-        if (drupal_strlen(trim($node->cpn['css']))) {
-          // NOTE: This is injected at the top of the page, before all <link> tags.
-          // It should be injected after the <link> tags.
-          // Possible in D7: http://drupal.org/node/259368
-          // Not easily possible in D6: http://drupal.org/node/143209
-          drupal_set_html_head('<style type="text/css" media="all"> '. $node->cpn['css'] .' </style>');
-        }
-        if (drupal_strlen(trim($node->cpn['js']))) {
-          drupal_add_js($node->cpn['js'], 'inline');
-        }
-      }
-      // Viewing: add files to the page (but only if they exist).
-      else {
-        $css = file_create_path('cpn/'. $node->nid .'.css');
-        $js = file_create_path('cpn/'. $node->nid .'.js');
-        if (is_file($css)) {
-          drupal_add_css($css, 'theme');
-        }
-        if (is_file($js)) {
-          drupal_add_js($js, 'theme');
-        }
-      }
-      break;
-  }
-
-  $previous_op = $op;
-}
-
-/**
- * Block validation callback.
- */
-function cpn_block_validate($form, &$form_state) {
-  // Ensure no "style" or "script" tags are included.
-  if (cpn_validate($form_state['values']['cpn']['css'], 'css')) {
-    form_set_error('cpn][css', t('Do not include @style tags in the CSS.', array('@style' => '<style>')));
-  }
-  if (cpn_validate($form_state['values']['cpn']['js'], 'js')) {
-    form_set_error('cpn][js', t('Do not include @script tags in the Javascript.', array('@script' => '<script>')));
-  }
-}
-
-/**
- * Block submit callback.
- */
-function cpn_block_submit($form, &$form_state) {
-  if (isset($form_state['values']['cpn'])) {
-    $module = $form_state['values']['module'];
-    $delta = $form_state['values']['delta'];
-
-    // "Block" block was just created; get delta from "boxes" table.
-    if (empty($delta) and $module == 'block') {
-      $delta = db_result(db_query("SELECT bid FROM {boxes} ORDER BY bid DESC LIMIT 1"));
-    }
-    
-    // Save in database.
-    db_query("UPDATE {blocks} SET css = '%s', js = '%s' WHERE module = '%s' AND delta = '%s'", $form_state['values']['cpn']['css'], $form_state['values']['cpn']['js'], $module, $delta);
-
-    // Delete existing files.
-    file_delete(file_create_path('cpn/'. $module .'-'. $delta .'.css'));
-    file_delete(file_create_path('cpn/'. $module .'-'. $delta .'.js'));
-
-    // Save files.
-    cpn_save_file($form_state['values']['cpn']['css'], $module .'-'. $delta .'.css');
-    cpn_save_file($form_state['values']['cpn']['js'], $module .'-'. $delta .'.js');
-  }
-}
-
-/**
- * Implementation of template_preprocess_block().
- */
-function cpn_preprocess_block(&$vars) {
-  // Add files to the page (but only if they exist).
-  $css = file_create_path('cpn/'. $vars['block']->module .'-'. $vars['block']->delta .'.css');
-  $js = file_create_path('cpn/'. $vars['block']->module .'-'. $vars['block']->delta .'.js');
-  if (is_file($css)) {
-    drupal_add_css($css, 'theme');
-  }
-  if (is_file($js)) {
-    drupal_add_js($js, 'theme');
-  }
-}
-
-/**
- * Validates CSS or Javascript.
- */
-function cpn_validate($data, $type) {
-  $patterns = array(
-    'css' => '~<\s*\/?\s*style\s*.*?>~i',
-    'js' => '~<\s*\/?\s*script\s*.*?>~i',
-  );
-  return preg_match($patterns[$type], $data);
-}
-
-/**
- * Saves CSS & Javascript in the file system (but only if not empty).
- */
-function cpn_save_file($data, $filename) {
-  if (!drupal_strlen(trim($data))) {
-    return FALSE;
-  }
-  $path = file_create_path('cpn');
-  file_check_directory($path, FILE_CREATE_DIRECTORY) or mkdir($path, 0755, TRUE);
-  return file_save_data($data, $path .'/'. $filename, FILE_EXISTS_REPLACE);
-}
+<?php
+// $Id$
+
+/**
+ * Implements hook_permission().
+ */
+function cpn_permission() {
+  return array(
+    'edit css per node' => array(
+      'title' => t('Edit CSS per node'),
+    ),
+    'edit javascript per node' => array(
+      'title' => t('Edit JavaScript per node'),
+    ),
+    'edit css per block' => array(
+      'title' => t('Edit CSS per block'),
+    ),
+    'edit javascript per block' => array(
+      'title' => t('Edit JavaScript per block'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function cpn_menu() {
+  $items['admin/config/content/cpn'] = array(
+    'title' => 'Code per Node',
+    'description' => 'Configure Code per Node settings.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('cpn_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'cpn.admin.inc',
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function cpn_form_node_type_form_alter(&$form, $form_state) {
+  if (isset($form['type'])) {
+    $form['cpn'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Code per Node settings'),
+      '#group' => 'additional_settings',
+    );
+    $form['cpn']['cpn_css'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enable custom CSS.'),
+      '#return_value' => 1,
+      '#default_value' => variable_get('cpn_css_' . $form['#node_type']->type, FALSE),
+      '#description' => t('Users with the <em>edit node css</em> permission will be able to edit custom CSS rules per node.'),
+    );
+    $form['cpn']['cpn_js'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enable custom Javascript.'),
+      '#return_value' => 1,
+      '#default_value' => variable_get('cpn_js_' . $form['#node_type']->type, FALSE),
+      '#description' => t('Users with the <em>edit node javascript</em> permission will be able to edit custom Javascript per node.'),
+    );
+  }
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter().
+ */
+function cpn_form_node_form_alter(&$form, $form_state) {
+  $title = array();
+  $cpn = !empty($form['#node']->cpn) ? $form['#node']->cpn : array('css' => '', 'js' => '');
+
+  // CSS.
+  if (variable_get('cpn_css_' . $form['#node']->type, FALSE) and user_access('edit css per node')) {
+    $form['cpn']['css'] = array(
+      '#type' => 'textarea',
+      '#title' => t('CSS'),
+      '#default_value' => $cpn['css'],
+      '#description' => t('Custom CSS rules for this node. Do not include @style tags.', array('@style' => '<style>')),
+    );
+    $title[] = 'CSS';
+  }
+
+  // JS.
+  if (variable_get('cpn_js_' . $form['#node']->type, FALSE) and user_access('edit javascript per node')) {
+    $form['cpn']['js'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Javascript'),
+      '#default_value' => $cpn['js'],
+      '#description' => t('Custom Javascript for this node. Do not include @script tags.', array('@script' => '<script>')),
+    );
+    $title[] = 'Javascript';
+  }
+
+  // Fieldset.
+  if (isset($form['cpn'])) {
+    $form['cpn']['#type'] = 'fieldset';
+    $form['cpn']['#title'] = t(join(' & ', $title));
+    $form['cpn']['#tree'] = TRUE;
+    $form['cpn']['#group'] = 'additional_settings';
+    if (variable_get('cpn_syntax_highlighting', 0) == 'codemirror') {
+      $form['cpn']['#attached']['js'][] = 'sites/all/libraries/codemirror/js/codemirror.js';
+      $form['cpn']['#attached']['js'][] = drupal_get_path('module', 'cpn') . '/cpn.js';
+      $form['cpn']['#attached']['css'][] = drupal_get_path('module', 'cpn') . '/cpn.css';
+    }
+  }
+
+}
+
+/**
+ * Implements hook_node_validate().
+ *
+ * Ensures no "style" or "script" tags are included.
+ */
+function cpn_node_validate($node, $form) {
+  if (isset($node->cpn['css']) && cpn_validate($node->cpn['css'], 'css')) {
+    form_set_error('cpn][css', t('Do not include @style tags in the CSS.', array('@style' => '<style>')));
+  }
+  if (isset($node->cpn['js']) && cpn_validate($node->cpn['js'], 'js')) {
+    form_set_error('cpn][js', t('Do not include @script tags in the Javascript.', array('@script' => '<script>')));
+  }
+}
+
+/**
+ * Implements hook_node_update().
+ *
+ * Deletes from DB and file system, and then insert.
+ */
+function cpn_node_update($node) {
+  if (isset($node->cpn)) {
+    db_delete('cpn')
+      ->condition('nid', $node->nid)
+      ->execute();
+    file_unmanaged_delete('public://cpn/' . $node->nid . '.css');
+    file_unmanaged_delete('public://cpn/' . $node->nid . '.js');
+  }
+  cpn_node_insert($node);
+}
+
+/**
+ * Implements hook_node_insert().
+ *
+ * Saves in DB and file system.
+ */
+function cpn_node_insert($node) {
+  if (isset($node->cpn) && drupal_strlen(trim($node->cpn['css'] . $node->cpn['js']))) {
+    db_insert('cpn')
+      ->fields(array(
+        'nid' => $node->nid,
+        'css' => $node->cpn['css'],
+        'js' => $node->cpn['js'],
+      ))
+      ->execute();
+    cpn_save_file($node->cpn['css'], $node->nid . '.css');
+    cpn_save_file($node->cpn['js'], $node->nid . '.js');
+  }
+}
+
+/**
+ * Implements hook_node_delete().
+ * Deletes from DB and file system.
+ */
+function cpn_node_delete($node) {
+  db_delete('cpn')
+    ->condition('nid', $node->nid)
+    ->execute();
+  file_unmanaged_delete('public://cpn/' . $node->nid . '.css');
+  file_unmanaged_delete('public://cpn/' . $node->nid . '.js');
+}
+
+/**
+ * Implements hook_node_load().
+ *
+ * Adds "cpn" variable to the node object.
+ */
+function cpn_node_load($nodes, $types) {
+  $result = db_query('SELECT nid, css, js FROM {cpn} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)));
+  foreach ($result as $record) {
+    $nodes[$record->nid]->cpn = array(
+      'css' => $record->css,
+      'js' => $record->js,
+    );
+  }
+}
+
+/**
+ * Implements hook_node_view().
+ */
+function cpn_node_view($node, $view_mode, $langcode) {
+  // Previewing: add CSS and/or JS to the node, inline.
+  if (isset($node->in_preview) && $node->in_preview) {
+    foreach (array('css', 'js') as $type) {
+      if (isset($node->cpn[$type]) && drupal_strlen(trim($node->cpn[$type]))) {
+        $node->content['#attached'][$type]['cpn'] = array(
+          'type' => 'inline',
+          'weight' => $type == 'css' ? CSS_THEME : JS_THEME,
+          'data' => $node->cpn[$type],
+        );
+      }
+    }
+  }
+  // Viewing: add files to the node (but only if they exist).
+  else {
+    foreach (array('css', 'js') as $type) {
+      $file = 'public://cpn/' . $node->nid . '.' . $type;
+      if (is_file($file)) {
+        $node->content['#attached'][$type]['cpn'] = array(
+          'type' => 'file',
+          'weight' => $type == 'css' ? CSS_THEME : JS_THEME,
+          'data' => $file,
+        );
+      }
+    }
+  }
+}
+
+/**
+ * Implemenation of hook_form_alter().
+ */
+function cpn_form_alter(&$form, $form_state, $form_id) {
+  // Block form (editing any block, or creating a Block module block).
+  if ($form_id == 'block_admin_configure' || ($form_id == 'block_add_block_form' && $form['module']['#value'] == 'block')) {
+    $title = array();
+
+    // Load block CSS & JS.
+    $cpn = array('css' => '', 'js' => '');
+    if (!empty($form['delta']['#value'])) {
+      $cpn = db_query("SELECT css, js FROM {block} WHERE module = :module AND delta = :delta", array(
+        ':module' => $form['module']['#value'],
+        ':delta' => $form['delta']['#value'],
+      ))->fetchAssoc();
+    }
+
+    // CSS.
+    if (user_access('edit css per block')) {
+      $form['cpn']['css'] = array(
+        '#type' => 'textarea',
+        '#title' => t('CSS'),
+        '#default_value' => $cpn['css'],
+        '#description' => t('Custom CSS rules for this node. Do not include @style tags.', array('@style' => '<style>')),
+      );
+      $title[] = 'CSS';
+    }
+
+    // JS.
+    if (user_access('edit javascript per block')) {
+      $form['cpn']['js'] = array(
+        '#type' => 'textarea',
+        '#title' => t('Javascript'),
+        '#default_value' => $cpn['js'],
+        '#description' => t('Custom Javascript for this node. Do not include @script tags.', array('@script' => '<script>')),
+      );
+      $title[] = 'Javascript';
+    }
+
+    // Fieldset.
+    if (isset($form['cpn'])) {
+      $form['cpn']['#type'] = 'fieldset';
+      $form['cpn']['#title'] = t(join(' & ', $title));
+      $form['cpn']['#tree'] = TRUE;
+      $form['cpn']['#group'] = 'visibility';
+      $form['submit']['#weight'] = 5;
+      $form['#validate'][] = 'cpn_block_validate';
+      $form['#submit'][] = 'cpn_block_submit';
+      if (variable_get('cpn_syntax_highlighting', 0) == 'codemirror') {
+        $form['cpn']['#attached']['js'][] = 'sites/all/libraries/codemirror/js/codemirror.js';
+        $form['cpn']['#attached']['js'][] = drupal_get_path('module', 'cpn') . '/cpn.js';
+        $form['cpn']['#attached']['css'][] = drupal_get_path('module', 'cpn') . '/cpn.css';
+      }
+    }
+  }
+}
+
+/**
+ * Block validation callback.
+ *
+ * Ensures no "style" or "script" tags are included.
+ */
+function cpn_block_validate($form, &$form_state) {
+  if (cpn_validate($form_state['values']['cpn']['css'], 'css')) {
+    form_set_error('cpn][css', t('Do not include @style tags in the CSS.', array('@style' => '<style>')));
+  }
+  if (cpn_validate($form_state['values']['cpn']['js'], 'js')) {
+    form_set_error('cpn][js', t('Do not include @script tags in the Javascript.', array('@script' => '<script>')));
+  }
+}
+
+/**
+ * Block submit callback.
+ */
+function cpn_block_submit($form, &$form_state) {
+  if (isset($form_state['values']['cpn'])) {
+    $module = $form_state['values']['module'];
+    $delta = $form_state['values']['delta'];
+
+    // "Block" block was just created; get delta from "block_custom" table.
+    if (empty($delta) and $module == 'block') {
+      $delta = db_query("SELECT bid FROM {block_custom} ORDER BY bid DESC LIMIT 1")->fetchField();
+    }
+
+    // Save in database.
+    db_update('block')
+      ->fields(array(
+        'css' => $form_state['values']['cpn']['css'],
+        'js' => $form_state['values']['cpn']['js'],
+        ))
+      ->condition('module', $module)
+      ->condition('delta', $delta)
+      ->execute();
+
+    // Replace any existing files.
+    foreach (array('css', 'js') as $type) {
+      file_unmanaged_delete('public://cpn/' . $module . '-' . $delta . '.' . $type);
+      cpn_save_file($form_state['values']['cpn'][$type], $module . '-' . $delta . '.' . $type);
+    }
+  }
+}
+
+/**
+ * Implementation of template_preprocess_block().
+ *
+ * Adds files to the page (but only if they exist).
+ */
+function cpn_preprocess_block(&$vars) {
+  $css = 'public://cpn/' . $vars['block']->module . '-' . $vars['block']->delta . '.css';
+  $js = 'public://cpn/' . $vars['block']->module . '-' . $vars['block']->delta . '.js';
+  if (is_file($css)) {
+    drupal_add_css($css, array('type' => 'file', 'group' => CSS_THEME));
+  }
+  if (is_file($js)) {
+    drupal_add_js($js, array('type' => 'file', 'group' => JS_THEME));
+  }
+}
+
+/**
+ * Validates CSS or Javascript.
+ */
+function cpn_validate($data, $type) {
+  $patterns = array(
+    'css' => '~<\s*\/?\s*style\s*.*?>~i',
+    'js' => '~<\s*\/?\s*script\s*.*?>~i',
+  );
+  return preg_match($patterns[$type], $data);
+}
+
+/**
+ * Saves CSS & Javascript in the file system (but only if not empty).
+ */
+function cpn_save_file($data, $filename) {
+  if (!drupal_strlen(trim($data))) {
+    return FALSE;
+  }
+  $path = 'public://cpn';
+  file_prepare_directory($path, FILE_CREATE_DIRECTORY);
+  return file_unmanaged_save_data($data, $path . '/' . $filename, FILE_EXISTS_REPLACE);
+}
