University of Oregon

PHP redirects if the headers have already been sent.

I ran in to a situation with some inherited code today.
The developer wanted to redirect users to a course listing page if they landed on a date/time listing that couldn’t be found.
See this code below; they check for an empty $dateTime var and create a log message before attempting to redirect the user.

if (empty($dateTime)) {
  $logger->err('DateTime with the id of '.$_REQUEST['dt_id'].' cannot be located.');
  Header( 'Location: course_list.php'); // Go back to the course list
  exit;
}

Unfortunately the headers had already been sent to the browser and the Header function call was ignored and the user only saw a partially loaded web page with no body content.

In the past I had implemented a fall-back with a meta and js redirect but I thought that could be improved upon.
Stack Overflow had some nice responses to a previous question and one of them included Redirect function that checked if the headers were already sent: headers_sent().
I liked that. So I added my fall back to that function.

Next I swapped that Header function call with the Redirect function call:

if (empty($dateTime)) {
  $logger->err('DateTime with the id of '.$_REQUEST['dt_id'].' cannot be located.');
  Redirect('course_list.php'); // Go back to the course list
}

Problem solved.

Here’s the function (note: it’s wrapped in a function_exists() call to make sure I don’t declare this twice at any point):

<?php
if(!function_exists('Redirect')){
  function Redirect($url, $code = 302) //from http://stackoverflow.com/questions/768431/how-to-make-a-redirect-in-php
  {
    #echo 'redirecting';
    if (strncmp('cli', PHP_SAPI, 3) !== 0)
    {
      #echo 'cli';
      if (headers_sent() !== true)
      {
        if (strlen(session_id()) > 0) // if using sessions
        {
            session_regenerate_id(true); // avoids session fixation attacks
            session_write_close(); // avoids having sessions lock other requests
        }
        if (strncmp('cgi', PHP_SAPI, 3) === 0)
        {
            header(sprintf('Status: %03u', $code), true, $code);
        }
      header('Location: ' . $url, true, (preg_match('~^30[1237]$~', $code) > 0) ? $code : 302);
      } else { //headers have already been sent. Use Meta refresh with js location.href fallback
      ?>
        <em>This page has moved.</em><br />
        If you are not redirected automatically please follow this link: <a href=\"<?php echo $url; ?>"><?php echo $url; ?></a>.
        <meta http-equiv="Refresh" content="1;
        URL=<?php echo $url; ?>">
        <script language=javascript>
        setTimeout("location.href='<?php echo $url; ?>'",1);
        </script>
        <?php
      }//end if headers_sent

      exit();
    }
  }
}
#usage:
#Redirect('course_list.php');
?>

Drush enable all inactive modules

I’ve been using make files a lot lately and when ever I’m ready to release an updated file I like to verify that all the project versions are up to date.
To leverage Drupal’s update functionality each project needs to be enabled. So I typically generate a new site from the make file and enable all the projects with drush. The key to that is the combination of commands:

drush en `drush pm-list --status="disabled,not installed" --pipe`

Here I’m enabling a list of module; specifically drush’s list of disabled and not installed modules.

drush pm-list --status="disabled,not installed"

That code gets you part of the way there. The addition of this:

--pipe

makes it a script-able list. Then when I surround that with back-ticks and feed it to

drush en `...`

I’m in business. (more…)

Reponsive design and the iPad viewport

I had been noticing that the iPad’s viewport in webkit browsers was odd but I couldn’t put my finger on it.

So I opened Safari on the iPad and ran some JS in firebug lite; the result: 768×504 in landscape and 768×928 in portrait mode.
Not what I expected at all.

The code I ran in firebug lite was:

alert(jQuery(window).width() + 'x ' + jQuery(window).height());

(prepend javascript: for use in the URL bar)

Bottom line: the viewport width is 768px in both portrait and landscape mode. That’s a little odd.
(more…)

Clean up your curl – fixing relative links in scraped content

A few years ago I Added jQuery browser check for ODT and Skillport eLearning. This week I updated that functionality and noticed a relative link in some remote content I was scraping via cURL.
The problem being that the relative link needed to be fully qualified.

Here’s the old table that I cURL from the Skillport support site:
Screen Shot of the old compatibility table

