Location: PHPKode > projects > StreamOnTheFly > node/code/classes/sotf_Programme.class.php
<?php // -*- tab-width: 3; indent-tabs-mode: 1; -*-

/* 
 * $Id: sotf_Programme.class.php,v 1.84 2005/02/01 10:06:19 micsik Exp $
 *
 * Created for the StreamOnTheFly project (IST-2001-32226)
 * Authors: András Micsik, Máté Pataki, Tamás Déri 
 *				at MTA SZTAKI DSD, http://dsd.sztaki.hu
 */

define("GUID_DELIMITER", ':');
define("TRACKNAME_LENGTH", 32);

require_once($config['classdir'] . '/Tar.php');
require_once($config['classdir'] . '/unpackXML.class.php');
//require_once($config['classdir'] . '/packXML.class.php');
require_once($config['classdir'] . '/sotf_Statistics.class.php');
require_once($config['getid3dir'] . "/getid3.putid3.php");
require_once($config['classdir'] . '/sotf_Metadata.class.php');

class sotf_Programme extends sotf_ComplexNodeObject {
  
  var $topics;
  var $listenTotal;
  var $downloadTotal;
  var $visitTotal;
  var $rating;
  var $ratingTotal;
  var $genre;
  var $station;
  var $stationName;
  var $series;

  var $roles;
  var $extradata;
  var $other_files;
  var $media_files;
  var $links;
  var $rights;
  var $refs;

  /**
	* constructor
	*/
  function sotf_Programme($id='', $data='') {
	 $this->sotf_ComplexNodeObject('sotf_programmes', $id, $data);
	 if($this->exists()) {
		$station = $this->getStation();
		if($station) {
		  $this->stationName = $station->get('name');
		}
		//debug("stationName", $this->stationName);
	 }
  }
  
  /** finds the next available track id within that date ($track may be empty) */
  function getNextAvailableTrackId() {
	global $db;
		$track = $this->get('track'); 
		if(!$track)
		  $track = '1';
		else
		  $track = substr($track, 0, TRACKNAME_LENGTH);
		$this->set('track', $track);
		$count = 0;
		while($count < 50) {
		  $dir = $this->getDir();
		  if(!is_dir($dir)) {
			 $this->checkDirs();
			 return;
		  }
		  $track = $this->get('track'); 
		  $track++;
		  $this->set('track', $track);
		  $count++;
		}
		raiseError("Could not create unique dir for prog!");
  }

  function create($stationOrSeriesId, $track='') {
	 global $db, $repository;

	 $db->begin();
	 // TODO may need some locking to get unique track id
	 $container = & $repository->getObject($stationOrSeriesId);
	 switch(get_class($container)) {
	 case 'sotf_series':
		$this->set('series_id', $container->id);
		$station = $container->getStation();
		$this->set('station_id', $station->id);
		$stationName = $station->get('name');
		break;
	 case 'sotf_station':
		$this->set('station_id', $container->id);
		$stationName = $container->get('name');
		break;
	 default:
		raiseError("Invalid station or series id!");
	 }
	 if(empty($stationName))
		raiseError("station does not exist");
	 if(empty($track))
		$track = 'prg';
	 $this->getStation();
	 $this->set('entry_date', date('Y-m-d'));
	 $this->set('modify_date', date('Y-m-d'));
	 $this->set('track', sotf_Utils::makeValidName($track, 32));
	 $this->getNextAvailableTrackId();
	 // guid is obsolete, but anyway:
	 $this->set('guid', $this->station->id . GUID_DELIMITER . $this->get('entry_date') . GUID_DELIMITER . $this->get('track'));
	 if(parent::create()) {
		debug("created new programme", $this->id);
		$db->commit();
		return true;
	 }
	 $db->rollback();
	 raiseError("Could not create new programme");
  }

  function update() {
	 $this->set('modify_date', date('Y-m-d'));
	 sotf_NodeObject::update();
	 if($this->isLocal()) {
		$this->checkDirs();
		$this->addToUpdate('updateMeta', $this->id);
		// instead of immediate update:
		// $this->saveMetadataFile();
	 }
  }

  function getStation() {
	 if(!$this->station) {
		$this->station = $this->getObject($this->get('station_id'));
		$this->stationName = $this->station->get('name');
	 }
	 return $this->station;
  }

