Udemy API Coding – Course Page and Widget

I’ve been tooling around with coding an API for Udemy once I got my hands on the keys. I love parsing through data to create something useful. In this sense, I managed to code an extension page to my About Me section.

My Udemy Course Info – PC Affinity

Udemy API for Developers


What I did

I used 3 files to complete the information on that page. “udemy-include.php”, “udemyinfo.php”, and “udemyactivity-widget.php”. It’s a relatively simple PHP script that parses an API site using Curl. Most of the shared functions are within the udemy-include.php file; while extra functions are in each-other’s respective php file.

I coded this over the course of a few nights (on and off) using Notepad++. It has been years since I’ve coded in PHP, so it was a learning curve to figure out how to deal with json_decode’d arrays. After learning to view these arrays with ‘print_r’, learning how to pull the data using ‘->’ was a cinch. Udemy’s API is all over the place. I then had to split up the API calls within the foreach loops so that I could gather more data per course.

And the last thing that I wanted to mention was about wrapping my head around ‘date’ and ‘DateTime’. I learned the hard way that ‘date’ and ‘time’ are separate. And ‘strtotime’ was easily used to create new ‘date’ formats. It’s easier to think of ‘date’ as more of a formatted string. The ‘DateTime’ function, however, is closer to what I’m used to using in C#. It allowed me to get the exact date and time with the ability to view individual parts. This made calculation for my widget much easier.


My Code

udemy-include.php code:

<?php
define('ID', "**********");

$token = 'Basic *************************************************************************************************************';

// This function uses curl to create a json decoded array from the url
function CurlExec($url)
{
	global $token;
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch,CURLOPT_HTTPHEADER, array('Authorization: '.$token));
	curl_setopt($ch, CURLINFO_HEADER_OUT, true);
	$result=curl_exec($ch);
	curl_close($ch);
	return json_decode($result);
}

// The following functions are just used to setup the url string to pass

function GetUserCourses()
{
	$url = "https://fmolhs.udemy.com/api-2.0/organizations/*****/analytics/user-course-activity/?user_email=".ID;
	return CurlExec($url); 
}

function GetCourseInfo($cID)
{
	$url = "https://www.udemy.com/api-2.0/courses/".$cID."?fields[course]=url,image_50x50,price&page_size=100";
	return CurlExec($url);
}

function GetDayViewing($date)
{
	if (!isset($date)) { $date = date('now'); }
	$url = "https://fmolhs.udemy.com/api-2.0/organizations/*****/analytics/user-activity/?user_email=".ID."&page_size=100&from_date=".$date."&to_date=".$date;
	return CurlExec($url); 
}

function PrevActivity($date)
{
	if (!isset($date)) { $date = date('now'); }
	$url = "https://fmolhs.udemy.com/api-2.0/organizations/*****/analytics/user-progress/?user_email=".ID."&page_size=100&from_date=".$date."&to_date=".$date;
	return CurlExec($url);
}

?>

udemyinfo.php source:

<?php
include_once('udemy-include.php');

// I decided to write all of the HTML code into a string.
// You can use 'echo' instead, but I think this looks cleaner.
// You can also move it into it's own function if doing it this way.
$printString = "";
$printString .= "<hr>";

$result = GetUserCourses($user);

$printString .= '<style type="text/css">
            .info td {
			  padding-top:20px;
			  padding-bottom:20px;
			  padding-right:20px;   
			}

			.info td:first-child {
			  padding-left:20px;
			  padding-right:0;
			}
        </style>';
$sortedResults = $result->results;
usort($sortedResults, "compare_course_title");
usort($sortedResults, "compare_completed_date");
usort($sortedResults, "compare_completion_ratio");
$cnt = 0;
$printString .= "<table class=info>";
foreach ($sortedResults as $r)
{
	$color = "#000000";
	$completion = "";
	$lastAccess = "";
	
	if ($r->completion_ratio == 100.0)
	{
		$cnt++; $color = "#00ff00";
		$completion = $r->course_completion_date;
		if ($completion != "") {$completion = " Completed (".explode('T',$completion,2)[0].")"; }
	}
	else if ($r->completion_ratio > 0)
	{
		$color = "#0000ff";
		$lastAccess = $r->course_last_accessed_date;
		if ($lastAccess != "") {$lastAccess = " Last Viewed on (".explode('T',$lastAccess,2)[0].")"; }
	}
	
	$courseInfo = GetCourseInfo($r->course_id);
	$printString .= "<tr style='padding-bottom:10px;'><td>";

	$printString .= "<a target=\"_new\" href=\"https://udemy.com".$courseInfo->url."\">";
	$printString .= "<img src=\"".$courseInfo->image_50x50."\">";
	$printString .= "</a></td><td>";

	$printString .= "<a target=\"_new\" href=\"https://udemy.com".$courseInfo->url."\">";
	$printString .= "<font color='".$color."';>";
	$printString .= $font.$r->course_title;
	$printString .= "</font></a>";
	
	$printString .= "<span style=\"vertical-align:super;padding-left:6px;\">";
	$printString .= "<a style=\"text-decoration: none;\" target=\"_new\" href=\"https://fmolhs.udemy.com".$courseInfo->url."\">";
	$printString .= "<font style=\"font-size: 7px;\">FMOL</font></a>";
	$printString .= "</span>";
	
	$printString .= "<br>".$r->completion_ratio."%".$completion.$lastAccess;
	if ($courseInfo->price == "")
	{ $printString .= "<br>Course no longer available.<br><br>"; }
	#else
	#{ $printString .= "<br>".$courseInfo->price."<br><br>"; }
	$printString .= "</td></tr>";
}
$printString .= "</table>";
$printString .= "<br> Completed ".$cnt."\\".$result->count;
$printString .= "<br><br>";


