BuddyPress Metas & Title using xprofile field data

17 08 2011

Populating the BuddyPress user Profile page’s title, meta description, and meta keywords with some of the user-generated content, delivered via xprofile fields, seems so much better than the default options, or even what’s available in seopress, the only current BuddyPress SEO plugin.

While we’re at it, setting the on-page SEO data for Activities based on the content is a great move too.

You’ll have to modify the fields based on the data you have available, and what fields feed into which page data, but this code, placed in your theme’s functions.php file, is a good start.
Want to make a plugin based on this code for the lay-men? Feel free.

Note: extractCommonWords function based on the function here.

function bp_xprofile_title($title=null, $without_site_name=null){
	global $bp, $current_blog;

	}else {

		$title_array = array();
		if($data = xprofile_get_field_data( 'Company Name', $bp->displayed_user->id))
			$title_array[] = "$data ({$bp->displayed_user->fullname})";
		if($data = xprofile_get_field_data( 'Business Overview', $bp->displayed_user->id))
			$title_array[] = $data;
		$title_array[] = $blog_title;
		$title = mb_strimwidth(strip_tags( esc_attr( implode( ' | ', $title_array))), 0, 70, '...');
	if (bp_is_activity_component() && is_numeric( $bp->current_action )) {
		$activity = bp_activity_get_specific( array( 'activity_ids' => $bp->current_action ) );
		if ( $activity = $activity['activities'][0])
				$title = mb_strimwidth(preg_replace("/[^A-Za-z0-9\s\s+\-]/", "", strip_tags( $activity->content)), 0, 70-3-3-strlen($blog_title), '...') . " | $blog_title";
	return $title;
add_filter('bp_page_title', 'bp_xprofile_title',0);

function extractCommonWords($string){
	$stopWords = array('i','a','about','an','and','are','as','at','be','by','com','de','en','for','from','how','in','is','it','la','of','on','or','that','the','this','to','was','what','when','where','who','will','with','und','the','www');
	$string = strip_tags($string);
	$string = trim($string); // trim the string
	$string = preg_replace('/[^a-zA-Z0-9\s\s+\-]/', '', $string); // only take alphanumerical characters, but keep the spaces and dashes too…
	$string = strtolower($string); // make it lowercase
	preg_match_all('/\b.*?\b/i', $string, $matchWords);
	$matchWords = $matchWords[0];
	foreach ( $matchWords as $key=>$item ) {
		if ( $item == '' || in_array(strtolower($item), $stopWords) || strlen($item) <= 3 ) {
	$wordCountArr = array();
	if ( is_array($matchWords) ) {
		foreach ( $matchWords as $key => $val ) {
			$val = strtolower($val);
			if ( isset($wordCountArr[$val]) ) {
			} else {
				$wordCountArr[$val] = 1;
	$wordCountArr = array_slice($wordCountArr, 0, 10);
	return array_keys($wordCountArr);

function bp_xprofile_meta()
	global $bp;

		$array = array();
		if($data = xprofile_get_field_data( 'Business Category', $bp->displayed_user->id))
			$array[] = htmlspecialchars_decode($data);
		if($data = xprofile_get_field_data( 'Years of Experience', $bp->displayed_user->id))
			$array[] = "$data years experience";
		if($data = xprofile_get_field_data( 'Location', $bp->displayed_user->id))
			$array[] = $data;
		if($data = xprofile_get_field_data( 'Business Overview', $bp->displayed_user->id))
			$array[] = $data;

		$desc = mb_strimwidth(preg_replace("/[^A-Za-z0-9\s\s+\-&]/", "", strip_tags( implode( ' - ', $array))), 0, 150, '...');

		echo '<meta name="description" content="'.$desc.'" />';

		if($data = xprofile_get_field_data( 'Profile Details', $bp->displayed_user->id))
			$array[] = $data;
		if($data = xprofile_get_field_data( 'Company Name', $bp->displayed_user->id))
			$array[] = $data;
		$array[] = $bp->displayed_user->fullname;

		$key_text = preg_replace("/[^A-Za-z0-9\s\s+\-]/", "", strip_tags( implode( ' ', $array)));
		$keys = extractCommonWords($key_text);
		echo '<meta name="keywords" content="'.implode(',', $keys).'" />';

	if (bp_is_activity_component() && is_numeric( $bp->current_action )) {
		$activity = bp_activity_get_specific( array( 'activity_ids' => $bp->current_action ) );
		if ( $activity = $activity['activities'][0])
				$content = preg_replace("/[^A-Za-z0-9\s\s+\-]/", "", strip_tags( $activity->content));
				echo '<meta name="description" content="'. mb_strimwidth($content, 0, 150, '...').'" />';
				$keys = extractCommonWords($content);
				echo '<meta name="keywords" content="'.implode(',', $keys).'" />';
add_action('wp_head', 'bp_xprofile_meta',1);

Automatically update your php_browscap.ini

19 10 2010

My website uses the PHP function get_browser to see if a client is Javascript enabled. (I use this primarily to see if the visitor is a search engine bot and should see a long list of data or a real person and can handle AJAX-loaded data in tabs – most real people these days are using Javascript-enabled browsers.)

get_browser relies on the php_browscap.ini file, and the problem is that keeping the php_browscap.ini file up-to-date can be a chore, and should certainly be done automatically. (I notice the problem now that I use Chrome, and with every major release, the browscap file has no clue about my new browser, which is silly.)

So I finally sat down and wrote a basic script to fetch the updated file and restart my web server, which I’ll run weekly. This works for my FreeBSD box – change the paths and download command to fit your installation.

/usr/local/bin/wget -Ophp_browscap.ini "http://browsers.garykeith.com/stream.asp?PHP_BrowsCapINI"
mv -f /usr/local/etc/php/php_browscap.ini /usr/local/etc/php/php_browscap.old.ini
mv -f php_browscap.ini /usr/local/etc/php/
/usr/local/etc/rc.d/apache22 restart

I saved to: usr/local/www/mysite.com/get_php_browscap.sh
Then add it to your crontab:

# php_browscap update
5       4       *       *       6       root    /usr/local/www/mysite.com/get_php_browscap.sh >> /var/log/cron.log

I run it Saturday morning.
Any questions?

How to browse PHP’s print_r output the right way

16 06 2010

I’ve had the pleasure recently of working with some complex object oriented PHP with massive objects or lists of objects. The easiest way to have a look at your data for analyzing and debugging is to print_r it. Unfortunately, skimming through this data can be tedious, especially if you want to skip a couple of large, nested objects that are irrelevant to your data analysis.

When programming, I always keep Notepad++ open so I can keep a bunch of data accesible in tabs, such as notes, texts, data files, etc (and my tabs of saved files are seamlessly preserved between sessions, which is critical, of course). Notepad++ can handle bunches of different file formats out of the box, but PHP print_r isn’t one of them.

So I’ve pasted my print_r output into Notepad++, and the new file language is “Normal Text”. Immediately, bracket matching/highlighting works, which is great. I can go to an opening parens and see where it ends. So that’s it, right? Well, Notepad++ can also do code folding, like when you’re browsing a class, and you want to see all the class functions without all that pesky function code cluttering your view. So if you’ve got a PHP file open, for example, you can either click the [-]  to the left of the code to fold that function, or go to View->Fold All, and then just click [+] to open the class and see the top-level items in the class in plain view.

So what about the print_r? Matlab! I haven’t used Matlab since my first or second year in engineering school, and never thought it would useful, but here it is. I don’t remember what the code looks like, and I don’t care. All I know is that when I select Language->Matlab, it let’s me do code folding on my print_r output, and that’s all that matters. A bunch of the other languages work well too, or the same as Matlab, but it was the first and best for my needs.

FYI, this breaks if you have variable data containing a chunk of text with newlines and whatnot, but so it goes.

(PHP) Timestamp for a WORKING date/time a number of minutes/days from a given start date

3 12 2009

That title long enough? It’s hard to describe this function, but you get it now, right?

Based on this function here.

* getTimePlusOffsetDuringWork
* The function returns the timestamp of a date + business minutes
* @param timestamp|string startDate Beginning date/time as base for offset
* @param numeric offsetMinutes Work minutes to add to the startDate
* @param mixed Array of holiday dates, preferrably in the 'MM-DD' format
* @return integer Timestamp for the resulting time during Work hours,
*     given the simple equation StartDate + offsetMinutes.
*     Ignores after-hours, weekends, and holidays [for the first year after the startDate].
* Limitations: too many to list entirely.
*     9am-5pm work day only
*     1 year of holidays only
*     no week/weekday dependent holidays
*     Need to set timezone prior to calling this function if used outside of the server's timezone
*     Relies on English locale setting
* @author Zvi Landsman
function getTimePlusOffsetDuringWork($startDate,$offsetMinutes,$holidays=array("12-25","12-26","01-01","07-04")){
    $j = $i = 0; //changed this to 0 or you were always starting one day ahead
    $tmp1 = validation::is_timestamp($startDate)?$startDate:strtotime($startDate); //worked out a timstamp to start with
    $offsetMinutes = round($offsetMinutes);
    $simple_time = strtotime("+$offsetMinutes minutes", $tmp1);
    if($simple_time <= strtotime('17:00', $tmp1))
        //offset is still today
        return $simple_time;
    //Check if start time is after-hours 
    if ($tmp1 > strtotime('17:00', $tmp1))
        $tmp1 = strtotime("09:00 +1 day", $tmp1);
    //This checks if the minute offset puts us into tomorrow
    $newdate = $tmp1;
    $minutes_left = $offsetMinutes%480;
    $newtime = strtotime("+ $minutes_left minutes", $newdate);
    if($newtime > strtotime('17:00', $newdate))
        $minutes_left = round(($newtime - strtotime('17:00', $newdate))/60);
        $tmp1 = strtotime("+ $minutes_left minutes", strtotime("09:00 +1 day", $tmp1));
        $tmp1 = strtotime("+ $minutes_left minutes", $tmp1);

    //create the holiday list for the first upcoming occurances only
    $holidayList = array();
    foreach ($holidays as $holiday)
        $time_stamp=strtotime($holiday, $tmp1);

        $date=($time_stamp < $tmp1)?
            strftime("%d-%m-%Y",strtotime($holiday.' +1 year')):
        $holidayList[] = $date;

    $days_to_offset = floor($offsetMinutes/480);
    while($i < $days_to_offset)
        $tmp2 = strtotime("+$j day", $tmp1);
        $day = strftime("%A",$tmp2);

        $tmp = strftime("%d-%m-%Y",$tmp2);
        if(($day != "Sunday") && ($day != "Saturday" )&&(!in_array($tmp, $holidayList)))
            $i = $i + 1;
            $j = $j + 1;

            $j = $j + 1;

    //$j = $j -1;
    return strtotime("+$j days",$tmp1);

I’m completely open to improvements in the comments.

strtotime for non-US (e.g. UK) dates

16 03 2009

Not a big novelty here, but I figured I’d show you what I did.

I’m working on an application for the UK market, and we have a number of date fields in the form. The previous Indian programmer took the easy way out and used text fields in the database to store the dates. When I added some more fields, I used the appropriate date/time field type, and had to store and retreive them appropriately.

The form uses a date picker taken from HTMLGoodies.  The prior version used another script which wasn’t working well, although it allowed for named months which removed any locale convention confusions. This date picker uses numeric dates, and allows for the UK “day/month/year” convention. Getting that into (and out of) MySQL is another story.

The “out of” is easy actually. Take a date and format it:


Before I did that, however, I wanted to test to see if the date was empty. Using empty() didn’t work, since MySQL returns an empty date as 0000-00-00, so I made another function:

function emptyD($d){
  return ($d=='0000-00-00')?true:empty($d);

Using strtotime, however, misinterprets the UK date format from the user, and cannot directly create a MySQL-ready date. Using the following function, quickly found at this Polish site, reformats the date from d/m/Y to m/d/Y using regular expressions (smart!).

function strtotime_uk($str)
  $str = preg_replace("/^\s*([0-9]{1,2})[\/\. -]+([0-9]{1,2})[\/\. -]*([0-9]{0,4})/", "\\2/\\1/\\3", $str);
  $str = trim($str,'/');
  //echo "Converted to UK date: $str<br>";
  return strtotime($str);

and formated for MySQL using

date("Y-m-d H:i:s", strtotime_uk($_POST['emp1_start']))

Going from a Live Site to a Database

3 03 2009

We all know that it’s a web developer’s job to turn a database into a live web site (often design the database too). But what about when a client wants to take the data on a live site, and turn it into a database/data file.

  1. Stage #1 – download the site
    Retreiving the site requires planning and foresight for the next step. Using wget, my first approach was to mirror the entire site

    wget -m -w2 -x -E -k -D  "www.thesite.com" -p http://www.thesite.com

    I was greeted with a restricted message, which lead me immediately to believe that wget was violating a robots.txt or similar restriction. I added to the above command:

    -U "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv: Gecko/2009011913 Firefox/3.0.6 (.NET CLR 3.5.30729)"

    which set the user agent to my browser settings.

  2. Stage #2 – prepare the files for pattern matching
    The easiest method I could imagine using was to combine all the (relevant) files into one file. I’m no Unix expert, but I was slightly familiar with cat. After some research, I combined cat with find, and later learned some xargs to make cat work for the job. But I kept running into problems, sometimes with syntax, sometimes with argument list size, and sometimes other unclear File errors.
  3. Stage #1 revisited – download a second site
    I decided to try the next site by downloading the pages directly into one file, using:

    wget -r -l inf --no-remove-listing -w2 -D "www.site2.com" http://www.site2.com -np -O site2.html -U "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv: Gecko/2009011913 Firefox/3.0.6 (.NET CLR 3.5.30729)"
  4. Stage #3 – the script
    My approach was to use a simple script using regular expressions to convert the data into a CSV file. Javascript will run pretty slow, so I modified an existing PHP script for our purposes.
    I gzipped the files on my unix box, and copied them to my local Windows machine.
    After getting the right regular expression using  The Regex Coach, I pushed the data file through, but hit some problems. As it turns out, one large file, what I was trying to acheive in Stage #2, didn’t fare well with preg_match_all. It seems the function hit a limit and stopped returning results.
    The previous script was equipped to read a directory and process each file independantly. So I found a class to emulate the unix split function, dividing the huge string into smaller, tolerable files. Of course, I might have been splitting the blocks and messing with the reg. exp. So instead I split the multi-file file into single-file strings

    $ar = explode('<html', $str);

    and handled each original “file” independantly.

  5. Stage #2 revisited – what to do with a complete site
    Now I have a site with lots of pages in lots of directories. What to do? I tried using Windows Search/Explorer to find the relevant files from the many directories, and copy them into a single directory to be processed by our now flexible script. But Search only returned 5,000 results (I found out later the file count was closer to 70,000), and when I tried to copy/move the files into a single directory, Windows hung for a few hours, gobbled the CPU and did nothing. A quick search found me XXCOPY, which I easily installed, flattened the directories, and ran our script without a hitch.

I’m sure there are quicker ways to do it by those Unix experts, but now I have this flexible script that can operate on a number of input types fairly easily, so I’m happy.

Let me know if you’re interested in seeing this PHP script.

Google Talk Status API in PHP

5 02 2009

I just put together this API in PHP for retrieving the online status of a Google Talk user. It’s based on the Google Talk badge but allows for server-side access to a user’s status.

Read the rest of this entry »