October 7, 2009
Drupal Inline Popup Reference Field

It seems that I am spending some time learning Drupal so the content of this post may defer from the editorial line a bit but as usual, I hope it may help someone.

The current drupal installation I am working with has CCK and Popups: Add and Reference (let’s call it PAR for future (shorter) references) modules. Unfortunately, by default the links added by PAR are in a new div tag below the input fields which increase the length of the form quite a bit as you can see below.

PAR - Links under input field

What I am proposing here is a way to put the PAR links inline with the input field. I am quite a beginner with Drupal so the method may not be really clean or fail proof and your comments about any easier way of doing it are welcomed. The result looks like the screenshot below.

PAR - Span inline tag

In order to get to the result, I had to modify the following files:

  1. the module file: popups_reference.module
  2. the template file of my theme: template.php

In the popups_reference.module file (that is located under sites/all/modules/popups_reference), I have modified the function popups_reference_alter_item(&$form, $key, $item, $fields) in order to remove the link addition from the $form[$key]['#suffix']. Instead, I am putting the links in an array that is used later by the post_render function.

The post_render function is actually looking for some string that I have added in the template.php and is replacing it if needed, i.e. if there is a PAR link to output. Let’s take at what was done in the template.php file (located under sites/all/themes/MYTHEME (where MYTHEME is to be replaced by the appropriate name, i.e. your theme name)). 

In the template.php, I override the form_element function that is used to display the field titles, description and input. You can see the complete function here under, but the only addition is the following line after the value is added to the $output variable.

  $output .= "<span class=\"popups-reference-link\"/>\n";
/**
* Function used to overwrite the default display of form elements.
* The description tag is placed before the input tag
* A span tag for the popups_reference module has been added before the final div.
* It allows to have the links created by the popups_reference module inline with the input field.
*/
function NewsFlash_form_element($element, $value) {
$output = '<div class="form-item"';
if (!empty($element['#id'])) {
$output .= ' id="'. $element['#id'] .'-wrapper"';
}
$output .= ">\n";
$required = !empty($element['#required']) ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';

if (!empty($element['#title'])) {
$title = $element['#title'];
if (!empty($element['#id'])) {
$output .= ' <label for="'. $element['#id'] .'">'. t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) ."</label>\n";
}
else {
$output .= ' <label>'. t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) ."</label>\n";
}
}

$output .= " $value\n";

// Add a span tag that will be replaced if necessary when the popups_reference
// module adds links to create new node.
$output .= "<span class=\"popups-reference-link\"/>\n";

if (!empty($element['#description'])) {
$output .= ' <div class="description">'. $element['#description'] ."</div>\n";
}

$output .= "</div>\n";

return $output;
}

And that’s it to display the “Add New: Add XXX” link inline with the input or select field.

I have attached the modified popups_reference.module and my template.php to this post. If you have any comment regarding a better way to do that, I’d be glad to know, so feel free to drop a comment.

<?php
// $Id: popups_reference.module,v 1.1.2.12 2009/03/07 06:54:25 starbow Exp $

/**
* @file
* Modify the Node Reference widget to use a popup to add a new node.
*/

$links_replacement;

/**
* Implementation of hook_form_alter().
*
* Modifies the nodereference setting form and the basic node form.
*/
function popups_reference_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'content_field_edit_form' && $form['#field']['type'] == 'nodereference') {
// Add a checkbox to the nodereference settings page.
$field_name = $form['#field']['field_name'];
$form['field']['show_add_link'] = array(
'#type' => 'checkbox',
'#default_value' => variable_get('popups_reference_show_add_link_'. $field_name, TRUE),
'#title' => t('Show the "Add New: Node Type" Popup links'),
'#description' => t("Activate Popups:Add and Reference behavior for this reference.")
);
$form['#submit'][] = '_popups_reference_manage_fields_submit';
}
elseif (isset($form['type'])) {
// Add the "Add New: Node Type" links.
$node = $form['#node'];
if ($form['type']['#value'] .'_node_form' == $form_id) {
$fields = content_fields();
foreach ($form as $key => $item) {
if (is_array($item)) {
$type = $item['#type'];
if ($type == 'fieldset') { // Loop through all the subitems.
foreach ($form[$key] as $subkey => $subitem) {
popups_reference_alter_item($form[$key], $subkey, $subitem, $fields);
}
}
else {
popups_reference_alter_item($form, $key, $item, $fields);
}
}

}
}
}
}

