New versions of TableField module for D6 and D7
I just got around to making some final commits and packaging two new versions of the TableField module. The D6 version backports the "import from CSV" functionality. The import was the most requested feature, so there ya go! You can now import any valid CSV file to any table. The D7 version adds a super convenient rebuild function using the #ajax features of the Forms API.
For those of you who haven't tried it, the TableField module lets you add tables to your nodes with a convenient input widget. In Drupal 7, you can add tables to any entity. Any help testing the new modules would be greatly appreciated!
TableField for Drupal 7!
I just released an alpha version of TableField for Drupal 7. All of the hooks are updated to use the new Field API, so now you can add tables to any Drupal entity. There were a ton of little changes required, but it seems to be working pretty well. Any help testing would be greatly appreciated :)
inspired template
<?php
/**
* Implements theme_preprocess_page().
*/
function inspired_preprocess_page(&$vars) {
$vars['theme_path'] = drupal_get_path('theme', 'inspired');
// Ensure that we are editing with the admin theme
$arg0 = arg(0);
$arg1 = arg(1);
$arg2 = arg(2);
if ($arg0 == 'node' && is_numeric($arg1) && $arg2 != 'edit') {
$vars['template_files'][] = 'page-custom';
}
// Render main menu
//$vars['v']['main_menu'] = generate_main_menu();
}
/**
* Implements theme_preprocess_node().
*/
function inspired_preprocess_node(&$vars) {
$vars['theme_path'] = drupal_get_path('theme', 'inspired');
// Attach node data to article groups from articles
if ($vars['type'] == 'article_group') {
// Primary article
$primary = node_load($vars['field_agroup_particle'][0]['nid']);
$vars['v']['primary'] = $primary->v;
// Featured and More articles
$vars['v']['featured'] = load_related_articles($vars['field_agroup_farticles']);
$vars['v']['more'] = load_related_articles($vars['field_agroup_marticles']);
}
// See if any templates are bound to this content type
if ($vars['type'] != 'template') {
$result = db_query(
"SELECT n.nid
FROM {node} n
INNER JOIN {content_type_template} c
ON c.vid = n.vid
WHERE n.type = 'template'
AND c.field_content_type_value = '%s'",
$vars['type']
);
// Load the template data from its node
$nid = db_result($result);
if (is_numeric($nid)) {
$template = node_load($nid);
if (is_object($template)) {
$file = $template->field_template_file_name[0]['value'];
attach_template($vars, $file);
}
// Merge the $v arrays
if (is_array($template->v)) {
$vars['v'] = is_array($vars['v']) ? array_merge($vars['v'], $template->v) : $template->v;
}
}
kpr($vars);
}
// Check if the node has specified a template file
if (isset($vars['field_template_file_name'][0]['value'])) {
attach_template($vars, $vars['field_template_file_name'][0]['value']);
}
}
/**
* Helper function to attach a specified template to a node
* @param array $vars
* The variables preprocessed for a node template
* @param str $file
* The file name of the template without a .tpl.php extension
*/
function attach_template(&$vars, $file) {
$vars['template_files'][] = $file;
}
function load_related_articles($articles) {
$v = array();
if (!empty($articles)) {
foreach ($articles as $article) {
$node = node_load($article['nid']);
$v[] = $node->v;
}
}
return $v;
}
?>Translator
<?php
/**
* Implements hook_nodeapi().
*/
function translator_nodeapi(&$node, $op, $a3, $a4) {
// Translate bindings
if ($node->type == 'template' && $op == 'load') {
translate_bindings($node);
}
// Translate CCK fields
if ($op == 'load') {
translate_content($node);
}
}
/**
* Helper function to determine language setting
*/
function translator_language_fr() {
$fr = $_COOKIE['locale'] == 'fr_FR' ? true : false;
return $fr;
}
/**
* Helper function to translate content
*/
function translate_content(&$node) {
// Check if they are rendering in French
$fr = translator_language_fr();
foreach ($node as $key => $value) {
if (preg_match('/^field_en_(.*)$/', $key, $matches)) {
if ($fr && $node->{'field_fr_' . $matches[1]}[0]['value] != '') {
$node->v[$matches[1]] = $node->{'field_fr_' . $matches[1]}[0]['value];
}
else {
$node->v[$matches[1]] = $value[0]['value'];
}
}
}
}
/**
* Helper function to translate bindings
*/
function translate_bindings(&$node) {
// If bindings are provided, make them available
// to the node object.
if (isset($node->field_bindings_txt[0]['value'])) {
$bindings = tablefield_rationalize_table(unserialize($node->field_bindings_txt[0]['value']));
// Check if they are rendering in French
$fr = translator_language_fr();
// Create default binding values in English
// $binding[0] - Key
// $binding[1] - English value
// $binding[2] - French value
foreach ($bindings as $binding) {
// Override with french if specified
if ($fr && $binding[2] != '') {
$node->v[$binding[0]] = $binding[2];
}
else {
$node->v[$binding[0]] = $binding[1];
}
}
}
}
?>override module
CP Module
<?php
/**
* Implements hook_block().
*/
function cp_block($op = 'list', $delta = 0, $edit = array()) {
if ($op == 'list') {
$blocks = array();
$blocks['theme_switcher'] = array(
'info' => t('Theme Switcher'),
);
return $blocks;
}
elseif ($op == 'view') {
switch($delta) {
case 'theme_switcher':
// Capture the cp variable
$cp_theme = $_REQUEST['cp'];
// If it is already set, remove it from
// the destination link - else, add it
if ($cp_theme == 'true') {
$cp = '';
}
else {
$cp = 'true';
}
// Allow drupal to render the link
$link = l(t('Switch Theme'), $_REQUEST['q'], array('query' => array('cp' => $cp)));
return array(
'subject' => t('Theme Switcher'),
'content' => theme('switcher_block', $link),
);
break;
}
}
}
/**
* Implements hook_theme().
*/
function cp_theme($existing, $type, $theme, $path) {
return array(
'switcher_block' => array(
'template' => 'switcher_block',
'arguments' => array(
'link' => NULL,
),
),
);
}
/**
* Default implementation of theme_switcher_block().
*/
function theme_switcher_block() {
return 'My themed block content';
}
?>Override Module
<?php
/**
* Implements hook_form_alter().
*/
function override_form_alter(&$form, $form_state, $form_id) {
switch ($form_id) {
case 'event_node_form':
// Require the body
$form['body_field']['body']['#required'] = TRUE;
// Add in our own submit handler
$form['#submit'][] = 'override_node_submit';
break;
}
}
/**
* Custom submit handler
*/
function override_node_submit($form, &$form_state) {
dpm('Submit handler function');
}
function override_form_event_node_form_alter(&$form, $form_state) {
}
/**
* Implements hook_nodeapi().
*/
function override_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
switch ($op) {
case 'load':
foreach ($node as $key => $value) {
if (preg_match('/^field_english_(.*)$/', $key, $matches)) {
$node->translated[$matches[1]] = $value[0];
}
}
break;
}
drupal_set_title($node->translated['title']['value']);
}
/**
* Implements hook_menu().
*/
function override_menu() {
$items = array();
$items['override'] = array(
'title' => 'My override page',
'page callback' => 'override_pageview',
'access arguments' => array('access override page'),
);
$items['override/settings'] = array(
'title' => 'My override page',
'page callback' => 'override_pageview',
'access arguments' => array('access override page'),
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['override/options'] = array(
'title' => 'My override page',
'page callback' => 'override_pageview_options',
'access arguments' => array('access override page'),
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Implements hook_perm().
*/
function override_perm() {
return array(
'access override page',
);
}
function override_pageview_options() {
return 'my options page';
}
function override_pageview() {
return 'my page';
}
?>New Version of TableField Released
I finally got around to committing another round of fixes for the TableField module. It's a relatively simple CCK widget that allows you to add tabular data to a node. 6.1 takes care of one critical issue, one minor issue and some feature requests. I plan on spending my code sprint time at DrupalCon creating a Field version for D7.
Thanks to everyone who tested and submitted issues!
The danger of "q=$1"; or, why you shouldn't ignore Drupal 404 errors.
Drupal makes your life very easy, but that's no excuse for being lazy. While profiling scripts today, I ran into something that, for whatever reason, never clicked before. I was running Xdebug and KCachegrind on some complex pages to get an idea where I could optimize. Xdebug has a profiler built in that creates files digestible by something like KCachegrind. With profiling on, you get a profile dump file for every PHP script you run. You can in-turn open that file with KCachegrind to get some helpful analysis.
Profiling is a great way to expose those problems that I often ignore...
Using Syslog Module and Splunk to Rock Your Reporting World
Drupal's Syslog module is one of the core modules that I find is often overlooked. On a high traffic site, it's generally a good idea to reduce the db traffic as much as possible. Besides, since the database logging doesn't rotate/archive logs, it is only marginally helpful. So, while you need watchdog, it can quickly bite you in the... sorry, bad pun, but you get the idea. The good news is that we can still log all of these helpful messages in a flat file using Syslog and do some insanely cool reporting using Splunk.
Keeping an eye on memory usage
So, while working on a project recently we were faced with the reality that Drupal was using too much memory for our server to handle. The culprit was an AJAX refresh script that helped us to keep the page content up-to-date without refreshing. We had spent a good amount of time tuning the amount of processing and bandwidth used by the module, but the Drupal full bootstrap was just killing us with a 10 second refresh rate.
The solution we settled on was to not bootstrap Drupal if it wasn't necessary. We implemented a preliminary PHP script that the AJAX request was first passed to. This script opened a MySQL connection and ran a single query to check for updated content. If there was none, the script returned empty JSON. If content was found, then Drupal was fully bootstrapped to load the content and return a full JSON structure with the new content.
The results shouldn't be surprising, but still blew me away. Using the PHP function memory_get_peak_usage() we were able to see just how much memory the scripts were using at the point where the JSON was returned. When new content was found and Drupal bootstrapped PHP used 16961212 bytes (16.18 MB) of memory, but when there was no new content, the intermediate script only used 86968 bytes (84.9 KB) of memory.
Read more for the code...
Drupal, jQuery and $.ajax() easyness.
So, I've been remiss in my blogging as of late. It's not for a lack of ideas though, so I'm going to jump right back in with something cool that I've been using a lot lately. Once again, I'm just astounded at how insanely easy jQuery makes my life. I've been reading Resig's book on the train this week and the main thing I'm learning is just how important it is to have Javascript frameworks like jQuery. There are so many nuances between major browsers - even the "modern" ones - that it would be ridiculous to try and account for them yourself when these frameworks already do such a great job.
I've written before about doing modals using the popups api and it sounds like there is some goodies in ctools. Though, sometimes you just need laser precision to accomplish one specific task to meet a clients requirement. In these cases, I'm not at all opposed to writing a quick module.
Let's say you need a really simple module that provides a link to trigger something on the back end of your site and returns a response without a page refresh. This type of thing is super common these days and jQuery makes it oh-so-simple to implement.
Read on for the code...
