You are here

cpn.module in Code per Node 6

Same filename and directory in other branches
  1. 7 cpn.module

Primary hook implementations.

File

cpn.module
View source
<?php

/**
 * @file
 * Primary hook implementations.
 */

/**
 * 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_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' => 'textarea',
      '#title' => t('CSS'),
      '#default_value' => variable_get('cpn_css_' . $form['#node_type']->type, ''),
      '#description' => t('Custom CSS rules for this content type. Do not include @style tags.', array(
        '@style' => '<style>',
      )),
    );
    $form['cpn']['cpn_js'] = array(
      '#type' => 'textarea',
      '#title' => t('JavaScript'),
      '#default_value' => variable_get('cpn_js_' . $form['#node_type']->type, ''),
      '#description' => t('Custom JavaScript for this content type. Do not include @script tags.', array(
        '@script' => '<script>',
      )),
    );
    $form['cpn']['cpn_css_enabled'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable custom CSS per node.'),
      '#return_value' => 1,
      '#default_value' => variable_get('cpn_css_enabled_' . $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_enabled'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable custom JavaScript per node.'),
      '#return_value' => 1,
      '#default_value' => variable_get('cpn_js_enabled_' . $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.'),
    );
    $form['#validate'][] = 'cpn_node_type_validate';
    $form['#submit'][] = 'cpn_node_type_submit';
    $form['#after_build'][] = 'cpn_after_build';
  }

  // 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 block. 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 block. 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';
    }
  }
}

/**
 * Implementation of hook_init().
 */
function cpn_init() {
  $page_is_admin = arg(0) == 'admin';
  foreach (array(
    'css',
    'js',
  ) as $type) {

    // Only proceed if the 'agree' option was checked and if some code was
    // actually saved.
    $agree = variable_get('cpn_global_' . $type . '_agree', FALSE);
    $code = variable_get('cpn_global_' . $type, '');
    if ($agree && !empty($code)) {

      // Only proceed if either this is not an admin page or if the 'load on
      // admin pages too' option was checked.
      $force_on_admin = (bool) variable_get('cpn_global_' . $type . '_admin', FALSE);
      if (!$page_is_admin || $force_on_admin) {
        $file = file_create_path(variable_get('cpn_path', 'cpn') . '/global.' . $type);
        if (!empty($file)) {
          if ($type == 'css') {
            drupal_add_css($file, 'theme', NULL, variable_get('cpn_aggregation_css', TRUE));
          }
          else {
            drupal_add_js($file, 'theme', 'header', TRUE, variable_get('cpn_aggregation_js', TRUE));
          }
        }
      }
    }
  }
}

/**
 * "#after_build" function which adds syntax highlighting.
 * See http://drupal.org/node/322290.
 */
function cpn_after_build($form_element, &$form_state) {
  if (variable_get('cpn_syntax_highlighting', 0) === 'codemirror' && ($path = cpn_codemirror())) {
    drupal_add_js($path . '/lib/codemirror.js');
    drupal_add_css($path . '/lib/codemirror.css');
    if (isset($form_element['cpn']['css'])) {
      drupal_add_js($path . '/mode/css/css.js');
    }
    if (isset($form_element['cpn']['js'])) {
      drupal_add_js($path . '/mode/javascript/javascript.js');
    }
    drupal_add_css($path . '/theme/default.css');
    drupal_add_js(drupal_get_path('module', 'cpn') . '/cpn.js');
    drupal_add_css(drupal_get_path('module', 'cpn') . '/cpn.css');
  }
  return $form_element;
}

/**
 * Node type validation callback.
 * 
 * Ensures no "style" or "script" tags are included.
 */
function cpn_node_type_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>',
    )));
  }
}

/**
 * Node type submit callback.
 */