/**
* Implementation of hook_nodeapi().
* Add cookies with node info when a new node is created.
* These cookies will be found by the popups_reference behavior and used
* to select the newly created node in the reference widget.
*/
function popups_reference_nodeapi($node, $op) {
if ($op == 'insert') {
$five = time()+300; // 5 minutes in the future.
setcookie("PopupRefNid", $node->nid, $five, '/');
// setcookie("PopupRefTitle", $node->title, $five, '/');
setrawcookie("PopupRefTitle", rawurlencode($node->title), $five, '/');
}
}

/**
* Submit added to the the nodereference settings form.
* Set a variable for each nodereference field.
*/
function _popups_reference_manage_fields_submit($form, &$form_state) {
$field_name = $form['#field']['field_name'];
variable_set('popups_reference_show_add_link_'. $field_name, $form_state['values']['show_add_link']);
}

/**
* Run on every element in the basic node form.
* Wrap the enabled nodereference fields, and add the popup links.
*
* @param $form - the form (or fieldgroup).
* @param $key - form element name.
* @param $item - the form element array.
* @param $fields - all fields info.
*/
function popups_reference_alter_item(&$form, $key, $item, $fields) {
$field_name = strstr($key, 'field_'); // Check if $key starts with 'field_';
if (isset($fields[$field_name]) &&
$fields[$field_name]['type'] == 'nodereference' &&
variable_get('popups_reference_show_add_link_'. $field_name, TRUE)) {
$type = $form['type']['#value'];
$field = content_fields($field_name, $type);
$wrapper_id = 'popups-reference-' . _popups_reference_counter();
$links = _popups_reference_links($field, $type, $wrapper_id, $field['widget']['type']);
if ($links) {
// Put the nodereference widget and links in an wpopups link cssrapper.
// Makes it easy to find for Ahah targeting, and popups_reference behavior selecting.
global $links_replacement;
// Register the links into the global array. Key is the field name so that we can identify the
// correct element in the post_render method
$links_replacement[$field_name] = implode(', ', $links);
// Set prefix and suffix
$form[$key]['#prefix'] = '<div id="'. $wrapper_id .'">';
$form[$key]['#suffix'] = '</div>';
// Set the post render method that will be called when the variable are rendered
$form[$key]['#post_render'] = array('cck_field_post_render_popups_reference_link');
}
}
}

// Post_render method
function cck_field_post_render_popups_reference_link($content, $element){
global $links_replacement;
//dsm($element);
foreach( $links_replacement as $field_name => $links) {
// If the content contains the field_name, then we can display the links
if(strstr($content, $field_name)){
$tag_to_replace = "<span class=\"popups-reference-link\"/>";
$replacing_tag_prefix = '<span class="popups-reference-link"> Créer: ';
$replacing_tag_suffix = '</span>';
// If the type of nodereference is an input or a select, then we can replace all (i.e. the only one) span tag existing
if ($element['#type'] != 'nodereference_buttons'){
$content = str_replace($tag_to_replace, $replacing_tag_prefix . $links . $replacing_tag_suffix, $content);
}
// If the type of nodereference is a list of checkboxes, then we replace only the last span tag
// i.e the tag that appears after all the checkboxes
else {
$last_occurrence = strrpos($content, $tag_to_replace);
$content = substr_replace($content, $replacing_tag_prefix . $links . $replacing_tag_suffix, $last_occurrence, $last_occurrence + strlen($tag_to_replace) - strlen($content));
}
}
}
return $content;
}