  function changeStation($newStationId) {
	 if($this->get('station_id') != $newStationId) {
		$oldDir = $this->getDir();
		// change station in SQL
		$this->set("station_id", $newStationId);
		$this->set("series_id", NULL);
		// we don't change entry_date!
		$this->update();
		// reinitialize station part in memory
		$this->station = NULL;
		$station = $this->getStation();
		// moving files to other station in file system
		$newDir = $this->getDir();
		debug("MOVING FROM", $oldDir);
		debug("MOVING PRG TO", $newDir);
		if(is_dir($newDir))
		  raiseError("Cannot move: a prg with same track exists!");
		//sotf_Utils::erase($newDir);  ?????
		$dateDir = $station->getDir() . '/' . $this->get('entry_date');
		if(!is_dir($dateDir))
		  mkdir($dateDir);
		rename($oldDir, $newDir);
	 }
  }

  function getSeries() {
	 $sid = $this->get('series_id');
	 debug('soid',$sid);
	 if(!empty($sid))
		return $this->getObject($sid);
	 else 
		return NULL;
  }

  function getAssociatedObjects($tableName, $orderBy) {
	 global $db;

	 $objects = $db->getAll("SELECT * FROM $tableName WHERE prog_id='$this->id' ORDER BY $orderBy");
	 return $objects;
  }

  /** returns the directory where programme files are stored */
  function getDir() {
	 $station = $this->getStation();
	 return $station->getDir() . '/' . $this->data['entry_date'] . '/' . $this->data['track'];
  }

  /** returns the directory where metadata/jingles/icons are stored */
  function getMetaDir() {
	 return $this->getDir();
  }

  /** returns directory where audio files are stored for the programme */
  function getAudioDir() {
	 return $this->getDir() . '/audio';
  }

  /** returns directory where other files are stored for the programme */
  function getOtherFilesDir() {
	 return $this->getDir() . '/files';
  }

  /** private
	  Checks and creates subdirs if necessary.
	*/
  function checkDirs() {
	 $station = $this->getStation();
	 $dir = $station->getDir();
	 if(!is_dir($dir))
		raiseError("Station " . $station->id ." does not exist!");
	 $dir = $dir . '/' . $this->get('entry_date');
	 if(!is_dir($dir))
		mkdir($dir, 0770);
	 $dir = $dir . '/' . $this->get('track');
	 if(!is_dir($dir)) {
		mkdir($dir, 0770);
	 }
	 if(!is_dir($dir . '/audio')) {
		mkdir($dir . '/audio', 0770);
	 }
	 if(!is_dir($dir . '/files')) {
		mkdir($dir . '/files', 0770);
	 }
  }

  function getFilePath($file) {
	 if(!$this->isLocal())
		raiseError('no_such_file');
	 if(is_object($file)) {
		if($file->tablename == 'sotf_media_files' && $file->get('main_content')=='t') {
		  return $this->getAudioDir() . '/' . $file->get('filename');
		} else {
		  return $this->getOtherFilesDir() . '/' . $file->get('filename');
		}
	 } elseif(is_array($file)) {
		if($file['main_content']=='t') {
		  return $this->getAudioDir() . '/' . $file['filename'];
		} else {
		  return $this->getOtherFilesDir() . '/' . $file['filename'];
		}
	 } else {
		raiseError("bad use of getFilePath method");
	 }
  }

  /** makes a new item available, announces to other nodes */
  function publish() {
	 $this->set('published','true');
	 $this->update();
  }

  /** marks as withdrawn, but not deletes it */
  function withdraw() {
	 $this->data['published'] = 'f';
	 $this->update();
  }

  /** return URL for this object on this node */
  function getURL() {
	 global $config;
	 return $config['rootUrl'] . '/get.php/' . $this->id;
  }

	/************************************************
	 *      METADATA
	 ************************************************/

  function saveMetadataFile() {
	 global $permissions;

	 if(!is_dir($this->getMetaDir())) {
		addError("Programme dir not found", $this->getMetaDir());
		return false;
	 }

	 $name = get_class($this);
	 $name = str_replace("sotf_", "", $name);
	 $xml = "<$name>";
	 $xml .= sotf_Utils::writeXML('data', $this->data, 1);
	 $roles = $this->getRoles();
	 $xml .= sotf_Utils::writeXML('role', $roles, 1);
	 $perms = $permissions->listUsersAndPermissions($this->id);
	 $xml .= sotf_Utils::writeXML('permission', $perms, 1);
	 $links = $this->getAssociatedObjects('sotf_links', 'caption');
	 $xml .= sotf_Utils::writeXML('link', $links, 1);
	 $rights = $this->getAssociatedObjects('sotf_rights', 'start_time');
	 $xml .= sotf_Utils::writeXML('right', $rights, 1);
	 $topics = $this->getTopics();
	 $xml .= sotf_Utils::writeXML('topic', $topics, 1);
	 $xml = $xml . "\n</$name>\n";
	 // TODO: save more data from other tables as well !!!!!

	 $file = $this->getMetaDir() . '/metadump.xml';
	 debug("dumping metadata xml in", $file);
	 $fp = fopen("$file", "w");
	 if(!$fp) {
		logError("Could not dump metadata into $file");
		// TODO: in this case the prg has been deleted in the meantime??
	 } else {
		fwrite($fp, $xml);
		fclose($fp);
	 }
	 
	 // save XBMF
	 if(is_writable($this->getMetaDir())) {
		$meta = new sotf_Metadata($this);
		$xbmf = $meta->getXBMFMetadata();
		$file = $this->getMetaDir() . '/metadata.xml';
		sotf_Utils::save($file, $xbmf);
	 }

	 // to change modify_date
	 $this->update(); 
	 return true;
  }