Here’s the updated table:
a view of the table pulled in from skillport
The culprit is the “Click here to view Skillsoft’s Browser Support Statement” at the top of the table. “here” links to /44007.htm which isn’t helpful.
(more…)

Testing web pages in multiple browers with appleScript v.2

This is a follow up to a post from a few years ago: Using applescript to open a url in several browsers.
I’ve made some updates to the script since then and a few them were today so I thought I’d share.

The old script opened a URL in several browsers (failing to load the web page in parallels VM browsers). The updated script now plays nice with Parallels and captures any URL in your clipboard if applicable.
The key to playing nice with Parallels is to manually drag the application icon into the script so that the address of the browser in the remote VM is accessible to the script.
Here’s the updated script: (more…)

Mailman as a bulk mailer – imported member managment tips

I have 20000 email subscribers for two email lists (cjobs and ucjobs). These were all imported into our new Mailman service.
I had a few settings I needed to change to accommodate using Mailman as a bulk mailer. And it’s going pretty well.
First there were normal things like pre-pending the default email subject, updating the list name and bounce protocols, making sure no-one can post to the list except the one authorized user (bulk moderate every user).
That was easy.

But then there’s a default setting where any list member can view all the other members of the list… not good.
First I updated the html docs, removing the relevant info.
Then I needed update all the users; but there’s no bulk operation for that.
Instead there’s a Member Mgmt screen with columns of check boxes. CheckBoxMate to the rescue…
Almost; Mailman displays a max of 30 members at a time… (20000 / 30 = ouch), that’s a lot of clicking and dragging. (more…)

Handle sites/default/files redirect in Drupal 6 with Custom Errors module

When I’ve migrated sites into Aegir recently, I find that the users have many links in the content hard-coded to files in the default directory.
While it is possible to use the Scanner search & replace module to ferret those out. It’s not always thorough enough. For example, it doesn’t search and replace menu link urls, or content in blocks, views, headers, footers or custom fields. You can specify custom fields and locations to search but that’s another bag of worms that fraught with issues.
So I wanted to find a simple way to catch any hard coded links that I missed.
Enter the Custom Error module.

I’ve been using it to provide a populated search form on the 404 ‘Not found’ page as well as a site map but I also have a few custom redirects in there so adding an option for sites/default/files was simple.

Here’s the code: (more…)

Left Join example in Oracle 9 – Current OA's with Review Data