/**
* Generates 'Add new...' link
* for each allowed content type
*
* @param $field
* @param $src_type - the type of base node.
* @param $wrapper_id - id for the wrapper around the node reference.
* @param $type - the type of widget.
* @return Array of html links.
*/
function _popups_reference_links($field, $src_type, $wrapper_id, $widget_type) {
if ($widget_type == 'nodereference_select' || $widget_type == 'nodereference_buttons') {
// Target the wrapper for replacing.
popups_add_popups(array('a.'.$wrapper_id=>array('targetSelectors'=>array('#'.$wrapper_id))));
}
else if ($widget_type == 'nodereference_autocomplete') {
// Don't replace the autocomplete when done.
popups_add_popups(array('a.'.$wrapper_id=>array('noUpdate'=>TRUE)));
}
else { // Unsupported type.
return;
}
$options = array(
'attributes' => array(
'class' => $wrapper_id . ' popups-reference',
'rel' => $wrapper_id,
),
'query' => array('destination' => 'node/add/' . str_replace('_', '-', $src_type)),
);
$links = array();
$all_types = node_get_types();
foreach ($field['referenceable_types'] as $add_type => $value) {
if (!empty($value) && (user_access("create $add_type content") || user_access('administer nodes'))) {
//if (!empty($value) && user_access("create $add_type content")) {
drupal_add_js(drupal_get_path('module', 'popups_reference') .'/popups_reference.js');
$path = 'node/add/' . str_replace('_', '-', $add_type);
$name = $all_types[$add_type]->name;
$links[] = l(" $name", $path, $options);
}
}
return $links;
}

/**
* A counter for generating unique element id's.
*
* @return int: next integer.
*/
function _popups_reference_counter() {
static $count = 0;
return $count++;
}