	/************************************************
	 *      STATISTICS AND FEEDBACK
	 ************************************************/

  function addStat($fileId, $type) {
		sotf_Statistics::addStat($this, $fileId, $type);
  }

  function getStats() {
	 return sotf_Statistics::getGlobalStats($this->id);
  }

  /** static */
  function getFileStats() {
	 global $db;
	 return $db->getRow("SELECT sum(f.filesize) AS filesize, sum(f.play_length) AS play_length FROM sotf_media_files f LEFT JOIN sotf_programmes p ON f.prog_id=p.id WHERE p.id IS NOT NULL ");
  }

  function getRefs() {
	global $db;

	 $id = $this->id;
	 $result = $db->getAll("SELECT * FROM sotf_prog_refs WHERE prog_id='$id'" );
	 if(DB::isError($result))
		return array();
	 else
		return $result;
  }

  /************************************************
   *   QUERIES
   ************************************************/

  /** get news for index page */
  function getNewProgrammes($fromDay, $maxItems) {
	 global $config, $db;

	 $sql = "SELECT i.* FROM sotf_programmes i, sotf_stations s WHERE i.station_id = s.id AND i.published='t' AND i.entry_date >= '$fromDay' ORDER BY i.entry_date DESC";
	 $res =	$db->limitQuery($sql, 0, $maxItems);
	 if(DB::isError($res))
		raiseError($res);
	 $results = null;
	 while (DB_OK === $res->fetchInto($row)) {
		$row['icon'] = sotf_Blob::cacheIcon2($row);
		$results[] = $row;
	 }
	 return $results;
  }

  /**
	* @method static countAll
	* @return count of available objects
  */
  function countAll() {
	 global $db;

	 return $db->getOne("SELECT count(*) FROM sotf_programmes WHERE published='t'");
  }

  /** static returns programmes owned/edited by current user */
  function myProgrammes($series, $filter, $sort, $count = false) {
	 global $permissions, $db, $user;

		if(!isset($permissions->currentPermissions))
		return NULL;  // not logged in yet
	 $sql = "SELECT  s.name AS station, se.name AS series, stats.visits, stats.listens, stats.downloads, flags.flags, rating.*, p.*".
		" FROM sotf_programmes p LEFT JOIN sotf_stations s ON p.station_id = s.id".
		" LEFT JOIN sotf_series se ON p.series_id=se.id".
		" LEFT JOIN sotf_prog_rating rating ON p.id=rating.prog_id".
		" LEFT JOIN sotf_user_progs flags ON p.id=flags.prog_id AND flags.user_id='$user->id'".
		" LEFT JOIN sotf_prog_stats stats ON stats.prog_id=p.id " .
      ", sotf_user_permissions u".
		" WHERE u.user_id = '$user->id' AND u.object_id=p.id";
	 if ($series != "allseries") $sql .= " AND p.series_id='$series'";
	 if ($filter == "all") ;
	 elseif ($filter == "published") $sql .= " AND p.published='t'";
	 elseif ($filter == "unpublished") $sql .= " AND p.published='f'";
	 else $sql .= " AND flags = '$filter'";
	 if ($sort) $sql .= " ORDER BY $sort";
	 
	 if ($count) return $db->getOne("SELECT count(*) FROM ($sql) as a");
	 $plist = $db->getAll($sql);
	 /*
	 foreach($plist as $item) {
		$retval[] = new sotf_Programme($item['id'], $item);
	 }*/
	 return $plist;
  }

  function getExpiredProgrammes() {
	 global $db, $config;
	 return $db->getCol("SELECT p.id FROM sotf_programmes p, sotf_node_objects n WHERE p.id=n.id AND expiry_date < CURRENT_DATE AND n.node_id='".$config['nodeId'] . "'"); 
  }

  function getMyExpiringProgrammes($days) {
	 global $db, $user;
	 if(!is_numeric($days))
		raiseError('invalid_parameter');
	 return $db->getCol("SELECT p.id FROM sotf_programmes p, sotf_user_permissions u WHERE p.id=u.object_id AND u.user_id='$user->id' AND u.permission_id=1 AND expiry_date < CURRENT_DATE + interval '$days days'"); 
  }