// Print the HTML code:
echo $printString;

// The following functions are used to sort the results.
function compare_course_title($a, $b)
{
	return strcmp($a->course_title, $b->course_title);
}
function compare_completion_ratio($a, $b)
{
	if ($a->completion_ratio == $b->completion_ratio) { return 0; }
	return ($a->completion_ratio < $b->completion_ratio) ? 1 : -1;
}
function compare_completed_date($a, $b)
{
	if ($a->course_completion_date == $b->course_completion_date) { return 0; }
	return ($a->course_completion_date < $b->course_completion_date) ? 1 : -1;
}
function compare_accessed_date($a, $b)
{
	if ($a->course_last_accessed_date == $b->course_last_accessed_date) { return 0; }
	return ($a->course_last_accessed_date < $b->course_last_accessed_date) ? 1 : -1;
}
?>

PHP calling the widget:

<?php
// My widget is unable to see the permalink or query_string.
// So I pass both with the include so I can use them.
include('https://pcaffinity.com/files/scripts/udemyactivity-widget.php?perm='.get_permalink().'&'.$_SERVER['QUERY_STRING']);
?>

udemyactivity-widget.php

<?php
include_once('udemy-include.php');

$url_components = parse_url($_SERVER['HTTP_HOST'].'?'.$_SERVER['QUERY_STRING']); 
  
// Use parse_str() function to parse the string passed via URL 
parse_str($url_components['query'], $params); 
      
// Permalink saved from the widget call.
$perm = $params['perm'];
// ID updated from forms
$dtID = $params['dtID'];

$prev = 0;

// API is updated at 12pm CST
// Do not allow date before API updates
$dt = new DateTime();
if ((int)$dt->H >= 12)
{ $prev = -1; }
else
{ $prev = -2; }

if ($dtID == "")
{ $dtID = $prev; }

// Set past date depending on when the API was updated.
// Or depending on if a dtID parameter was found.
$date = date("Y-m-d h:i:s", strtotime(strval($dtID)." Days"));

// I could have saved all HTML to a string before using echo.
// However, this works just fine and is an alternate example.
echo "<div style='float:left; padding-right:10px;'>";
echo "Activity for ".date('Y-m-d', strtotime($date));
echo "</div>";
echo "<div style='float:left;'>";
echo '<form action="'.$perm.'?dtID='.strval($dtID - 1).'" method="post">';
echo '<input type="submit" name="dtadj" value="-" />';
echo '</form>';
echo "</div>";
echo "<div style='float:left;'>";
echo '<form action="'.$perm.'?dtID='.strval($dtID + 1).'" method="post">';
if ($dtID >= $prev)
{
	$dtID = $prev;
	echo '<input type="submit" name="dtadjp" value=" " disabled/>';
}
else
{
	echo '<input type="submit" name="dtadjm" value="+"/>';
}
echo '</form>';
echo "</div>";


$printString = GetPrevActivity($date);

//print all Activity HTML
echo $printString;

// Code used to get and print the Activity.
// Turned into a function so it can be reusable in the future.
function GetPrevActivity($d)
{
	$activityResult = PrevActivity($d);
	#print_r($activityResult);
	$cnt = $activityResult->{'count'};

	$tdstyle = "font-size:10px; padding-top:0px; padding-bottom:0px; padding-right:0px; border-width:1px; border-style:solid;";
	$tablestyle = "width:98%; order-width:1px; border-style:solid;";
	$divstyle = "width:280px; height:".($cnt > 0 ? '150' : '20')."px; overflow:scroll; overflow-x:hidden; border-width:1px; border-style:solid;";
	$pString = "";
	$pString .= "<div style='".$divstyle."'><table style='".$tablestyle."'>";
	foreach ($activityResult->results as $r)
	{
		$d1 = new DateTime($r->item_start_time);
		#print_r($d1);
		$d2 = new DateTime($r->item_completion_time);
		$diff = $d1->diff($d2);
		$pString .= "<tr><td style='".$tdstyle."'>";
		$pString .= $r->course_title."<br>";
		$pString .= $r->item_title."<br>";
		$pString .= $r->item_type." - ".$diff->i.":".$diff->s."s";
		$pString .= "</td></tr>";
	}
	$pString .= "</table></div>";
	$viewingResult = GetDayViewing($d);

	$track = 0;
	foreach ($viewingResult->results as $r)
	{
		$track += $r->num_video_consumed_minutes;
	}
	$trackTime = convertToHoursMins($track);
	if ($trackTime == "") { $trackTime = "0 Hours, 0 Minutes"; }
	$pString .= "<center>".$trackTime."</center>";
	return $pString;
}

// Simple function to return a time format used above.
function convertToHoursMins($time, $format = '%02d Hour(s), %02d Minute(s)')
{
    if ($time < 1) {
        return;
    }
    $hours = floor($time / 60);
    $minutes = ($time % 60);
    return sprintf($format, $hours, $minutes);
}
?>

Hope you enjoyed. If you have any questions I will try my best to answer them.

Thanks!