function cpn_node_type_submit($form, &$form_state) {

  // Delete existing files, then save them.
  foreach (array(
    'css',
    'js',
  ) as $type) {

    // Remove the existing file.
    cpn_delete_file($form_state['values']['type'] . '.' . $type);

    // Wrap the output.
    $output = cpn_wrap_output($form_state['values']['cpn_' . $type], 'node', $type);
    if (!empty($output)) {
      cpn_save_file($output, $form_state['values']['type'] . '.' . $type);
    }
  }
}

/**
 * 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);
        cpn_delete_file($node->nid . '.css');
        cpn_delete_file($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']);

        // Add the global wrapper code.
        foreach (array(
          'css',
          'js',
        ) as $type) {
          $output = cpn_wrap_output($node->cpn[$type], 'node', $type);
          if (!empty($output)) {
            cpn_save_file($output, $node->nid . '.' . $type);
          }
        }
      }
      break;

    // Deleting: delete from DB and file system.
    case 'delete':
      db_query("DELETE FROM {cpn} WHERE nid = %d", $node->nid);
      cpn_delete_file($node->nid . '.css');
      cpn_delete_file($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':

      // Attach the content type CSS/JS.
      $css = variable_get('cpn_css_' . $node->type, '');
      if (!empty($css)) {
        $css = file_create_path(variable_get('cpn_path', 'cpn') . '/' . $node->type . '.css');
        if (!empty($css)) {
          drupal_add_css($css, 'theme', NULL, variable_get('cpn_aggregation_css', TRUE));
        }
      }
      $js = variable_get('cpn_js_' . $node->type, '');
      if (!empty($js)) {
        $js = file_create_path(variable_get('cpn_path', 'cpn') . '/' . $node->type . '.js');
        if (!empty($js)) {
          drupal_add_js($js, 'theme', 'header', TRUE, variable_get('cpn_aggregation_js', TRUE));
        }
      }

      // 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');
        }
      }
      else {
        if (!empty($node->cpn['css']) && drupal_strlen(trim($node->cpn['css']))) {
          $css = file_create_path(variable_get('cpn_path', 'cpn') . '/' . $node->nid . '.css');
          if (!empty($css)) {
            drupal_add_css($css, 'theme', NULL, variable_get('cpn_aggregation_css', TRUE));
          }
        }
        if (!empty($node->cpn['js']) && drupal_strlen(trim($node->cpn['js']))) {
          $js = file_create_path(variable_get('cpn_path', 'cpn') . '/' . $node->nid . '.js');
          if (!empty($js)) {
            drupal_add_js($js, 'theme', 'header', TRUE, variable_get('cpn_aggregation_js', TRUE));
          }
        }
      }
      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);
    foreach (array(
      'css',
      'js',
    ) as $type) {
      if (!empty($form_state['values']['cpn'][$type])) {

        // Delete the existing file.
        cpn_delete_file($module . '-' . $delta . '.' . $type);

        // Add the global wrapper code.
        $output = cpn_wrap_output($form_state['values']['cpn'][$type], 'block', $type);

        // Output the file.
        if (!empty($output)) {
          cpn_save_file($output, $module . '-' . $delta . '.' . $type);
        }
      }
    }
  }
}

/**
 * Implementation of template_preprocess_block().
 */
function cpn_preprocess_block(&$vars) {
  $block = $vars['block'];
  if (!empty($block->css)) {
    $css = file_create_path(variable_get('cpn_path', 'cpn') . '/' . $block->module . '-' . $block->delta . '.css');
    if (!empty($css)) {
      drupal_add_css($css, 'theme', NULL, variable_get('cpn_aggregation_css', TRUE));
    }
  }
  if (!empty($block->js)) {
    $js = file_create_path(variable_get('cpn_path', 'cpn') . '/' . $block->module . '-' . $block->delta . '.js');
    if (!empty($js)) {
      drupal_add_js($js, 'theme', 'header', TRUE, variable_get('cpn_aggregation_js', TRUE));
    }
  }
}