  /** static */
  function getPrgFromFilename($filename) {
	 global $db, $config, $page, $repository;
	 $repoDir = $config['repositoryDir'];
	 $len = strlen($repoDir);
	 debug("fname1", $filename);
	 if(substr($filename, 0, $len) == $repoDir) {
		$filename = substr($filename, $len);
		debug("fname2", $filename);
		if(preg_match("/\/(.*)\/(.*)\/(.*)\/(audio|files)\/(.*)$/", $filename, $mm)) {
		  $stationName = $mm[1];
		  $entryDate = $mm[2];
		  $track = $mm[3];
		  $data = $db->getRow("SELECT p.* FROM sotf_programmes p, sotf_stations s WHERE p.entry_date='$entryDate' AND p.track='$track' AND s.id = p.station_id AND s.name='$stationName'");
		  if($data && !DB::isError($data))
			 return new sotf_Programme($data['id'], $data);
		  else
			 return $page->getlocalized('unknown_audio');
		} else {
		  return $page->getlocalized('jingle');
		}
	 } else {
		return $page->getlocalized('unknown_audio');
	 }
  }

  /************************************************
   *  FILE MANAGEMENT
   ************************************************/

  /** Returns an array containing info about the available audio files. */
  function listAudioFiles($mainContent = 'true', $orderBy = 'filename') {
	 global $db;

	 $objects = $db->getAll("SELECT * FROM sotf_media_files WHERE prog_id='$this->id' AND main_content='$mainContent' ORDER BY $orderBy");
	 return $objects;
  }

  /** Returns an array containing info about the available other files. */
  function listOtherFiles() {
	global $db;

	 $objects = $db->getAll("SELECT * FROM sotf_other_files WHERE prog_id='$this->id' ORDER BY filename");
	 return $objects;
  }

  function selectFileToListen() {

	 // TODO: write this better
	 $files = $this->listAudioFiles();
	 // if lowest bitrate is free, select that
	 while(list(,$f) = each($files)) {
		if(preg_match("/^24kbps/", $f['format']) && $f['stream_access']=='t')
		  return $f['id'];
	 }
	 reset($files);
	 // return first free to stream
	 while(list(,$f) = each($files)) {
		if($f['stream_access']=='t')
		  return $f['id'];
	 }
	 return '';
  }

  function setAudio($filename, $copy=false) {
	 global $page;

	 $source = $filename;
	 if(!is_file($source))
		raiseError("no such file: $source");
	 $srcFile = new sotf_AudioFile($source);
	 $target = $this->getAudioDir() .  '/' . $this->get('track') . '_' . $srcFile->getFormatFilename();
	 if(!$srcFile->isAudio())
		raiseError("this is not an audio file");
	 //if(is_file($target)) {
	 //	raiseError($page->getlocalized('format_already_present'));
	 //}
	 // check and save length
	 $length = round($srcFile->duration);
	 if(!$this->get('length')) {
		$this->set('length', $length);
		$this->update();
	 } else {
		$diff = abs($this->get('length') - $length);
		if($diff > 15) { 
		  // allow for 15 sec of difference in program length
		  $page->addStatusMsg("audio_length_no_match");
		  //raiseError("audio_length_no_match");
		  $this->set('length', $length);
		  $this->update();
		}
	 }
	 if($copy)
		$success = copy($source,$target);
	 else
		$success = rename($source,$target);
	 if(!$success)
		raiseError("could not copy/move $source");
	 // save file info into database
	 $this->saveFileInfo($target, true);
  }

  function setOtherFile($filename, $copy=false) {
	 global $user;

	 $source =	$filename;
	 $target = $this->getOtherFilesDir() . '/' . basename($filename);
	 while (file_exists($target)) {
		$target .= "_1";
	 }
	 if (is_file($source))
		{
		  if($copy)
			 $success = copy($source,$target);
		  else
			 $success = rename($source,$target);
		}
	 if(!$success)
		raiseError("could not copy/move $source to $target");
	 // save into database
	 return $this->saveFileInfo($target, false);
  }