I recently wanted to combine two tables; Hoping to show all OA employees and any ‘1 on 1’ Review data for each. A simple query restricted my results to only show those employees who had review data. So a Left Join was in order. Luckily we’re using Oracle 9 and I didn’t have mess with weird join syntax (see Burleson’s article here: http://www.dba-oracle.com/tips_oracle_left_outer_join.htm).

All I had to work out was how to separate my criteria for the left table from the rest of the query.

Here’s an example:

select
  ROWNUM as "#",
  HRISMGR.EMPLOYEE.ID,
  decode(HRISMGR.EMPLOYEE.PREF_NAME,null,First_Name,HRISMGR.EMPLOYEE.PREF_NAME) as Preferred_Name,
  HRISMGR.EMPLOYEE.LAST_NAME,
  HRISMGR.JOB_DETAIL.JOB_DESC as Position_Title,
  HRISMGR.EMPLOYEE.ORGN_CODE_HOME as Home_Org,
  HRISMGR.JOB_GENERAL.CONTRACT_TYPE,
  HRISMGR.REVIEW.REVT_DATE,
  HRISMGR.REVIEW.REVIEWER_NAME
from
  HRISMGR.JOB_DETAIL,
  HRISMGR.JOB_GENERAL,
  HRISMGR.EMPLOYEE
Left join HRISMGR.REVIEW
  ON HRISMGR.REVIEW.WAREHOUSE_ID = HRISMGR.EMPLOYEE.WAREHOUSE_ID
  AND(  /*Don't included step increases or other types that don't include a 1 on 1 review*/
      (
        HRISMGR.REVIEW.REVT_COMPLETE IS NOT NULL
      )
      and
      (
        HRISMGR.REVIEW.REVIEWER_NAME IS NOT NULL
      )
    )
    and
    (
      HRISMGR.REVIEW.REVT_CODE IN
        (
          'R1',
          'RC'
        )
    )
    and
    (/*Reviews from the last 2 years */
    TO_CHAR(HRISMGR.REVIEW.REVT_DATE, 'YYYY') >= (TO_CHAR(CURRENT_DATE, 'YYYY')-2)
    )
/*end left join*/
where
(
	(
		(
			HRISMGR.JOB_DETAIL.STATUS  'T'		/*Not terminated */
		)
	)
	/* Primary job type only */
	and
	(
		HRISMGR.JOB_GENERAL.CONTRACT_TYPE = 'P'
	)
	/*Job Type: P (primary), S (secondary), or O (overload)*/
	and /*Qualify OAs*/
	/*All non-classfied employees*/
	/*(
		HRISMGR.JOB_GENERAL.PCLS_CODE  LIKE 'U%'
	)*/

	/*Non-teaching Postion Clasess from banner NTRPCLS*/
	(
		(
			(
				(
					HRISMGR.JOB_GENERAL.PCLS_CODE  LIKE 'UE%'
				)or
				(
					HRISMGR.JOB_GENERAL.PCLS_CODE  LIKE 'UG%'
				)or
				(
					HRISMGR.JOB_GENERAL.PCLS_CODE  LIKE 'UF%'
				)or
				(
					HRISMGR.JOB_GENERAL.PCLS_CODE  LIKE 'UH%'
				)or
				(
					HRISMGR.JOB_GENERAL.PCLS_CODE IN
					(
					'UV101',
					'UV301',
					'UV401',
					'UV501',
					'UV601',
					'UV701',
					'UW101',
					'UW301',
					'UW401',
					'UW501',
					'UW601',
					'UW701'
					)
				)
			)
		)
	)

	/*End Non-teaching Postion Clasess from banner NTRPCLS*/
	and
	(
		HRISMGR.JOB_DETAIL.JOB_DESC NOT LIKE '%Stipend%'
	)
)
and /*get current*/
(
	HRISMGR.JOB_DETAIL.EFFECTIVE_DATE = F_JOB_EFF_DATE
	(
		HRISMGR.JOB_DETAIL.WAREHOUSE_ID ,
		HRISMGR.JOB_DETAIL.POSN ,
		HRISMGR.JOB_DETAIL.SUFF ,
		TO_CHAR(CURRENT_DATE, 'DD-MON-YYYY')
	)
)
and
HRISMGR.EMPLOYEE.WAREHOUSE_ID = HRISMGR.JOB_GENERAL.WAREHOUSE_ID
and
HRISMGR.JOB_GENERAL.WAREHOUSE_ID = HRISMGR.JOB_DETAIL.WAREHOUSE_ID
and
HRISMGR.JOB_GENERAL.POSN = HRISMGR.JOB_DETAIL.POSN
and
HRISMGR.JOB_GENERAL.SUFF = HRISMGR.JOB_DETAIL.SUFF
order by
	1
;

The key being to add an “and” to the On statement of the Left Join; after that the Where clause only applied to the left table (all oa’s).

That gives me a list of all the OA’s and populates Review data where applicable.
There’s not much data in there yet but at least I have the framework for a report now.

Get the Unit for each Department in Excel using HRIS Data Warehouse Data

We have 7 units at the University and a Chart of Accounts is maintained defining each department and where they fall in the organization structure. The unit for each department is determined by looking up the 2nd level of depth in the roll-up hierarchy.

For example; here’s the first few rows of the chart:

Orgn Title Pred Data-Rollup
0 University of Oregon   /University of Oregon
500 President of the University 0 /University of Oregon/President of the University
100100 President Administrative Operations 500 /University of Oregon/President of the University/President Administrative Operations
101001 President’s Office Ops 100100 /University of Oregon/President of the University/President Administrative Operations/President’s Office Ops

The Pres. Department (500) reports to the University (0) and the Presidents Office Operations (100100) reports to the President (500) and you can see that hierarchical chain in the Data-Rollup column.
I’ve posted about how I created the data-rollup column in Data Warehouse queries here: updating-the-chart-of-accounts-to-include-the-hierarchy-path.

The units are only departments that report to the University (0):

Orgn Title Pred Data-Rollup Unit from rollup
500 President of the University 0 /University of Oregon/President of the University President of the University
100000 Senior VP & Provost 0 /University of Oregon/Senior VP & Provost Senior VP & Provost
200000 VP Academic Affairs 0 /University of Oregon/VP Academic Affairs VP Academic Affairs
400000 VP Finance & Administration 0 /University of Oregon/VP Finance & Administration VP Finance & Administration
422000 VP Student Affairs 0 /University of Oregon/VP Student Affairs VP Student Affairs
500000 VP University Relations 0 /University of Oregon/VP University Relations VP University Relations
540001 VP University Development 0 /University of Oregon/VP University Development VP University Development
690000 VP Rsch, Innovation & Graduate Educ 0 /University of Oregon/VP Rsch, Innovation & Graduate Educ VP Rsch, Innovation & Graduate Educ
900000 UO General 0 /University of Oregon/UO General UO General

Here you can see I’ve added a new column highlighting the unit as well. That column is the result of parsing out the Data-Rollup value and displaying the 2nd level.

Here’s the formula for Row 3:

=IFERROR(MID(G3,FIND("/",G3,2)+1,FIND("/",REPLACE(G3,1,FIND("/",G3,2)+1,""),1)),REPLACE(G3,1,FIND("/",G3,2),""))

Column G is the Data-Rollup value and basically, I look up the position of the 2nd ‘/’ and then capture everything between that and the next slash.

That’s handy later on when you’re looking a department that is nested 7 levels deep and you want to organized your data by unit.
In this use case, I have a report review dates for employees which includes their Department Title and Org Code.
To add the Unit to that report I used a simple vlookup in excel referencing the Chart of Accounts.
Steps:
Include a copy of the Chart of Accounts spreadsheet in your workbook.
Highlight the relevant section of the chart and create name for that section; I used ‘chart_of_accounts’.
With vlookup you need the first column of the selection to be the lookup value so be sure to start with the org code.
Naming the chart of accounts selection
The add a column to your spreadsheet for the Unit and add the vlookup code.
Here’s the formula for Row 4 of that spreadsheet:

=VLOOKUP(I4,chart_of_accounts,6,FALSE)

Column I is employee’s Org Code and I use that to find the unit in the Chart of Accounts.
Here’s a quick look at what that looks like in the report:
Unit data in the report

Nothing too exciting going on here but I wanted to document it so I have a reference later.
I’m inclined to add the unit to my data warehouse query and do away with this step but that’s for later.

sh script to update multiple .htaccess files

I have a couple of personal sites that I don’t use much but they have been getting pounded by hackers or at least hacker’s scripts.
So I grown tired of watching the same IP addresses bombard my different sites looking for vulnerabilities. They usually generate about 100 404’s at a time and those are only the attempts that failed. So to stop repeat offenders I wrote this script.
Many thanks to some code examples at cyberciti.biz and gabeanderson.com.

Here’s my file called blockip:
#!/bin/sh

if [ $# -lt 1 ] ; then
echo
echo Wrong number of params.
echo Try again using the following format:
echo "./blockip 91.121.83.100"
echo
exit 1
fi

FILES="$HOME/URL1.com/.htaccess
$HOME/URL2.com/.htaccess
$HOME/sub.URL3.com/.htaccess"

echo updating the following files:
for f in $FILES
do
echo "$f"
done

IFS=$'n'

for i in $FILES
do
echo "working on " "$i"
cp "$i" "$i.blockip_saved"
sed "s##Deny from $1n#g" "$i.blockip_saved" > "$i"

rm $i.blockip_saved #comment this line to save backup.
done

So it takes the ip address I pass it and looks in each of the .htaccess files for the tag: prepending that with “Deny from _ipaddress_” and we’re good to go.

So now I just have to type the offending ip address in once and it propagates across my sites.
It works for multiple ip’s as well; for ex: ./blockip “91.121.83.100, 91.121.160.160”

This method seems to work OK for now. I still get 404’s when the repeat offender returns but this time they are denied access before they might find a vulnerability.

It’s not a perfect solution. I would be nice to detect the hack attempt while it’s happening and block them dynamically while sending an abuse notification email to their ISP, but I haven’t stumbled across any scripts that will get me there yet.