/**
 * 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(variable_get('cpn_path', 'cpn'));
  if (empty($path)) {
    return FALSE;
  }
  file_check_directory($path, FILE_CREATE_DIRECTORY) or mkdir($path, 0755, TRUE);
  return file_save_data($data, $path . '/' . $filename, FILE_EXISTS_REPLACE);
}

/**
 * Deletes CSS & JavaScript from the file system (but only if it exists).
 */
function cpn_delete_file($filename) {
  $path = file_create_path(variable_get('cpn_path', 'cpn') . '/' . $filename);
  if (file_exists($path)) {
    return file_delete($path);
  }
  return FALSE;
}

/**
 * Implementation of hook_content_extra_fields().
 */
function cpn_content_extra_fields($type_name) {
  if (variable_get('cpn_css_' . $type_name, FALSE) || variable_get('cpn_js_' . $type_name, FALSE)) {
    $extras['cpn'] = array(
      'label' => t('Code Per Node'),
      'description' => t('Custom CSS and/or JS fields.'),
      'weight' => 5,
    );
    return $extras;
  }
}

/**
 * Returns path to CodeMirror, or FALSE if not found.
 */
function cpn_codemirror() {
  if (module_exists('libraries')) {
    $path = libraries_get_path('codemirror');
  }
  else {
    $path = 'sites/all/libraries/codemirror';
  }
  return file_exists($path) ? $path : FALSE;
}

/**
 * Implements hook_ctools_render_alter().
 */
function cpn_ctools_render_alter(&$info, $page, $args, $contexts, $task, $subtask, $handler) {

  // Only work with node views.
  if ($task['name'] == 'node_view') {

    // Get the node data.
    if (isset($contexts['argument_nid_1']) && isset($contexts['argument_nid_1']->data)) {

      // Pass the node to hook_nodeapi.
      cpn_nodeapi($contexts['argument_nid_1']->data, 'view', FALSE, TRUE);
    }
  }
}

/**
 * Wrap output for a given type using the wrapper variables.
 *
 * @param $code
 *   The code string to be wrapped.
 * @param $entity_type
 *   The type of object to be wrapped. For now this only supports 'node' and
 *   'block'.
 * @param $type
 *   The type of code, i.e. "js" or "css".
 *
 * @return string
 *   The wrapped string.
 */
function cpn_wrap_output($code, $entity_type, $type) {

  // If the code string is empty, return an empty string.
  if (empty($code)) {
    return '';
  }

  // Load the wrapper.
  $wrapper = variable_get('cpn_wrapper_' . $entity_type . '_' . $type, '[code]');

  // If the wrapper is empty, just return the raw code.
  if (empty($wrapper)) {
    return $code;
  }

  // Make the string replacement.
  $output = str_replace('[code]', $code, $wrapper);
  return $output;
}

Functions

Namesort descending Description
cpn_after_build "#after_build" function which adds syntax highlighting. See http://drupal.org/node/322290.
cpn_block_submit Block submit callback.
cpn_block_validate Block validation callback.
cpn_codemirror Returns path to CodeMirror, or FALSE if not found.
cpn_content_extra_fields Implementation of hook_content_extra_fields().
cpn_ctools_render_alter Implements hook_ctools_render_alter().
cpn_delete_file Deletes CSS & JavaScript from the file system (but only if it exists).
cpn_form_alter Implemenation of hook_form_alter().
cpn_init Implementation of hook_init().
cpn_menu Implementation of hook_menu().
cpn_nodeapi Implementation of hook_nodeapi().
cpn_node_type_submit Node type submit callback.
cpn_node_type_validate Node type validation callback.
cpn_perm Implementation of hook_perm().
cpn_preprocess_block Implementation of template_preprocess_block().
cpn_save_file Saves CSS & JavaScript in the file system (but only if not empty).
cpn_validate Validates CSS or JavaScript.
cpn_wrap_output Wrap output for a given type using the wrapper variables.