  function saveFileInfo($filepath, $mainContent = false) {
	 global $db;

	 // convert boolean into pgsql format
	 if($mainContent)
		$mainContent = 'true';
	 else 
		$mainContent = 'false';
	 // get audio properties of file
	 $file = new sotf_AudioFile($filepath);
	 // find if this is an existing file
	 if($file->isAudio()) {
		$fileInfo = new sotf_NodeObject('sotf_media_files');
	 } else {
		$fileInfo = new sotf_NodeObject('sotf_other_files');
	 }
	 $fileInfo->set("prog_id", $this->id);
	 $fileInfo->set("filename", $file->name);
	 $fileInfo->find();
	 // save file info into database
	 if($file->isAudio()) {
		$fileInfo->set('play_length', round($file->duration));
		$fileInfo->set('type', $file->type);
		if(is_numeric($file->bitrate)) {
		  // constant bitrate
		  $fileInfo->set('kbps', round($file->bitrate));
		  $fileInfo->set('vbr', 'f');
		} else {
		  // variable bitrate
		  $fileInfo->set('kbps', round($file->average_bitrate));
		  $fileInfo->set('vbr', 't');
		}
		$fileInfo->set('format', $file->getFormatFilename());
		$fileInfo->set('main_content', $mainContent);
	 }
	 $fstat = stat($filepath);
	 $fileInfo->set('filesize', $fstat['size']);
	 $fileInfo->set('last_modified', $db->getTimestampTz($fstat['mtime']));
	 $fileInfo->set('mime_type', $file->mimetype);
	 $success = $fileInfo->save();
	 //if(!$success)
	 //	raiseError("could not write into database");

	 // change id3 tags in file
	 $this->saveID3($file);

	 return $fileInfo->id;
  }

  /** saves some info into ID3 $file=sotf_AudioFile object */
  function saveID3($file) {
	 global $config;
	 $fileInfo = $file->allInfo;
	 $id3 = $fileInfo['id3v1'];
	 $id3['comment'] =  substr(substr($config['rootUrl'], 7), 0, 30);
	 $id3['album'] =  "id: " . $this->id;
	 if(!$id3['title'])
		$id3['title'] = substr($this->get('title'), 0, 30);
	 if(!$id3['artist'])
		$id3['artist'] = substr($this->getCreatorNames(), 0, 30);
	 debug("writing ID3V1", $id3);
	 $succ = WriteID3v1($file->path, $id3['title'], $id3['artist'], $id3['album'], $id3['year'], $id3['comment'], $id3['genre'], NULL /*track*/);
	 if(!$succ)
		logError("Could not change ID3V1 tags in file ". $file->path);
  }

  function deleteFile($fid) {
	global $repository;

	 $table = $repository->getTable($fid);
	 $file = new sotf_NodeObject($table, $fid);
	 if($table == 'sotf_media_files' && $file->get('main_content') == 't')
		$filepath = $this->getAudioDir() . '/' . $file->get('filename');
	 else
		$filepath = $this->getOtherFilesDir() . '/' . $file->get('filename');
	 $file->delete();
	 if (unlink($filepath))
		return 0;
	 else
		raiseError("Could not remove file $filepath");
  }

  /************************************************
   *      TOPICS
   ************************************************/

  function sortTopicsByName($a, $b) {
	 return strcmp($a['name'], $b['name']);
  }

  function getTopics($language='') {
	 global $vocabularies, $lang;
	 if(!$language)
		$language = $lang;
 
	 $topics = $this->getAssociatedObjects('sotf_prog_topics', 'id');
	 for($i=0; $i<count($topics); $i++) {
		$topics[$i]['name'] = $vocabularies->getTopicName($topics[$i]['topic_id'], $language);
	 }
	 usort($topics, array('sotf_Programme', 'sortTopicsByName'));
	 return $topics;
  }


  /************************************************
   *      XBMF import/export
   ************************************************/

  /** private: strips newlines, truncates, etc. metadata fields in XBMF */
  function normalizeText($text, $length=0) {
	 $text = str_replace("\r","", $text);
	 $text = str_replace("\n"," ", $text);
	 if($length) {
		if(strlen($text) > $length)
		  $text = substr($text, 0, $length-3) . '...';
	 }
	 return $text;
  }