10:36pm  |   URL: http://tumblr.com/Zha3CysDV0P
(View comments  
Filed under: drupal popup 
August 26, 2009
Drupal Private Download Folder

Still working with Drupal and still learning a lot… The challenge I was facing was to let users upload files to a specific folder but to restrict access to that file so that it cannot be downloaded by anybody simply by giving the file path (private download). Therefore, users are allowed to post content but not access it (otherwise nothing prevent them to access files from other users in the folders).

Fortunately - as often with Drupal - there are already solutions on the web:

  1. http://www.drupalcoder.com/story/406-mixing-private-and-public-downloads…
  2. http://drupal.org/node/540754

To summarize, I mixed the two solutions given here above, because I had a problem when using only solution 1 (my drupal site is not at the root of the website but in a subfolder). Therefore I used method 2 and edited the .htaccess file at the root of the website in order to add the following line somewhere in the block delimited by <IfModule mod_rewrite.c>...</IfModule>:

RewriteRule ^sites\/default\/files\/(protected_download_dir\/.*)$ index.php?q=system/files/$1

For what I have noticed, it is also possible to edit put the line as

RewriteRule ^sites\/default\/files\/(protected_download_dir\/.*)$ /system/files/$1

but maybe one is better than the other.

After that, I just implemented the private download module given in one of the comment of the first link. As a result, the following is now the current flow:

  1. Some users are allowed to create a custom content containing a CCK filefield item so that they can upload a file in the private folder
  2. Once the file is uploaded, normal users do not have the permission to access the file
  3. Administrators receive a email that a new content containing a file has been uploaded
  4. Administrator with the privatedownload permission (part of the module) are allowed to access the file in the private folder and therefore allowed to download it

Note that as mentioned in the links above, this solution offers one private folder that would be like a pool in which user can throw their files but not access them. Only administrators are allowed to access the files. It is therefore easier if all files are situated in this unique folder so that administrators can retrieve / remove all files at once (by ftp/sftp/ssh) if necessary.

11:16pm  |   URL: http://tumblr.com/Zha3CysDRU_
(View comments
Filed under: drupal 
July 7, 2009
Share Firefox Profile Between Computers

It is no secret, it is easy to share a firefox profile. I am running a dual boot Linux / Windows and was therefore interested to share my Firefox profile between the two. But in the process, I actually became interested to be able to access that profile from my work computer. After all, there are already extensions to store bookmarks remotely so why not do the same with the complete profile.

This is the reason why I decided to create a new profile on my Dropbox. That way, the profile is stored remotely and synchronized in real-time between my work computer and my personal computer (both Linux and Windows).

Creating a new profile can be done in two ways. The first is by using the graphical interface and the second is by modifying the profiles.ini file directly. I will present both.

Add a profile with UI

To access the profile manager,

  • Under Linux: Open a console and type “firefox -profilemanager” (without the “”)

  • Under Windows: Open a command line (Windows+R), navigate to the firefox folder (cd C:\Program Files\Mozilla Firefox), and type “firefox -profilemanager”

This will open the profile manager window that should look like the following. In here, simply click the “Create Profile…” button.

The new windows allows to specify the folder where you want to store the profile.

Once the new profile is created, next time firefox start, the profile manager will pop-up and ask the profile that you want to use.

Note that if you want to completely replace your current profil with the “Dropbox” profile, you can simply copy and paste the content of the old default folder to the new “Dropbox” folder and then delete the default profile from the profile manager.

Modify directly the profiles.ini file

As an alternative to the UI method presented above, you can modify directly Firefox initialization file. To do so, you need to locate the file called profiles.ini. The path to the file is as follow:

  • Under Linux: /home/USERNAME/.mozilla/firefox/
  • Under windows: C:\Documents and Settings\USERNAME\Application Data\Mozilla\Firefox\

Open the file with your favorite text editor, and simply add a profile. After the addition of my dropbox profile, the file looks like the following:

The StartWithLastProfile indicates whether or not Firefox should ask you at start-up which profile to use or if it should load the previous profile that was in use. The first profile is my default profile and the second profile is the Dropbox profile. To switch to the whole dropbox solution, you just need to remove the default profile.

It is of course possible to move the content of your previous default profile to the new dropbox profile so that all previous information are copied.

A word about security

As anything that is stored on the cloud, be careful about what you put out there. By default, if you have allowed your password to be stored by Firefox without any master password, they are stored in clear. I actually haven’t checked where Firefox stores the passwords as I am not using it to store my passwords but I wouldn’t be surprised if it is stocked somewhere in the profile folder. So even though your Dropbox folder is private and probably encrypted on the server, better be safe than sorry. So use other password manager (like keypass for instance). You’ve been warned and can’t blame me if anything goes wrong ;-)

Last comment

Ok, this post was no rocket science :-). However, it gives a good overview of what you can do with the so-called “cloud” in order not only to keep your documents synchronized but your preferences. I am sure that you can use the method with other softwares but as mention previously, always keep the security in mind! Find out if the application store any password or personal information before adventuring yourself with such a solution.

Based on the scenario presented above, I am sure that one could easily put a more secure solution in place. One could for instance store the profile in an encrypted container (with truecrypt) that would be mounted at start-up. Using Dropbox is not the only solution of course. One could use his own folder anywhere on the web mounted as a network drive over ssh. I am sure that you can come up with many ideas about how to generalize the suggested idea…

July 6, 2009
Install Linux Mint on Lenovo X200

In my previous post, I detailed how I partitioned the drive of the X200 in order to install Linux. I will now give a few words about the installation process of Linux Mint 7. The focus of this post is set on how to keep your boot loader as it is and allow a dual boot windows/Linux from the Windows boot loader.

There are only a few things that I changed from the default installation. The first one concerns the partitioning. Since the drive is already partitioned, it is necessary to tell the installer which partitions to use for what. From the picture below, you can easily see which partitions are used and where they are mounted.

The important thing is to mount /boot on the primary partition created previously (/dev/sda4 in the example above). I decided to create a partition for /home as well so that a future upgrade of the system doesn’t overwrite personal data. This is a good common practice unless upgrade means global cleaning to you.

