So, I created a content type called "Contact" with a cck textfield called "Skypename" (field_skypename). I know that if I write <a href="skype:someskypename?call">Call</a> I get a link that triggers a skype call to someskypename. What I would like to acheive is a block that:
- Appears only when viewing a Contact. No prob here, I just set the Contact node type pathauto settings to /contact, so in the block settings I set it to display only on contact/* paths
- Shows a link to call the viewed Contact if field_skypename is filled.
Some prob here... I'm not familiar with PHP, but I guess I should first pull out filed_skypename from the current node(how?), then check if it is filled(double how?), then build something like:

	print: '<a href="skype:'. $node->field_skype[0][value] .'?call">Call</a>';

I know it's a supernoob question, but... help please
p.s. I have Dru 6.10 and CCK 6.X-2.2

Comments

grobemo’s picture

You're definitely on the right track.

PHP has a handy function for checking whether a variable has meaningful content: empty(foo). You can read about empty() in the PHP documentation, but it returns true if foo is unset, null, an empty string, the number 0, an empty array, false, etc. Thus, !empty(foo) will be true whenever foo has meaningful content.

Try something like this:

<?php

  $skype_name = check_plain($node->field_skypename[0][value]); // Use check_plain as a security measure.
  if (!empty($skype_name)) { // Do the following only if $skype_name is not empty:
    print '<a href="skype:'. $skype_name .'?call">Call</a>';
  }

?>

The reason to use check_plain is that $node->field_skype[0][value] presumably contains (or could contain) user input, and you shouldn't print user input directly to output. You should always run it through some function that filters it first. Otherwise, you open security holes in your site.

The most Drupalicious way to generate links is with l(), but I don't know how to get it to recognize skype as a protocol.

nofuseto’s picture

Thanks for your (very) quick reply!
I think I understood your code and it quite makes sense to me, but I think there's a problem with how Drupal handles <a> tags. I tried your code in a block, in a Computed field, and on the node's body (PHP input filter always on), but the "Call" link never appears. It's strange because if I just write HTML code <a href="skype:someskypename?call">Call</a>, the link pops up easily... Must dig some more...

grobemo’s picture

I'll bet the problem is with accessing $node->field_skypename[0]['value'] rather than with the anchor tags. And in fact, now that I'm retyping it, I think I see the problem: we should have (single or double) quotation marks around 'value'. It should be:

$skype_name = $node->field_skypename[0]['value'];

If that doesn't work, we'll do some debugging to ensure that $node is defined, etc.

nofuseto’s picture

You're right, but it didn't work either. After some search I found out that the problem is with filter_xss_bad_protocol(), here: http://drupal.org/node/324731, But the two solutions given ther didn't work for me.

grobemo’s picture

That sounds like a more difficult problem then. I take it that your code outputs <a href="skypename?call">Call</a>?

I don't know anything about filter_xss_bad_protocol, so you may have to keep searching elsewhere, unless anyone else has any ideas.

nofuseto’s picture

The problem shows up only when trying to output the link with php. With static html it works... Anyway thank you for your support!

grobemo’s picture

I just tried to create a block with the code above (using a dummy variable instead of $node->field_skypename[0]['value']), and it seems to work fine. The link appears and it opens Skype. I think having the PHP input format gets around the filter issues.

Can you doublecheck the source code for the block and copy and paste it here?

nofuseto’s picture

Ok, I tried with these two:


  $skype_name = testname;
    print '<a href="skype:'. $skype_name .'?call">Call</a>';
  }

and:


  $skype_name = check_plain($node->field_skypename[0]['value']);
  if (!empty($skype_name)) {
    print '<a href="skype:'. $skype_name .'?call">Call</a>';
  }

But still no chance. Maybe is it because I'm on localhost?

grobemo’s picture

Here's what I did:

  1. Go to Administer > Site building > Blocks.
  2. Click "Add Block" at the top.
  3. Under "Block Description," type 'Skype'.
  4. Under "Block title," type something like 'Call this person on Skype'
  5. Under "Block body," enter the following:
    <?php
    
    $skype_name = 'foobar';
    if (!empty($skype_name)) {
      print '<a href="skype:'. $skype_name .'?call">Call</a>';
    }
    
    ?>
    
  6. Under "Input format," check 'PHP code'.
  7. Leaving everything else on its default settings, click 'Save block' at the bottom.
  8. On the main Blocks page, move the Skype block from Disabled to the Right Sidebar region. (Content would work, too.)
  9. At the bottom of the Blocks page, click 'Save blocks'.

Once I did that, I went to my home page, and there was a block titled "Call this person on Skype" with my link, fully functional, as its content.

Did you skip any of these steps? What exactly do you see where you're expecting to see the link? Anything?

nofuseto’s picture

Tried it, it works. So the thing is to pass the variable correctly...

grobemo’s picture

Okay, good.

Let's check that $node is defined before we try to get $skype_name.

Add the following to the end of the block you just created:

<?php

  if (!empty($node)) {
    print '<pre>';
    print_r($node);
    print '</pre>';
  }
  else {
    print '$node is empty.';
  }

?>

If $node is defined, you should see a detailed printout of $node and its contents. If you see '$node is empty', then we'll I'll have some more questions about exactly what you're looking to do on your site.

nofuseto’s picture

I get '$node is empty'.

grobemo’s picture

Okay. Well that shows us what the problem is. We've been trying to get $node->field_skypename[0]['value'] when $node isn't defined!

Let's say I create a Contact node with nid 212. If I understand your original post, you want this block to show up only when you're looking at yoursite.com/contact/212. Is that correct? Because if so, we can extract the nid from the path, load the node inside your block, and do whatever we want with it.

nofuseto’s picture

That sounds right to me. I don't want the block to show the call link only for a specific node. I would like it to show the current contact's (the node the user is viewing) call link. That means that if I have more contacts, everytime I view one of them, the block should let me call the current one. BTW I'm using pathauto. Is it a problem?

grobemo’s picture

Okay. That all sounds good. Pathauto is not a problem.

One way to solve the problem is by replacing all of the code in your block with the following. I'll explain the code below.

<?php

if (arg(0) == 'contact' && is_numeric(arg(1))) {
  if ($node = node_load(arg(1))) {
    $skype_name = check_plain($node->field_skypename[0]['value']);
  }

  if (!empty($skype_name)) {
    print '<a href="skype:'. $skype_name .'?call">Call</a>';
  }
}

?>

The arg function returns pieces of the path. So, arg(0) returns the first element in the path, which in your case will be 'contact'. In your case, arg(1) will return the nid of the Contact node that is currently being viewed.

This code checks to see that we're looking at a Contact node and that arg(1) is a numeric value. It then tries to load the node with the nid in the path. If that's successful, it tries to get $skype_name from it. If that's successful, it prints the link.

The code is a little ugly because, well, arg() is kind of ugly, but that's okay for now.

Give this a try and tell me if it works. (I have tested it because I don't have the Contact node type set up.)

UPDATE: Oops. There was a typo in the code. (I omitted an opening parenthesis after check_plain.) It's fixed now.

nofuseto’s picture

I tried this last code but it still doesn't work...
Let's see: 'contact' is the path I set in pathauto, field_skypename is the name of the cck field, and for the block settings I followed your comments strictly. I have no clue .

grobemo’s picture

Okay. Let's add some extra steps to the code for debugging purposes. We'll try to figure out where it's failing.

Try this:

<?php

if (arg(0) == 'contact' && is_numeric(arg(1))) {
  if ($node = node_load(arg(1))) {
    $skype_name = check_plain($node->field_skypename[0]['value']);
  }
  else {
    print 'Error 2. Could not load $node.';
  }

  if (!empty($skype_name)) {
    print '<a href="skype:'. $skype_name .'?call">Call</a>';
  }
  else {
    print 'Error 3. $skype_name was empty. Trying to printing $node:';
    if (empty($node)) {
      print 'Error 4: $node is empty!';
    }
    else {
      print '<pre>';
      print_r($node);
      print '</pre>';
    }
  }
}
else {
  print 'Error 1. arg(0) is '. arg(0) .' and arg(1) is '. arg(1);
}

?>

I know this is frustrating, but we are making progress!

Run that code and tell me what you see.

nofuseto’s picture

I get:
Error 1. arg(0) is node and arg(1) is 14
BTW thank for your patience...

grobemo’s picture

Okay. Interesting. And when you look at the address bar in your browser, what do you see? http://localhost/contact/14? Or http://localhost/node/14?

nofuseto’s picture

http://localhost/contact/jimbo , since I had set pathauto to display contacts like this: contact/[title-raw] , and my test contact's title is Jimbo

grobemo’s picture

Okay. I didn't realize that you would see contact/jimbo, rather than conctact/212, but that's fine. arg() seems to be ignoring the alias created by pathauto and using the "original" path, which is node/14. Try the workaround I posted and see if that works.

grobemo’s picture

Here's a possible workaround:

<?php

if (arg(0) == 'node' && is_numeric(arg(1))) {
  if ($node = node_load(arg(1))) {
    if ($node->type == 'contact') {
      $skype_name = check_plain($node->field_skypename[0]['value']);
    }
    else {
      print 'Error 5. $node is not a contact node.';
    }
  }
  else {
    print 'Error 2. Could not load $node.';
  }

  if (!empty($skype_name)) {
    print '<a href="skype:'. $skype_name .'?call">Call</a>';
  }
  else {
    print 'Error 3. $skype_name was empty. Trying to printing $node:';
    if (empty($node)) {
      print 'Error 4: $node is empty!';
    }
    else {
      print '<pre>';
      print_r($node);
      print '</pre>';
    }
  }
}
else {
  print 'Error 1. arg(0) is '. arg(0) .' and arg(1) is '. arg(1);
}

?>
nofuseto’s picture

IT WORKS!!! Wait I'm going to have a little dancing in the streets!!!

tike012’s picture

Congrats guys, and props to grobemo for his patience and generosity.

grobemo’s picture

Fantastic. Sorry I couldn't help you resolve the issue more quickly.

Here's a cleaner version (without the debugging code) for you when you get back from dancing in the streets:

<?php

if (arg(0) == 'node' && is_numeric(arg(1))) {
  if ($node = node_load(arg(1))) {
    if ($node->type == 'contact') {
      $skype_name = check_plain($node->field_skypename[0]['value']);
    }
  }

  if (!empty($skype_name)) {
    print '<a href="skype:'. $skype_name .'?call">Call</a>';
  }
}

?>

For the record, here's what the code does:

  1. Check whether the original path (i.e., not the alias created by pathauto) is node/NID, where NID is the numeric node ID of the node we're looking at.
  2. Tries to load the node with ID NID, failing "silently" (without doing anything) if it can't.
  3. Check whether $node is a Contact node, failing "silently" if it isn't. This prevents the block from showing any content on non-Contact nodes.
  4. Get the skypename from the CCK field, using check_plain to protect against malicious code.
  5. If skypename contains meaningful content (i.e., isn't empty, etc.), print a link to call that person on Skype.

Among the things that caused us problems in trying to figure this out (and thus things to check if you're having similar problems):

  • $node is not defined when the block is constructed, so we need to do it ourselves.
  • arg() ignores path aliases, so it saw arg(0) as 'node' and arg(1) as '14', not 'contact' and 'jimbo', even when the browser shows the path as 'contact/jimbo'.
nofuseto’s picture

Thank you very much for your help, you've been patient and explained everything in detail. Grazie mille! :)))

sjf’s picture

Thanks for this. How would you add a field if it had multiple values in an array (I need to add a list of URLs to a block which have been posted in the node using the CCK Link module)?

grobemo’s picture

This might get slightly more complicated because you've got links in there, but here's the basic idea:

If you have a field called foo that has multiple values, Drupal stores them in $node->field_foo[0], $node->field_foo[1], etc. You'll want to loop through those values and add to to your own array. With a simple textfield, you'd do something like this:

<?php
  $node = node_load(NID); // Replace NID with the nid of the node you want to work with.
  $foo = array();

  foreach ($node->field_foo as $key => $value) { // Loop through every value of the field.
    $foo[] = check_plain($value['value']);  // Add a sanitized each value to the end of our $foo array.
  }

  if (count($foo) > 0) { // Proceed only if there are some values in $foo.
    foreach ($foo as $item) { // Loop through each element in $foo.
      if (!empty($item)) { // Proceed only if $item is not empty (e.g., null or an empty string).
        print $item;
      }
    }
  }

?>

The catch here is that you want to display links, which are slightly more complicated. You'll need something like this:

<?php
  $node = node_load(NID); // Replace NID with the nid of the node you want to work with.
  $foo = array();

  foreach ($node->field_foo as $link) { // Loop through every value of the field.
    $foo[] = $link; // $link is an array containg the URL, title, etc.
  }

  if (count($foo) > 0) { // Proceed only if there are some values in $foo.
    foreach ($foo as $link) { // Loop through each element in $foo.
      print '<li>'; // or whatever markup you want before the link
      print l( (!empty($link['title']) ? $link['title'] : $link['url']), $link['url']); // See below.
      print '</li>'; // or whatever markup you want after the link
    }
  }

?>

This code uses Drupal's l() function, which sanitizes and formats links for you. There's a ternary ( x ? y : z ) operator in there in case titles are optional in your link field. That checks whether the title is non-empty, displaying the title if it's there and the URL if the title is empty.

sjf’s picture

Thank you. Below is the code I ended up using based on this and your previous examples. The node type on which I want the block to show is called 'project' and my CCK links field is called 'field_project_links'. It works, but the block still shows even if there are no links. I checked in Firebug and the first link in the list is still getting printed, even though it is empty.

<?php

if (arg(0) == 'node' && is_numeric(arg(1))) { //http://drupal.org/node/416630
  if ($node = node_load(arg(1))) {
    if ($node->type == 'project') {
$project_links = array();

  foreach ($node->field_project_links as $link) { // Loop through every value of the field.
    $project_links[] = $link; // $link is an array containing the URL, title, etc.
  }
}
  if (count($project_links) > 0) { // Proceed only if there are some values in $project_links.
      print '<ul class="project_links">';
    foreach ($project_links as $link) { // Loop through each element in $project_links.

      print '<li>';
      print l( (!empty($link['title']) ? $link['title'] : $link['url']), $link['url']);
      print '</li>';

  }
      print '</ul>';
}
}
}
?>

In the node/add form, the number of links that can be added is set to unlimited and by default there are two fields (with the option to add more). Could this be the issue?

grobemo’s picture

Looks good. Try replacing if (count($project_links) > 0) { with:

<?php
  if (!empty($project_links[0]['url'])) {
?>

I think you're on the right track about what's happening. Drupal creates the node object with (at least) one value in $node->field_project_links, even if the field is empty.

sjf’s picture

That works, many thanks. I discovered this can also be done in Views by setting the argument to Node: Nid, default argument to node ID from URL, but I prefer your method. Tangential, but in case anyone else needs it, you can hide the block on the node/edit form if necessary by including the following code in the Block page visibility textfield (from http://drupal.org/node/134425).

<?php
$match = TRUE;
$url = request_uri();

if (strpos($url, "edit")) {
  $match = FALSE;
}
return $match;
?>
Gabriel R.’s picture

But how would you render the field properly, as defined in the content type display settings?

For example, when the value is a node or user reference, it can be rendered as a view.