  /** static: import a programme from the given XBMF archive */
  function importXBMF($fileName, $publish=false, $console=false) {
	 global $db, $config, $permissions, $repository, $vocabularies;

	 $pathToFile = $config['xbmfInDir'] . '/';
	 // create temp folder with unique name
	 $folderName = uniqid("xbmf");
	 if(!mkdir($pathToFile . $folderName)) {
		logError("Could not create dir for XBMF", $pathToFile . $folderName);
		return false;
	 }
	
	 // untar contents of file to folder
	 $tar = new Archive_Tar($fileName, true);								// create archive handler
	 $tar->setErrorHandling(PEAR_ERROR_PRINT);								// enable error reporting
	 $result = $tar->extract($pathToFile . $folderName);			// untar contents
	 debug("untar result", $result);
	
	 //parse the xml file
	 $metaFile = $pathToFile . $folderName . "/XBMF/Metadata.xml";
	 if(!is_file($metaFile)) {
		$metaFile = $pathToFile . $folderName . "/XBMF/metadata.xml";
		if(!is_file($metaFile)) {
		  logError("no metadata file found in XBMF!", $folderName);
		  return false;
		}
	 }
	 $myPack = new unpackXML($metaFile);
	
	 if(!$myPack->error){		//if the file has been found
		$metadata = $myPack->process();
	 }
		
	 if(!$metadata or $myPack->error){ //errors during import - stop execution
		sotf_Utils::delete($pathToFile . $folderName);
		echo "<font color=#FF0000><b>The import of $fileName did not succeed!</b></font>";
		logError("XML processing failed within this XBMF", $folderName);
		return false;	//did not succeed
	 }else{
		/*
		echo "Came In: " . $myPack->encoding . "<br>";
		echo "Went Out: " . $myPack->outencoding . "<br>";
		echo "<pre>";
		print_r($metadata);
		echo "</pre>";
		*/
		dump($metadata, "METADATA");
		debug("METADATA", $metadata);
	 }

	 $db->begin();

	 // Select station
	 $stId = trim($metadata['stationid']);
	 if(is_numeric($stId)) {
		$stId = $newPrg->makeId($config['nodeId'],  'sotf_stations', (int)$stId);
	 }
	 $station = &$repository->getObject($stId);
	 if(!$station) {
		logError("invalid stationid: ". $metadata['stationid']);
		return false;
		// by default I put the programme into the first station
		//$stId = $db->getOne("SELECT id FROM sotf_stations ORDER BY id");
		//$station = &$repository->getObject($stId);
	 }

	 // select/create programme entry
	 if($metadata['identifier']) {
		$prgId = sotf_Programme::getMapping($station->id, $metadata['identifier'], 'prg');
	 }
	 if($prgId) {
		// updating an exisiting programme
		debug("updating existing programme", $prgId);
		$newPrg =  new sotf_Programme($prgId);
		if($station->id != $newPrg->get('station_id')) {
		  logError("station provided in metadata is different from the station saved previously!");
		  return false;
		}
		//$station = &$repository->getObject($newPrg->get('station_id'));
		$updatingPrg = 1;
	 } else {
		// a new programme
		$newPrg = new sotf_Programme();
		$track = $metadata['title'];
		debug("create new programme with track", $track);
		$newPrg->create($station->id, $track);
		sotf_Programme::addMapping($station->id, $metadata['identifier'], 'prg', $newPrg->id);
	 }
	 $newPrg->set('foreign_id', $metadata['identifier']);

	 // select/create series
	 if($metadata['series'] && $metadata['series']['id']) {
		$seriesId = sotf_Programme::getMapping($station->id, $metadata['series']['id'], 'series');
		if(!$seriesId) {
		  $series1 = new sotf_Series();
		  $series1->set('name', $metadata['series']['title']);
		  $series1->set('station_id', $station->id);
		  $series1->find();
		  if($series1->exists())
			 $seriesId = $series1->id;
		}
		if($seriesId) {
		  $newPrg->set('series_id', $seriesId);
		  $series = &$repository->getObject($seriesId);
		} else {
		  $newSeries = 1;
		  $series = new sotf_Series();
		  $series->set('station_id', $station->id);
		}
		$series->set('name', $metadata['series']['title']);
		$series->set('description', $metadata['series']['description']);
		if($series->exists()) {
		  $series->update();
		} else {
		  $series->create();
		  sotf_Programme::addMapping($station->id, $metadata['series']['id'], 'series', $series->id);
		}
	 }

	 // permissions
	 foreach(array($metadata['owner'], $metadata['publishedby']) as $foreignUser) {
		if(is_array($foreignUser)) {
		  $userId = sotf_User::getUserid($foreignUser['login']);
		  debug("owner/publisher", $foreignUser);
		  if($userId) {
			 if($permissions->hasPermission($station->id, 'admin', $userId) ||
				 ($series && $permissions->hasPermission($series->id, 'create', $userId))) {
				// add permission for user
				$permissions->addPermission($newPrg->id, $userId, 'admin');
				$admins[] = $userId;
			 }
		  }
		}
	 }
	 // if we did not get permission info, add permissions for all station/series admins
	 debug("admins2", $admins);
	 if(empty($admins)) {
		if($series)
		  $admins1 = $permissions->listUsersWithPermission($series->id, 'admin');
		if(!$admins1)
		  $admins1 = $permissions->listUsersWithPermission($station->id, 'admin');
		while(list(, $admin) = each($admins1)) {
		  $admins[] = $admin['id'];
		  $permissions->addPermission($newPrg->id, $admin['id'], 'admin');
		}
	 }
	 debug("admins3", $admins);
	 // now create permissions
	 while(list(, $adminId) = each($admins)) {
		$permissions->addPermission($newPrg->id, $adminId, 'admin');
		if($newSeries)
		  $permissions->addPermission($series->id, $adminId, 'admin');
	 }

	 /*
	  * PART 2.2 - Insert all the relevant data from the xml file into the database
	  */

	 // basic metadata
	 $newPrg->set('title', sotf_Programme::normalizeText($metadata['title'],255));
	 $newPrg->set('alternative_title', sotf_Programme::normalizeText($metadata['alternative'],255));
	 $newPrg->set('episode_sequence', 0);
	 if(!empty($metadata['episodesequence'])) {
		$epiSeq = sotf_Programme::normalizeText($metadata['episodesequence']);
		if(is_numeric($epiSeq)) {
		  $newPrg->set('episode_sequence', (int)$epiSeq);
		} else {
		  logError("Bad episode sequence: ". $metadata['episodesequence']);
		}
	 }
	 $newPrg->set('abstract', sotf_Programme::normalizeText($metadata['description']));
	 $newPrg->set('keywords', sotf_Programme::normalizeText($metadata['keywords']));
	 
	 $newPrg->set("production_date", date('Y-m-d', strtotime($metadata['created'])));
	 $newPrg->set("broadcast_date", date('Y-m-d', strtotime($metadata['issued'])));
	 $newPrg->set("modify_date", date('Y-m-d', strtotime($metadata['modified'])));

	 $newPrg->set('language', $metadata['language']);
	 if($metadata['language']=='ger')
		$newPrg->set('language','deu');
	 if($metadata['language']=='English')
		$newPrg->set('language','eng');

	 $newPrg->update();
		
	 // topic
	 if($metadata['topic']) {
		$vocabularies->addToTopic($newPrg->id, $metadata['topic']);
	 }
	 // genre
	 $genre = trim($metadata['genre']);
	 if(is_numeric($genre)) {
		$newPrg->set('genre_id', $genre);
	 } else {
		logError("invalid genre id: " . $genre);
	 }

	 // rights
	 $rights = new sotf_NodeObject("sotf_rights");
	 $rights->set('prog_id', $newPrg->id);
	 $rights->set('rights_text', $metadata['rights']);
	 $rights->find();
	 $rights->save();

	 $db->commit();
	 // contacts
	 //$role = 21; // Other
		
	 foreach($metadata['publisher'] as $contact) {
		$role = 23; // Publisher
		$id = sotf_Programme::importContact($contact, $role, $newPrg->id, $station->id, $admins);
	 }
	 foreach($metadata['creator'] as $contact) {
		$role = 22; // Creator
		$id = sotf_Programme::importContact($contact, $role, $newPrg->id, $station->id, $admins);
	 }
		
	 if(is_array($metadata['contributor'])){
		foreach($metadata['contributor'] as $contact) {
		  $role = 24; // Contributor
		  $id = sotf_Programme::importContact($contact, $role, $newPrg->id, $station->id, $admins);
		}
	 }

	 /*
	  * PART 2.1 - Move the audio data to the specified station folder
	  */
	 
	 // insert audio
	 $dirPath = $pathToFile . $folderName . "/XBMF/audio";
	 $dir = dir($dirPath);
	 while($entry = $dir->read()) {
		if ($entry != "." && $entry != "..") {
		  $currentFile = $dirPath . "/" . $entry;
		  if (!is_dir($currentFile)) {
			 if(is_file($currentFile)) {
				debug("insert audio", $currentFile);
				$newPrg->setAudio($currentFile, true);
			 }
		  }
		}
	 }
	 $dir->close();

	 // insert other files
	 $dirPath = $pathToFile . $folderName . "/XBMF/files";
	 $dir = dir($dirPath);
	 while($entry = $dir->read()) {
		if ($entry != "." && $entry != "..") {
		  $currentFile = $dirPath . "/" .$entry;
		  if (!is_dir($currentFile)) {
			 $id = $newPrg->setOtherFile($currentFile, true);
			 debug("insert other", $currentFile);
			 /* by default, no need for this
			 if($id) {
				$fileInfo = &$repository->getObject($id);
				$fileInfo->set('public_access', 't');
				$fileInfo->update();
			 }
			 */
		  }
		}
	 }
	 $dir->close();

	 // insert metadata
	 if(is_readable($metaFile)) {
		debug("insert meta", $metaFile);
		$target1 = $newPrg->getMetaDir() . '/metadata.xml';
		$target2 = $newPrg->getMetaDir() . '/metadata-in.xml';
		if(!copy($metaFile, $target1))
		  logError("Could not copy metadata into $target1");
		if(!copy($metaFile, $target2))
		  logError("Could not copy metadata into $target2");
	 }

	 // insert icon
	 $logoFile = $pathToFile . $folderName . "/icon.png";
	 if(is_readable($logoFile)) {
		debug("insert icon", $logoFile);
		$newPrg->setIcon($logoFile);
	 }

	 // convert missing formats!
	 $audioFiles = & new sotf_FileList();
	 $audioFiles->getAudioFromDir($newPrg->getAudioDir());
	 $checker = & new sotf_AudioCheck($audioFiles);
	 $checker->console = $console; // if we don't want progress bars
	 $targets = $checker->convertAll($newPrg->id);
	 if(is_array($targets)) {
		foreach($targets as $target) {
		  $newPrg->setAudio($target);
		}
	 }

	 /*
	  * PART 2.3 - Remove (unlink) the xbmf file and the temp dir
	  */
		
	 //publish if needed
	 if($publish){
		$newPrg->publish();
	 }
	 
	 sotf_Utils::delete($pathToFile . $folderName);
	 //unlink($fileName);
		
	 return $newPrg->id;
  }//end func