During the last step of the installation process, it is important to click on the advanced button if you don’t want Grub to be installed on the MBR. As stated in introduction, I want to keep the MBR so I specified that Grub should be installed on the primary partition that was created (/dev/sda4).

When installation is finished, restart the computer. There is at the moment no possibility to boot Linux as the MBR as not be replaced and no entry has been added to Windows boot file. It is therefore necessary first to reboot the live CD so that we can copy Linux boot sector. To do so, open a terminal and enter the following command:

dd if=/dev/sda4 of=/media/disk/linux.bin bs=512 count=1

In the command line above, don’t forget to indicate the correct paths to the if and of parameters so that it reflects your installation. Note that in the example above I am copying the boot sector to a USB stick and it is necessary afterwards to copy from the USB stick to the C drive of windows.

When the boot sector is copied, we can copy it in a windows accessible folder and edit C:\boot.ini.If there is any problem to edit it, don’t forget to remove the Read-Only flag by right click > Properties. My boot.ini looks like the following:

[boot loader]
timeout=15
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect
c:\linux.bin="Linux-Mint"

You can specify the timeout and default system to start by modifying the line below [boot loader]. You will notice that when you chose the Linux-Mint entry, Grub will be called offering you some more options.

If you want more detailed instructions concerning the dual boot procedure, I can recommend the following articles:

6:30pm  |   URL: http://tumblr.com/Zha3CysDNWj
(View comments
Filed under: linux lenovo 
July 6, 2009
Partitioning Lenovo X200 to Install Linux

I wanted to install Linux on my computer (Lenovo X200) but I was a bit afraid to mess with the partition table due to the fact that there is a hidden Restore & Recovery partition available at boot up when one press on the ThinkVantage button.

I did want to keep Windows available as there are a few programs that can come handy and I didn’t want to mess with the default behavior of the computer (understand that the ThinkVantage button should still work, that windows should still be accessible and that I can basically still use the laptop the way I used to do :-)).

I will explain in the following paragraphs how to reformat the drive using open source tools and how I created the new partitions for the X200.

  1. Download UNetbootin and PartedMagic and install it on a USB stick.

    Make sure that you computer can boot on a USB stick (most recent computer can if the option is set up properly in the BIOS) and make sure that your USB stick is bootable. You can as well install it on the hardrive but if it boots from the harddrive, it may not allow to reformat the drive.

  2. Partition the disk

    I have divided the process in two parts:

    • Resize the Windows partition (followed by a reboot to verify that windows AND the recovery partition are accessible)
    • Create the new partitions

    Before resizing the windows partition, do not forget to defragment the disk so that there is no risk of data loss and to create a backup of your data. To resize, just boot on the USB stick and execute GParted. The interface is quite intuitive and many good tutorials exist online. Resizing is the easy part and can take a while so just be patient. Once resized, restart the computer and you will notice that Windows does a check of the drive at startup. The only problem I noticed was related to the icon of the C drive but I will come to that later.

    Now that we have some unused space, we can create the partitions that we want (reboot on the USB stick one more time to access GParted). The number of primary partitions is limited to 4 so we will create one extended partition (that will contain many logical partitions) and one primary partition. We need the primary partition in order for Linux to boot without installing the bootloader on the Master Boot Record (MBR).

    The partitions I have created can be easily seen on the picture above but here is a quick explanation of the steps:

    • Create an extended partition (just leave around 200 MB at the end)
    • Create an primary partition in the last 200 MB that you left in the first step
    • Within the extended partition, create logical partitions. I have created the following:
      • Two NTFS partitions
      • One partition that will be for Linux root
      • One partition for Linux home
      • One partition for swap

    Next time Windows restart, there is two more drives available but as mentioned, the only problem was related to the icon of the C drive. In order to fix it, launch TweakUI and go the Repair section to rebuild icons. Restart the computer and everything should be back to normal.

Liked posts on Tumblr: More liked posts »