  /** static: create contact record from metadata */
  function importContact($contactData, $contactRole, $prgId, $stationId, $admins) {
	 global $db, $permissions, $repository, $vocabularies, $config;

	 $db->begin();

	 // find out what should go into the 'name' field
	 if($contactData['type']=='organisation') {
		$name = $contactData['organizationname'];
	 } elseif($contactData['type']=='individual') {
		$name = $contactData['firstname'] . ' ' . $contactData['lastname'];
	 } else {
		logError("unknown type of contact: " . $contactData['type']);
		return null;
	 }

	 // if not exists, create new contact
	 $id = sotf_Contact::findByNameLocal($name);
	 if(!$id) {
		$contact = new sotf_Contact();
		$status = $contact->create($name, $stationId);
		if(!$status) {
		  //$page->addStatusMsg('contact_create_failed');
		  return null;
		}
		// add permissions for all station admins (??)
		while(list(, $adminId) = each($admins)) {
		  $permissions->addPermission($contact->id, $adminId, 'admin');
		}
	 } else {
		$contact = $repository->getObject($id);
	 }

	 //debug("contactData", $contactData);
	 // set/update contact data
	 $contact->set('acronym', $contactData['organizationacronym']);
	 $contact->set('alias', $contactData['alias']);
	 $contact->set('url', $contactData['uri']);
	 $contact->set('email', $contactData['email']);
	 $contact->set('address', $contactData['address']);
	 $contact->update();

	 // determine role
	 if($contactData['role']) {
		$language = 'eng'; // for now
		$rid = $vocabularies->getRoleId($contactData['role'], $language);
		if($rid)
		  $contactRole = $rid;
	 }
	 // create role
	 if(!sotf_ComplexNodeObject::findRole($prgId, $contact->id, $contactRole)) {
		$role = new sotf_NodeObject("sotf_object_roles");
		$role->set('object_id', $prgId);
		$role->set('contact_id', $contact->id);
		$role->set('role_id', $contactRole);
		$role->create();
	 }

	 $db->commit();

	 // fetch logo from url and store
	 if(!empty($contactData['logo'])) {
		$url = $contactData['logo'];
		if ($handle = @fopen($url,'rb')) {
		  $contents = "";
		  while (!feof($handle)) {
			 $contents .= fread($handle, 8192);
		  }
		  /*
		  do {
			 $data = fread ($handle, 100000);
			 if (strlen($data) == 0) {
				break;
			 }
			 //debug("received", strlen($data));
			 $contents .= $data;
			 } while(0); */
		  fclose($handle);
		  $tmpFile = tempnam($config['tmpDir'], 'logo_u');
		  debug("received logo from", $url);
		  sotf_Utils::save($tmpFile, $contents);
		  chmod($tmpFile, 0660);
		  $contact->setIcon($tmpFile);
		  unlink($tmpFile);
		} else {
		  logError("Could not fetch icon from $url");
		}
	 }

	 return $contact->id;
  }

  /** private */
  function getMapping($station, $foreignId, $type) {
	 global $db;
	 return $db->getOne("SELECT id_at_node FROM sotf_station_mappings WHERE id_at_station='$foreignId' AND station='$station' AND type='$type'");
  }

  /** private */
  function addMapping($station, $foreignId, $type, $localId) {
	 global $db;
	 return $db->query("INSERT INTO sotf_station_mappings (station,id_at_station,type,id_at_node) VALUES('$station', '$foreignId', '$type', '$localId')");
  }



}

?>
Return current item: StreamOnTheFly