From 5507db9b15034c73d9a121596da4bf440206f173 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 3 Feb 2012 20:32:06 +0000 Subject: [PATCH 001/302] Initial migration code, and basic export for bookmarks --- apps/admin_export/settings.php | 7 +- apps/bookmarks/lib/migrate.php | 18 +++++ apps/user_migrate/appinfo/app.php | 26 +++++++ apps/user_migrate/appinfo/info.xml | 11 +++ apps/user_migrate/settings.php | 95 ++++++++++++++++++++++++ apps/user_migrate/templates/settings.php | 12 +++ lib/migrate.php | 66 ++++++++++++++++ lib/migrate/provider.php | 23 ++++++ 8 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 apps/bookmarks/lib/migrate.php create mode 100644 apps/user_migrate/appinfo/app.php create mode 100644 apps/user_migrate/appinfo/info.xml create mode 100644 apps/user_migrate/settings.php create mode 100644 apps/user_migrate/templates/settings.php create mode 100644 lib/migrate.php create mode 100644 lib/migrate/provider.php diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index a33c872ccf..cafd35570c 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -5,6 +5,8 @@ * * @author Thomas Schmidt * @copyright 2011 Thomas Schmidt tom@opensuse.org + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -22,13 +24,15 @@ */ OC_Util::checkAdminUser(); OC_Util::checkAppEnabled('admin_export'); + + if (isset($_POST['admin_export'])) { $root = OC::$SERVERROOT . "/"; $zip = new ZipArchive(); $filename = get_temp_dir() . "/owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; OC_Log::write('admin_export',"Creating export file at: " . $filename,OC_Log::INFO); if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { - exit("Cannot open <$filename>\n"); + exit("Cannot open <$filename>\n"); } if (isset($_POST['owncloud_system'])) { @@ -49,6 +53,7 @@ if (isset($_POST['admin_export'])) { } if (isset($_POST['user_files'])) { + // needs to handle data outside of the default data dir. // adding user files $zip->addFile($root . '/data/.htaccess', basename($root) . "/data/.htaccess"); $zip->addFile($root . '/data/index.html', basename($root) . "/data/index.html"); diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php new file mode 100644 index 0000000000..2e6581cd9f --- /dev/null +++ b/apps/bookmarks/lib/migrate.php @@ -0,0 +1,18 @@ +appid = 'bookmarks'; + // Create the xml for the user supplied + function export($uid){ + $xml = ''; + $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks WHERE *PREFIX*bookmakrs.user_id = ?"); + $bookmarks = $query->execute($uid); + OC_Log::write('user_migrate',print_r($bookmarks)); + foreach($bookmarks as $bookmark){ + $xml .= ''; + $xml .='DATA WILL BE HERE'; + $xml .= ''; + } + return $xml; + } +} diff --git a/apps/user_migrate/appinfo/app.php b/apps/user_migrate/appinfo/app.php new file mode 100644 index 0000000000..4a795a5474 --- /dev/null +++ b/apps/user_migrate/appinfo/app.php @@ -0,0 +1,26 @@ +. +* +*/ + +OC_APP::registerPersonal('user_migrate','settings'); + +?> \ No newline at end of file diff --git a/apps/user_migrate/appinfo/info.xml b/apps/user_migrate/appinfo/info.xml new file mode 100644 index 0000000000..6abcb4af92 --- /dev/null +++ b/apps/user_migrate/appinfo/info.xml @@ -0,0 +1,11 @@ + + + user_migrate + User Account Migration + Migrate your user accounts + 0.1 + AGPL + Tom Needham + 2 + + diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php new file mode 100644 index 0000000000..fbf190e37d --- /dev/null +++ b/apps/user_migrate/settings.php @@ -0,0 +1,95 @@ +. + * + */ +OC_Util::checkAppEnabled('user_migrate'); + + +if (isset($_POST['user_migrate'])) { + // Looks like they want to migrate + $errors = array(); + $root = OC::$SERVERROOT . "/"; + $user = OC_User::getUser(); + $zip = new ZipArchive(); + $tempdir = get_temp_dir(); + $filename = $tempdir . "/" . $user . "_export_" . date("y-m-d_H-i-s") . ".zip"; + OC_Log::write('user_migrate',"Creating user export file at: " . $filename,OC_Log::INFO); + if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { + exit("Cannot open <$filename>\n"); + } + + // Does the user want to include their files? + if (isset($_POST['user_files'])) { + // needs to handle data outside of the default data dir. + // adding user files + OC_Log::write('user_migrate',"Adding owncloud user files of $user to export",OC_Log::INFO); + zipAddDir($root . "data/" . $user, $zip, true, "files/"); + } + + // Does the user want their app data? + if (isset($_POST['user_appdata'])) { + // adding owncloud system files + OC_Log::write('user_migrate',"Adding app data to user export",OC_Log::INFO); + // Call to OC_Migrate for the xml file. + $appdatafile = $tempdir . "/appdata.xml"; + $fh = fopen($appdatafile, 'w'); + $appdata = OC_Migrate::export(OC_User::getUser()); + fwrite($fh, $appdata); + $zip->addFile($appdatafile, "appdata.xml"); + fclose($fh); + } + + $zip->close(); + + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($filename)); + header("Content-Length: " . filesize($filename)); + @ob_end_clean(); + readfile($filename); + unlink($filename); +} else { +// fill template + $tmpl = new OC_Template('user_migrate', 'settings'); + return $tmpl->fetchPage(); +} + +function zipAddDir($dir, $zip, $recursive=true, $internalDir='') { + $dirname = basename($dir); + $zip->addEmptyDir($internalDir . $dirname); + $internalDir.=$dirname.='/'; + + if ($dirhandle = opendir($dir)) { + while (false !== ( $file = readdir($dirhandle))) { + + if (( $file != '.' ) && ( $file != '..' )) { + + if (is_dir($dir . '/' . $file) && $recursive) { + zipAddDir($dir . '/' . $file, $zip, $recursive, $internalDir); + } elseif (is_file($dir . '/' . $file)) { + $zip->addFile($dir . '/' . $file, $internalDir . $file); + } + } + } + closedir($dirhandle); + } else { + OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); + } +} diff --git a/apps/user_migrate/templates/settings.php b/apps/user_migrate/templates/settings.php new file mode 100644 index 0000000000..ece8f70e06 --- /dev/null +++ b/apps/user_migrate/templates/settings.php @@ -0,0 +1,12 @@ +
+
+ t('Export your user account');?> +

t('This will create a compressed file that contains the data of owncloud account. + Please choose which components should be included:');?> +

+


+
+

+ +
+
diff --git a/lib/migrate.php b/lib/migrate.php new file mode 100644 index 0000000000..8f13d07f46 --- /dev/null +++ b/lib/migrate.php @@ -0,0 +1,66 @@ +. + * + */ + + +/** + * provides an interface to all search providers + */ +class OC_Migrate{ + static public $providers=array(); + + /** + * register a new migration provider + * @param OC_Migrate_Provider $provider + */ + public static function registerProvider($provider){ + self::$providers[]=$provider; + } + + /** + * export app data for a user + * @param string userid + * @return string xml of app data + */ + public static function export($uid){ + $xml = ''; + foreach(self::$providers as $provider){ + $xml .= ''; + $xml .= self::appInfoXML($provider->$appid); + $xml .= $provider->export($uid)); + $xml .= ''; + } + return $xml; + } + + /** + * generates the app info xml + * @param string appid + * @return string xml app info + */ + public static function appInfoXML($appid){ + $info = OC_App::getAppInfo($appid); + $xml = ''; + $zml .= 'INFO HERE'; + $xml .= ''; + return $xml; + } +} diff --git a/lib/migrate/provider.php b/lib/migrate/provider.php new file mode 100644 index 0000000000..920fde7db3 --- /dev/null +++ b/lib/migrate/provider.php @@ -0,0 +1,23 @@ + Date: Fri, 3 Feb 2012 20:48:32 +0000 Subject: [PATCH 002/302] fix syntax, add logging, debug xml output instead of zip --- apps/user_migrate/settings.php | 21 +++++++++++---------- lib/migrate.php | 4 +++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index fbf190e37d..5831bf54cb 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -49,21 +49,22 @@ if (isset($_POST['user_migrate'])) { // adding owncloud system files OC_Log::write('user_migrate',"Adding app data to user export",OC_Log::INFO); // Call to OC_Migrate for the xml file. - $appdatafile = $tempdir . "/appdata.xml"; - $fh = fopen($appdatafile, 'w'); + //$appdatafile = $tempdir . "/appdata.xml"; + //$fh = fopen($appdatafile, 'w'); $appdata = OC_Migrate::export(OC_User::getUser()); - fwrite($fh, $appdata); - $zip->addFile($appdatafile, "appdata.xml"); - fclose($fh); + //fwrite($fh, $appdata); + //$zip->addFile($appdatafile, "appdata.xml"); + //fclose($fh); } $zip->close(); - header("Content-Type: application/zip"); - header("Content-Disposition: attachment; filename=" . basename($filename)); - header("Content-Length: " . filesize($filename)); - @ob_end_clean(); - readfile($filename); + //header("Content-Type: application/zip"); + //header("Content-Disposition: attachment; filename=" . basename($filename)); + //header("Content-Length: " . filesize($filename)); + //@ob_end_clean(); + echo $appdata; + //readfile($filename); unlink($filename); } else { // fill template diff --git a/lib/migrate.php b/lib/migrate.php index 8f13d07f46..b3345ccd1a 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -41,11 +41,13 @@ class OC_Migrate{ * @return string xml of app data */ public static function export($uid){ + OC_Log::write('user_migrate','Starting user appdata export for: '.$uid,OC_Log::INFO); $xml = ''; foreach(self::$providers as $provider){ + OC_Log::write('user_migrate','Getting app data for app:'.$provider->appid,OC_Log::INFO); $xml .= ''; $xml .= self::appInfoXML($provider->$appid); - $xml .= $provider->export($uid)); + $xml .= $provider->export($uid); $xml .= ''; } return $xml; From ee88ded463b24a9b9cd8ba78a5df8657159eeef8 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 3 Feb 2012 21:00:12 +0000 Subject: [PATCH 003/302] more commenting out for debugging, added logging commands --- apps/bookmarks/appinfo/app.php | 4 ++++ apps/bookmarks/lib/migrate.php | 19 ++++++++----------- apps/user_migrate/settings.php | 2 +- lib/migrate.php | 5 +++-- lib/migrate/provider.php | 4 ++-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/bookmarks/appinfo/app.php b/apps/bookmarks/appinfo/app.php index 479d8ed476..5481218841 100644 --- a/apps/bookmarks/appinfo/app.php +++ b/apps/bookmarks/appinfo/app.php @@ -17,3 +17,7 @@ OC_App::addNavigationEntry( array( 'id' => 'bookmarks_index', 'order' => 70, 'hr OC_App::registerPersonal('bookmarks', 'settings'); require_once('apps/bookmarks/lib/search.php'); OC_Util::addScript('bookmarks','bookmarksearch'); + +// Include the migration provider + +require_once('apps/bookmarks/lib/migrate.php'); \ No newline at end of file diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 2e6581cd9f..d5a6a75ca8 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -1,18 +1,15 @@ appid = 'bookmarks'; // Create the xml for the user supplied function export($uid){ - $xml = ''; - $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks WHERE *PREFIX*bookmakrs.user_id = ?"); - $bookmarks = $query->execute($uid); - OC_Log::write('user_migrate',print_r($bookmarks)); - foreach($bookmarks as $bookmark){ - $xml .= ''; - $xml .='DATA WILL BE HERE'; - $xml .= ''; - } + $xml = 'debugfrombookmarks'; + //$query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks WHERE *PREFIX*bookmakrs.user_id = ?"); + //$bookmarks = $query->execute($uid); + //foreach($bookmarks as $bookmark){ + // $xml .= ''; + // $xml .='DATA WILL BE HERE'; + // $xml .= ''; + //} return $xml; } } diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 5831bf54cb..4fa4d23a6f 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -47,7 +47,7 @@ if (isset($_POST['user_migrate'])) { // Does the user want their app data? if (isset($_POST['user_appdata'])) { // adding owncloud system files - OC_Log::write('user_migrate',"Adding app data to user export",OC_Log::INFO); + OC_Log::write('user_migrate',"Adding app data to user export file",OC_Log::INFO); // Call to OC_Migrate for the xml file. //$appdatafile = $tempdir . "/appdata.xml"; //$fh = fopen($appdatafile, 'w'); diff --git a/lib/migrate.php b/lib/migrate.php index b3345ccd1a..e3394c5a67 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -25,7 +25,7 @@ * provides an interface to all search providers */ class OC_Migrate{ - static public $providers=array(); + static private $providers=array(); /** * register a new migration provider @@ -33,6 +33,7 @@ class OC_Migrate{ */ public static function registerProvider($provider){ self::$providers[]=$provider; + OC_Log::write('user_migrate','Provider registered',OC_Log::INFO); } /** @@ -46,7 +47,7 @@ class OC_Migrate{ foreach(self::$providers as $provider){ OC_Log::write('user_migrate','Getting app data for app:'.$provider->appid,OC_Log::INFO); $xml .= ''; - $xml .= self::appInfoXML($provider->$appid); + //$xml .= self::appInfoXML($provider->$appid); $xml .= $provider->export($uid); $xml .= ''; } diff --git a/lib/migrate/provider.php b/lib/migrate/provider.php index 920fde7db3..b604a01072 100644 --- a/lib/migrate/provider.php +++ b/lib/migrate/provider.php @@ -6,7 +6,7 @@ abstract class OC_Migrate_Provider{ public function __construct(){ OC_Migrate::registerProvider($this); } - public static $appid; + //public static $appid; /** * exports data for apps * @param string $uid @@ -19,5 +19,5 @@ abstract class OC_Migrate_Provider{ * @param string $query * @return array An array of OC_Search_Result's */ - abstract function import($data); + //abstract function import($data); } From 1133eaa679558a3f88a4678de977734533c29507 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 3 Feb 2012 21:28:58 +0000 Subject: [PATCH 004/302] load bookmarks provider class --- apps/bookmarks/lib/migrate.php | 1 + apps/user_migrate/settings.php | 2 +- lib/migrate.php | 7 +++---- lib/migrate/provider.php | 5 ++++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index d5a6a75ca8..bfbe3fc583 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -13,3 +13,4 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ return $xml; } } +new OC_Migrate_Provider_Bookmarks('bookmarks'); \ No newline at end of file diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 4fa4d23a6f..7e3510d97e 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -63,7 +63,7 @@ if (isset($_POST['user_migrate'])) { //header("Content-Disposition: attachment; filename=" . basename($filename)); //header("Content-Length: " . filesize($filename)); //@ob_end_clean(); - echo $appdata; + echo htmlspecialchars($appdata); //readfile($filename); unlink($filename); } else { diff --git a/lib/migrate.php b/lib/migrate.php index e3394c5a67..a1b4c5019b 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -33,7 +33,6 @@ class OC_Migrate{ */ public static function registerProvider($provider){ self::$providers[]=$provider; - OC_Log::write('user_migrate','Provider registered',OC_Log::INFO); } /** @@ -42,12 +41,12 @@ class OC_Migrate{ * @return string xml of app data */ public static function export($uid){ - OC_Log::write('user_migrate','Starting user appdata export for: '.$uid,OC_Log::INFO); + OC_Log::write('user_migrate','App data export started for user: '.$uid,OC_Log::INFO); $xml = ''; foreach(self::$providers as $provider){ OC_Log::write('user_migrate','Getting app data for app:'.$provider->appid,OC_Log::INFO); $xml .= ''; - //$xml .= self::appInfoXML($provider->$appid); + $xml .= self::appInfoXML($provider->appid); $xml .= $provider->export($uid); $xml .= ''; } @@ -62,7 +61,7 @@ class OC_Migrate{ public static function appInfoXML($appid){ $info = OC_App::getAppInfo($appid); $xml = ''; - $zml .= 'INFO HERE'; + $xml .= 'INFO HERE'; $xml .= ''; return $xml; } diff --git a/lib/migrate/provider.php b/lib/migrate/provider.php index b604a01072..9dd1b2f389 100644 --- a/lib/migrate/provider.php +++ b/lib/migrate/provider.php @@ -3,7 +3,10 @@ * provides search functionalty */ abstract class OC_Migrate_Provider{ - public function __construct(){ + public $appid; + + public function __construct($appid){ + $this->appid = $appid; OC_Migrate::registerProvider($this); } //public static $appid; From 24c79c5bceaa9381e892c2ed9d0eca512fec20ed Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 3 Feb 2012 22:41:39 +0000 Subject: [PATCH 005/302] Updated Migrate_provider bookmakr implementation --- apps/bookmarks/lib/migrate.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index bfbe3fc583..af76a4ef4d 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -2,14 +2,12 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ // Create the xml for the user supplied function export($uid){ - $xml = 'debugfrombookmarks'; - //$query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks WHERE *PREFIX*bookmakrs.user_id = ?"); - //$bookmarks = $query->execute($uid); - //foreach($bookmarks as $bookmark){ - // $xml .= ''; - // $xml .='DATA WILL BE HERE'; - // $xml .= ''; - //} + $xml = 'test'; + $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks WHERE *PREFIX*bookmarks.user_id = ?"); + $bookmarks =& $query->execute(array($uid)); + while ($row = $bookmarks->fetchRow()) { + $xml .= $row[0] . "\n"; + } return $xml; } } From 960dd750c95e116b76e17de728936a17556f2f93 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 5 Feb 2012 23:00:38 +0000 Subject: [PATCH 006/302] Added dbexport to export output. Initial import code. --- apps/admin_export/appinfo/app.php | 2 + apps/admin_export/appinfo/info.xml | 2 +- apps/admin_export/settings.php | 129 +++++++++++++++++++++-- apps/admin_export/templates/settings.php | 12 ++- 4 files changed, 134 insertions(+), 11 deletions(-) diff --git a/apps/admin_export/appinfo/app.php b/apps/admin_export/appinfo/app.php index beebb4864e..4e896abd6a 100644 --- a/apps/admin_export/appinfo/app.php +++ b/apps/admin_export/appinfo/app.php @@ -5,6 +5,8 @@ * * @author Dominik Schmidt * @copyright 2011 Dominik Schmidt dev@dominik-schmidt.de +* @author Tom Needham +* @copyright 2012 Tom Needham tom@owncloud.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/apps/admin_export/appinfo/info.xml b/apps/admin_export/appinfo/info.xml index df8a07c2f5..e434705c9a 100644 --- a/apps/admin_export/appinfo/info.xml +++ b/apps/admin_export/appinfo/info.xml @@ -5,7 +5,7 @@ Import/Export your owncloud data 0.1 AGPL - Thomas Schmidt + Thomas Schmidt and Tom Needham 2 diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index cafd35570c..62b4b68ca0 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -25,11 +25,15 @@ OC_Util::checkAdminUser(); OC_Util::checkAppEnabled('admin_export'); +define('DS', '/'); + + if (isset($_POST['admin_export'])) { $root = OC::$SERVERROOT . "/"; $zip = new ZipArchive(); - $filename = get_temp_dir() . "/owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; + $tempdir = get_temp_dir(); + $filename = $tempdir . "/owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; OC_Log::write('admin_export',"Creating export file at: " . $filename,OC_Log::INFO); if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { exit("Cannot open <$filename>\n"); @@ -40,37 +44,79 @@ if (isset($_POST['admin_export'])) { OC_Log::write('admin_export',"Adding owncloud system files to export",OC_Log::INFO); zipAddDir($root, $zip, false); foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dirname) { - zipAddDir($root . $dirname, $zip, true, basename($root) . "/"); + zipAddDir($root . $dirname, $zip, true, "/"); } } if (isset($_POST['owncloud_config'])) { // adding owncloud config // todo: add database export + $dbfile = $tempdir . "/dbexport.xml"; + OC_DB::getDbStructure( $file, 'MDB2_SCHEMA_DUMP_ALL'); + $zip->addFile($dbfile, "dbexport.xml"); + OC_Log::write('admin_export',"Adding owncloud config to export",OC_Log::INFO); - zipAddDir($root . "config/", $zip, true, basename($root) . "/"); - $zip->addFile($root . '/data/.htaccess', basename($root) . "/data/owncloud.db"); + zipAddDir($root . "config/", $zip, true, "/"); + $zip->addFile($root . '/data/.htaccess', "data/owncloud.db"); } if (isset($_POST['user_files'])) { // needs to handle data outside of the default data dir. // adding user files - $zip->addFile($root . '/data/.htaccess', basename($root) . "/data/.htaccess"); - $zip->addFile($root . '/data/index.html', basename($root) . "/data/index.html"); + $zip->addFile($root . '/data/.htaccess', "data/.htaccess"); + $zip->addFile($root . '/data/index.html', "data/index.html"); foreach (OC_User::getUsers() as $i) { OC_Log::write('admin_export',"Adding owncloud user files of $i to export",OC_Log::INFO); - zipAddDir($root . "data/" . $i, $zip, true, basename($root) . "/data/"); + zipAddDir($root . "data/" . $i, $zip, true, "/data/"); } } - $zip->close(); - header("Content-Type: application/zip"); header("Content-Disposition: attachment; filename=" . basename($filename)); header("Content-Length: " . filesize($filename)); @ob_end_clean(); readfile($filename); unlink($filename); + unlink($dbfile); +} else if( isset($_POST['admin_import']) ){ + + $root = OC::$SERVERROOT . "/"; + $importname = "owncloud_import_" . date("y-m-d_H-i-s"); + + // Save data dir for later + $datadir = OC_Config::getValue( 'datadirectory' ); + + // Copy the uploaded file + $from = $_FILES['owncloud_import']['tmp_name']; + $to = get_temp_dir().'/'.$importname.'.zip'; + if( !move_uploaded_file( $from, $to ) ){ + OC_Log::write('admin_export',"Failed to copy the uploaded file",OC_Log::INFO); + exit(); + } + + // Extract zip + $zip = new ZipArchive(); + if ($zip->open(get_temp_dir().'/'.$importname.'.zip') != TRUE) { + OC_Log::write('admin_export',"Failed to open zip file",OC_Log::INFO); + exit(); + } + $zip->extractTo(get_temp_dir().'/'.$importname.'/'); + $zip->close(); + + // Delete uploaded file + unlink( get_temp_dir() . '/' . $importname . '.zip' ); + + // Delete current data folder. + OC_Log::write('admin_export',"Deleting current data dir",OC_Log::INFO); + unlinkRecursive( $datadir, false ); + + // Copy over data + if( !copy_r( get_temp_dir() . '/' . $importname . '/data', $datadir ) ){ + OC_Log::write('admin_export',"Failed to copy over data directory",OC_Log::INFO); + exit(); + } + + // TODO: Import db } else { // fill template $tmpl = new OC_Template('admin_export', 'settings'); @@ -99,3 +145,68 @@ function zipAddDir($dir, $zip, $recursive=true, $internalDir='') { OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); } } + +function unlinkRecursive($dir, $deleteRootToo) +{ + if(!$dh = @opendir($dir)) + { + return; + } + while (false !== ($obj = readdir($dh))) + { + if($obj == '.' || $obj == '..') + { + continue; + } + + if (!@unlink($dir . '/' . $obj)) + { + unlinkRecursive($dir.'/'.$obj, true); + } + } + + closedir($dh); + + if ($deleteRootToo) + { + @rmdir($dir); + } + + return; +} + + + function copy_r( $path, $dest ) + { + if( is_dir($path) ) + { + @mkdir( $dest ); + $objects = scandir($path); + if( sizeof($objects) > 0 ) + { + foreach( $objects as $file ) + { + if( $file == "." || $file == ".." ) + continue; + // go on + if( is_dir( $path.DS.$file ) ) + { + copy_r( $path.DS.$file, $dest.DS.$file ); + } + else + { + copy( $path.DS.$file, $dest.DS.$file ); + } + } + } + return true; + } + elseif( is_file($path) ) + { + return copy($path, $dest); + } + else + { + return false; + } + } diff --git a/apps/admin_export/templates/settings.php b/apps/admin_export/templates/settings.php index 47689facbb..9f0845bf55 100644 --- a/apps/admin_export/templates/settings.php +++ b/apps/admin_export/templates/settings.php @@ -8,6 +8,16 @@

- + + + +
+
+ t('Import an ownCloud instance THIS WILL DELETE ALL CURRENT OWNCLOUD DATA');?> +

t('All current ownCloud data will be replaced by the ownCloud instance that is uploaded.');?> +

+

+

+
From 20b96787899906c596d4132fe98955f1639a9ff2 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 10 Feb 2012 20:23:28 +0000 Subject: [PATCH 007/302] replace database name and table prefix with default values --- apps/admin_export/settings.php | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 62b4b68ca0..851388c0b5 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -40,19 +40,32 @@ if (isset($_POST['admin_export'])) { } if (isset($_POST['owncloud_system'])) { - // adding owncloud system files - OC_Log::write('admin_export',"Adding owncloud system files to export",OC_Log::INFO); - zipAddDir($root, $zip, false); - foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dirname) { - zipAddDir($root . $dirname, $zip, true, "/"); - } + // adding owncloud system files + OC_Log::write('admin_export',"Adding owncloud system files to export",OC_Log::INFO); + zipAddDir($root, $zip, false); + foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dirname) { + zipAddDir($root . $dirname, $zip, true, "/"); + } } if (isset($_POST['owncloud_config'])) { // adding owncloud config // todo: add database export $dbfile = $tempdir . "/dbexport.xml"; - OC_DB::getDbStructure( $file, 'MDB2_SCHEMA_DUMP_ALL'); + OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); + + // Now add in *dbname* and *dbtableprefix* + $dbexport = file_get_contents( $dbfile ); + + $dbnamestring = "\n\n " . OC_Config::getValue( "dbname", "owncloud" ); + $dbtableprefixstring = "\n\n " . OC_Config::getValue( "dbtableprefix", "_oc" ); + + $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); + $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbtableprefix*", $dbexport ); + + // Write the new db export file + file_put_contents( $dbfile, $dbexport ); + $zip->addFile($dbfile, "dbexport.xml"); OC_Log::write('admin_export',"Adding owncloud config to export",OC_Log::INFO); @@ -106,6 +119,9 @@ if (isset($_POST['admin_export'])) { // Delete uploaded file unlink( get_temp_dir() . '/' . $importname . '.zip' ); + // Now we need to check if everything is present. Data and dbexport.xml + + // Delete current data folder. OC_Log::write('admin_export',"Deleting current data dir",OC_Log::INFO); unlinkRecursive( $datadir, false ); From 33c5b3a2efa8559a3e960e6d8256576aab604869 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Thu, 1 Mar 2012 19:41:14 +0000 Subject: [PATCH 008/302] Added replaceDB method in lib/db.php --- lib/db.php | 83 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/lib/db.php b/lib/db.php index 9d3c20e014..f43f00a397 100644 --- a/lib/db.php +++ b/lib/db.php @@ -316,8 +316,11 @@ class OC_DB { // read file $content = file_get_contents( $file ); - // Make changes and save them to a temporary file - $file2 = tempnam( get_temp_dir(), 'oc_db_scheme_' ); + // Make changes and save them to an in-memory file + $file2 = 'static://db_scheme'; + if($file2 == ''){ + die('could not create tempfile in get_temp_dir() - aborting'); + } $content = str_replace( '*dbname*', $CONFIG_DBNAME, $content ); $content = str_replace( '*dbprefix*', $CONFIG_DBTABLEPREFIX, $content ); if( $CONFIG_DBTYPE == 'pgsql' ){ //mysql support it too but sqlite doesn't @@ -328,7 +331,7 @@ class OC_DB { // Try to create tables $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); - // Delete our temporary file + //clean up memory unlink( $file2 ); // Die in case something went wrong @@ -368,8 +371,8 @@ class OC_DB { return false; } - // Make changes and save them to a temporary file - $file2 = tempnam( get_temp_dir(), 'oc_db_scheme_' ); + // Make changes and save them to an in-memory file + $file2 = 'static://db_scheme'; $content = str_replace( '*dbname*', $previousSchema['name'], $content ); $content = str_replace( '*dbprefix*', $CONFIG_DBTABLEPREFIX, $content ); if( $CONFIG_DBTYPE == 'pgsql' ){ //mysql support it too but sqlite doesn't @@ -378,7 +381,7 @@ class OC_DB { file_put_contents( $file2, $content ); $op = self::$schema->updateDatabase($file2, $previousSchema, array(), false); - // Delete our temporary file + //clean up memory unlink( $file2 ); if (PEAR::isError($op)) { @@ -389,6 +392,27 @@ class OC_DB { return true; } + /** + * @breif replaces the owncloud tables with a new set + */ + public static function replaceDB( $file ){ + + // Delete the old tables + self::removeDBStructure( '/home/tom/sites/secure.tomneedham.com/public_html/migration/db_structure.xml' ); + + $apps = OC_App::getAllApps(); + foreach($apps as $app){ + $path = '/apps/'.$app.'/appinfo/database.xml'; + if(file_exists($path)){ + self::removeDBStructure( $path ); + } + } + + // Create new tables + self::createDBFromStructure( $file ); + + } + /** * @brief connects to a MDB2 database scheme * @returns true/false @@ -482,6 +506,27 @@ class OC_DB { } } + /** + * @breif replaces the owncloud tables with a new set + */ + public static function replaceDB( $file ){ + + // Delete the old tables + self::removeDBStructure( OC::$DOCUMENTROOT . 'db_structure.xml' ); + + $apps = OC_App::getAllApps(); + foreach($apps as $app){ + $path = '/apps/'.$app.'/appinfo/database.xml'; + if(file_exists($path)){ + self::removeDBStructure( $path ); + } + } + + // Create new tables + self::createDBFromStructure( $file ); + + } + /** * Start a transaction */ @@ -505,6 +550,21 @@ class OC_DB { self::$connection->commit(); self::$inTransaction=false; } + + /** + * check if a result is an error, works with MDB2 and PDOException + * @param mixed $result + * @return bool + */ + public static function isError($result){ + if(!$result){ + return true; + }elseif(self::$backend==self::BACKEND_MDB2 and PEAR::isError($result)){ + return true; + }else{ + return false; + } + } } /** @@ -524,11 +584,15 @@ class PDOStatementWrapper{ public function execute($input=array()){ $this->lastArguments=$input; if(count($input)>0){ - $this->statement->execute($input); + $result=$this->statement->execute($input); }else{ - $this->statement->execute(); + $result=$this->statement->execute(); + } + if($result){ + return $this; + }else{ + return false; } - return $this; } /** @@ -567,3 +631,4 @@ class PDOStatementWrapper{ return $this->statement->fetchColumn($colnum); } } + From 9c79de4aa01697e6b2eece450a83ca2019eb9b1c Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 2 Mar 2012 21:47:20 +0000 Subject: [PATCH 009/302] removed duplicate function --- lib/db.php | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/db.php b/lib/db.php index f43f00a397..2348da908e 100644 --- a/lib/db.php +++ b/lib/db.php @@ -392,27 +392,6 @@ class OC_DB { return true; } - /** - * @breif replaces the owncloud tables with a new set - */ - public static function replaceDB( $file ){ - - // Delete the old tables - self::removeDBStructure( '/home/tom/sites/secure.tomneedham.com/public_html/migration/db_structure.xml' ); - - $apps = OC_App::getAllApps(); - foreach($apps as $app){ - $path = '/apps/'.$app.'/appinfo/database.xml'; - if(file_exists($path)){ - self::removeDBStructure( $path ); - } - } - - // Create new tables - self::createDBFromStructure( $file ); - - } - /** * @brief connects to a MDB2 database scheme * @returns true/false @@ -511,10 +490,11 @@ class OC_DB { */ public static function replaceDB( $file ){ + $apps = OC_App::getAllApps(); + // Delete the old tables self::removeDBStructure( OC::$DOCUMENTROOT . 'db_structure.xml' ); - $apps = OC_App::getAllApps(); foreach($apps as $app){ $path = '/apps/'.$app.'/appinfo/database.xml'; if(file_exists($path)){ From 8188a9f51bdf0845dcbc24ffe654118b695febc6 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 2 Mar 2012 21:54:44 +0000 Subject: [PATCH 010/302] change value in db export --- apps/admin_export/settings.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 851388c0b5..5eecd4f3a2 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -59,9 +59,11 @@ if (isset($_POST['admin_export'])) { $dbnamestring = "\n\n " . OC_Config::getValue( "dbname", "owncloud" ); $dbtableprefixstring = "
\n\n " . OC_Config::getValue( "dbtableprefix", "_oc" ); + $createstring = "\n\n *dbname*\n true"; $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbtableprefix*", $dbexport ); + $dbexport = str_replace( $createstring, "\n\n *dbname*\n false Date: Fri, 2 Mar 2012 22:19:06 +0000 Subject: [PATCH 011/302] Update database.xml locations. Fix dbexport.xml. --- apps/admin_export/settings.php | 7 ++++--- lib/db.php | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 5eecd4f3a2..5ba6f716a2 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -62,8 +62,8 @@ if (isset($_POST['admin_export'])) { $createstring = "\n\n *dbname*\n true"; $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); - $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbtableprefix*", $dbexport ); - $dbexport = str_replace( $createstring, "\n\n *dbname*\n false\n\n *dbprefix*", $dbexport ); + $dbexport = str_replace( $createstring, "\n\n *dbname*\n false", $dbexport ); // Write the new db export file file_put_contents( $dbfile, $dbexport ); @@ -134,7 +134,8 @@ if (isset($_POST['admin_export'])) { exit(); } - // TODO: Import db + // TODO: Import db + OC_DB::replaceDB( get_temp_dir() . '/' . $importname . '/dbexport.xml' ); } else { // fill template $tmpl = new OC_Template('admin_export', 'settings'); diff --git a/lib/db.php b/lib/db.php index 2348da908e..07e5859096 100644 --- a/lib/db.php +++ b/lib/db.php @@ -493,10 +493,10 @@ class OC_DB { $apps = OC_App::getAllApps(); // Delete the old tables - self::removeDBStructure( OC::$DOCUMENTROOT . 'db_structure.xml' ); + self::removeDBStructure( OC::$SERVERROOT . '/db_structure.xml' ); foreach($apps as $app){ - $path = '/apps/'.$app.'/appinfo/database.xml'; + $path = OC::$SERVERROOT.'/apps/'.$app.'/appinfo/database.xml'; if(file_exists($path)){ self::removeDBStructure( $path ); } From 6906988b4ec795d5d33367cd192b47d061f1c906 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 3 Mar 2012 13:22:45 +0000 Subject: [PATCH 012/302] Update bookmarks migration provider. --- apps/bookmarks/lib/migrate.php | 68 +++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index af76a4ef4d..86a5b95725 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -1,14 +1,72 @@ formatOutput = true; + $bookmarks = $doc->createElement('bookmarks'); + $bookmarks = $doc->appendChild($bookmarks); + $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks WHERE *PREFIX*bookmarks.user_id = ?"); - $bookmarks =& $query->execute(array($uid)); - while ($row = $bookmarks->fetchRow()) { - $xml .= $row[0] . "\n"; + $bookmarksdata =& $query->execute(array($uid)); + + + // Foreach bookmark + while ($row = $bookmarksdata->fetchRow()) { + $bookmark = $doc->createElement('bookmark'); + $bookmark = $bookmarks->appendChild($bookmark); + + $attr = $doc->createElement('title'); + $attr = $bookmark->appendChild($attr); + $value = $doc->createTextNode($row['title']); + $attr->appendChild($value); + + $attr = $doc->createElement('url'); + $attr = $bookmark->appendChild($attr); + $value = $doc->createTextNode($row['url']); + $attr->appendChild($value); + + $attr = $doc->createElement('added'); + $attr = $bookmark->appendChild($attr); + $value = $doc->createTextNode($row['added']); + $attr->appendChild($value); + + $attr = $doc->createElement('lastmodified'); + $attr = $bookmark->appendChild($attr); + $value = $doc->createTextNode($row['lastmodified']); + $attr->appendChild($value); + + $attr = $doc->createElement('public'); + $attr = $bookmark->appendChild($attr); + $value = $doc->createTextNode($row['public']); + $attr->appendChild($value); + + $attr = $doc->createElement('clickcount'); + $attr = $bookmark->appendChild($attr); + $value = $doc->createTextNode($row['clickcount']); + $attr->appendChild($value); + + $attr = $doc->createElement('tags'); + $tags = $bookmark->appendChild($attr); + + $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks_tags WHERE *PREFIX*bookmarks_tags.bookmark_id = ?"); + $tagsdata =& $query->execute(array($row['id'])); + + // Foreach tag + while ($row = $tagsdata->fetchRow()) { + $attr = $doc->createElement('tag'); + $attr = $tags->appendChild($attr); + $value = $doc->createTextNode($row['tag']); + $attr->appendChild($value); + } } - return $xml; + + return $bookmarks; + } + } + new OC_Migrate_Provider_Bookmarks('bookmarks'); \ No newline at end of file From 188a304625b8ddb597505f69bc34487806b5b18e Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 3 Mar 2012 13:26:01 +0000 Subject: [PATCH 013/302] Replace db on import. Update user_migration export function. --- apps/admin_export/settings.php | 3 --- apps/user_migrate/settings.php | 21 ++++++++++----------- lib/migrate.php | 34 +++++++++++++++++++++++----------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 5ba6f716a2..5584181fbb 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -59,11 +59,9 @@ if (isset($_POST['admin_export'])) { $dbnamestring = "\n\n " . OC_Config::getValue( "dbname", "owncloud" ); $dbtableprefixstring = "
\n\n " . OC_Config::getValue( "dbtableprefix", "_oc" ); - $createstring = "\n\n *dbname*\n true"; $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbprefix*", $dbexport ); - $dbexport = str_replace( $createstring, "\n\n *dbname*\n false", $dbexport ); // Write the new db export file file_put_contents( $dbfile, $dbexport ); @@ -134,7 +132,6 @@ if (isset($_POST['admin_export'])) { exit(); } - // TODO: Import db OC_DB::replaceDB( get_temp_dir() . '/' . $importname . '/dbexport.xml' ); } else { // fill template diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 7e3510d97e..7c00dace3c 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -49,23 +49,22 @@ if (isset($_POST['user_migrate'])) { // adding owncloud system files OC_Log::write('user_migrate',"Adding app data to user export file",OC_Log::INFO); // Call to OC_Migrate for the xml file. - //$appdatafile = $tempdir . "/appdata.xml"; - //$fh = fopen($appdatafile, 'w'); + $appdatafile = $tempdir . "/appdata.xml"; + $appdata = OC_Migrate::export(OC_User::getUser()); - //fwrite($fh, $appdata); - //$zip->addFile($appdatafile, "appdata.xml"); - //fclose($fh); + file_put_contents($appdatafile, $appdata); + $zip->addFile($appdatafile, "appdata.xml"); + } $zip->close(); - //header("Content-Type: application/zip"); - //header("Content-Disposition: attachment; filename=" . basename($filename)); - //header("Content-Length: " . filesize($filename)); - //@ob_end_clean(); - echo htmlspecialchars($appdata); - //readfile($filename); + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($filename)); + header("Content-Length: " . filesize($filename)); + readfile($filename); unlink($filename); + } else { // fill template $tmpl = new OC_Template('user_migrate', 'settings'); diff --git a/lib/migrate.php b/lib/migrate.php index a1b4c5019b..c5def5b583 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -41,16 +41,26 @@ class OC_Migrate{ * @return string xml of app data */ public static function export($uid){ + + $doc = new DOMDocument(); + $doc->formatOutput = true; + OC_Log::write('user_migrate','App data export started for user: '.$uid,OC_Log::INFO); - $xml = ''; + foreach(self::$providers as $provider){ + OC_Log::write('user_migrate','Getting app data for app:'.$provider->appid,OC_Log::INFO); - $xml .= ''; - $xml .= self::appInfoXML($provider->appid); - $xml .= $provider->export($uid); - $xml .= ''; + $app = $doc->createElement('app'); + $doc->appendChild($app); + // Append app info + $app = $doc->appendChild( self::appInfoXML( $provider->appid ) ); + + // Add the app data + $app->appendChild($provider->export($uid)); + } - return $xml; + + return $doc->saveXML(); } /** @@ -59,10 +69,12 @@ class OC_Migrate{ * @return string xml app info */ public static function appInfoXML($appid){ - $info = OC_App::getAppInfo($appid); - $xml = ''; - $xml .= 'INFO HERE'; - $xml .= ''; - return $xml; + $doc = new DOMDocument(); + $appinfo = $doc->createElement('appinfo'); + $appinfo = $doc->appendChild($appinfo); + $data = $doc->createTextNode($appid); + $appinfo->appendChild($data); + + return $appinfo; } } From 34f05ba180792afb40d953b6cf8595a4513eb972 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 3 Mar 2012 14:35:17 +0000 Subject: [PATCH 014/302] Udpdate bookmarks migration provider. App version included in export. --- apps/bookmarks/lib/migrate.php | 2 +- lib/migrate.php | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 86a5b95725..c655154d41 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -63,7 +63,7 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ } } - return $bookmarks; + return $doc; } diff --git a/lib/migrate.php b/lib/migrate.php index c5def5b583..5179e43199 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -51,12 +51,17 @@ class OC_Migrate{ OC_Log::write('user_migrate','Getting app data for app:'.$provider->appid,OC_Log::INFO); $app = $doc->createElement('app'); - $doc->appendChild($app); + $app = $doc->appendChild($app); + $app->setAttribute('id',$provider->appid); // Append app info - $app = $doc->appendChild( self::appInfoXML( $provider->appid ) ); + $appinfo = $doc->importNode( self::appInfoXML( $provider->appid )->documentElement, true ); + $app->appendChild( $appinfo ); + $appdata = $doc->createElement('appdata'); + $appdata = $app->appendChild($appdata); // Add the app data - $app->appendChild($provider->export($uid)); + $appdatanode = $doc->importNode( $provider->export($uid)->documentElement, true ); + $appdata->appendChild( $appdatanode ); } @@ -69,12 +74,17 @@ class OC_Migrate{ * @return string xml app info */ public static function appInfoXML($appid){ + + $info = OC_App::getAppInfo($appid); + $doc = new DOMDocument(); $appinfo = $doc->createElement('appinfo'); $appinfo = $doc->appendChild($appinfo); - $data = $doc->createTextNode($appid); - $appinfo->appendChild($data); + $version = $doc->createElement('version'); + $appinfo->appendChild($version); + $versionval = $doc->createTextNode($info['version']); + $version->appendChild($versionval); - return $appinfo; + return $doc; } } From 691103acd5aad2673b6375726ba24fb56e88451b Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 3 Mar 2012 17:30:21 +0000 Subject: [PATCH 015/302] Use json for migration data --- apps/bookmarks/lib/migrate.php | 98 ++++++++++---------- apps/user_migrate/settings.php | 4 +- lib/migrate.php | 157 +++++++++++++++++++++++++-------- lib/migrate/provider.php | 9 +- 4 files changed, 178 insertions(+), 90 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index c655154d41..f50a8c4633 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -4,69 +4,71 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ // Create the xml for the user supplied function export($uid){ - $doc = new DOMDocument(); - $doc->formatOutput = true; - $bookmarks = $doc->createElement('bookmarks'); - $bookmarks = $doc->appendChild($bookmarks); - + $bookmarks = array(); + $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks WHERE *PREFIX*bookmarks.user_id = ?"); $bookmarksdata =& $query->execute(array($uid)); - - // Foreach bookmark while ($row = $bookmarksdata->fetchRow()) { - $bookmark = $doc->createElement('bookmark'); - $bookmark = $bookmarks->appendChild($bookmark); - - $attr = $doc->createElement('title'); - $attr = $bookmark->appendChild($attr); - $value = $doc->createTextNode($row['title']); - $attr->appendChild($value); - - $attr = $doc->createElement('url'); - $attr = $bookmark->appendChild($attr); - $value = $doc->createTextNode($row['url']); - $attr->appendChild($value); - - $attr = $doc->createElement('added'); - $attr = $bookmark->appendChild($attr); - $value = $doc->createTextNode($row['added']); - $attr->appendChild($value); - - $attr = $doc->createElement('lastmodified'); - $attr = $bookmark->appendChild($attr); - $value = $doc->createTextNode($row['lastmodified']); - $attr->appendChild($value); - - $attr = $doc->createElement('public'); - $attr = $bookmark->appendChild($attr); - $value = $doc->createTextNode($row['public']); - $attr->appendChild($value); - - $attr = $doc->createElement('clickcount'); - $attr = $bookmark->appendChild($attr); - $value = $doc->createTextNode($row['clickcount']); - $attr->appendChild($value); - - $attr = $doc->createElement('tags'); - $tags = $bookmark->appendChild($attr); + // Get the tags $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks_tags WHERE *PREFIX*bookmarks_tags.bookmark_id = ?"); $tagsdata =& $query->execute(array($row['id'])); + $tags = array(); // Foreach tag while ($row = $tagsdata->fetchRow()) { - $attr = $doc->createElement('tag'); - $attr = $tags->appendChild($attr); - $value = $doc->createTextNode($row['tag']); - $attr->appendChild($value); - } + $tags[] = $row['tag']; + } + + $bookmarks[] = array( + 'url' => $row['url'], + 'title' => $row['title'], + 'public' => $row['public'], + 'added' => $row['added'], + 'lastmodified' => $row['lastmodified'], + 'clickcount' => $row['clickcount'], + 'tags' => $tags + ); + } - return $doc; + return array('bookmarks' => $bookmarks); } + // Import function for bookmarks + function import($data,$uid){ + + // Different import code for different versions of the app + switch($data['info']['version']){ + default: + // Foreach bookmark + foreach($data['data']['bookmarks'] as $bookmark){ + + $query = OC_DB::prepare( "INSERT INTO `*PREFIX*bookmarks` ( `url`, `title`, `user_id`, `public`, `added`, `lastmodified`, `clickcount` ) VALUES( ?, ?, ?, ?, ?, ?, ? )" ); + $result = $query->execute( array( + $bookmark['url'], + $bookmark['title'], + $uid, + $bookmark['public'], + $bookmark['added'], + $bookmark['lastmodified'], + $bookmark['clickcount'] + ) ); + // Now add the tags + $id = OC_DB::insertid(); + foreach($bookmark['tags'] as $tag){ + $query = OC_DB::prepare( "INSERT INTO `*PREFIX*bookmarks_tags` ( `id`, `tag` ) VALUES( ?, ? )" ); + $result = $query->execute( array( $id, $tag)); + } + + } + break; + } + // Finished import + } + } new OC_Migrate_Provider_Bookmarks('bookmarks'); \ No newline at end of file diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 7c00dace3c..ed08abe79d 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -49,11 +49,11 @@ if (isset($_POST['user_migrate'])) { // adding owncloud system files OC_Log::write('user_migrate',"Adding app data to user export file",OC_Log::INFO); // Call to OC_Migrate for the xml file. - $appdatafile = $tempdir . "/appdata.xml"; + $appdatafile = $tempdir . "/userexport.json"; $appdata = OC_Migrate::export(OC_User::getUser()); file_put_contents($appdatafile, $appdata); - $zip->addFile($appdatafile, "appdata.xml"); + $zip->addFile($appdatafile, "userexport.json"); } diff --git a/lib/migrate.php b/lib/migrate.php index 5179e43199..f3dd3080d3 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -42,49 +42,134 @@ class OC_Migrate{ */ public static function export($uid){ - $doc = new DOMDocument(); - $doc->formatOutput = true; - - OC_Log::write('user_migrate','App data export started for user: '.$uid,OC_Log::INFO); - - foreach(self::$providers as $provider){ + // Only export database users, otherwise we get chaos + if(OC_User_Database::userExists($uid)){ + + $data = array(); + $data['userid'] = OC_User::getUser(); - OC_Log::write('user_migrate','Getting app data for app:'.$provider->appid,OC_Log::INFO); - $app = $doc->createElement('app'); - $app = $doc->appendChild($app); - $app->setAttribute('id',$provider->appid); - // Append app info - $appinfo = $doc->importNode( self::appInfoXML( $provider->appid )->documentElement, true ); - $app->appendChild( $appinfo ); + $query = OC_DB::prepare( "SELECT uid, password FROM *PREFIX*users WHERE uid LIKE ?" ); + $result = $query->execute( array( $uid)); + + $row = $result->fetchRow(); + if($row){ + $data['hash'] = $row['password']; + } else { + return false; + exit(); + } - $appdata = $doc->createElement('appdata'); - $appdata = $app->appendChild($appdata); - // Add the app data - $appdatanode = $doc->importNode( $provider->export($uid)->documentElement, true ); - $appdata->appendChild( $appdatanode ); - + foreach(self::$providers as $provider){ + + $data['apps'][$prodider->appid]['info'] = OC_App::getAppInfo($provider->appid); + $data['apps'][$provider->appid]['data'] = $provider->export($uid); + + } + + return self::indent(json_encode($data)); + + } else { + return false; } - - return $doc->saveXML(); + } /** - * generates the app info xml - * @param string appid - * @return string xml app info - */ - public static function appInfoXML($appid){ + * @breif imports a new user + * @param $data json data for the user + * @param $uid optional uid to use + * @return json reply + */ + public function import($data,$uid=null){ + + // Import the data + $data = json_decode($data); + if(is_null($data)){ + // TODO LOG + return false; + exit(); + } + + // Specified user or use original + $uid = !is_null($uid) ? $uid : $data['userid']; + + // Check if userid exists + if(OC_User::userExists($uid)){ + // TODO LOG + return false; + exit(); + } + + // Create the user + $query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" ); + $result = $query->execute( array( $uid, $data['hash'])); + if(!$result){ + // TODO LOG + return false; + exit(); + } + + foreach($data['app'] as $app){ + // Check if supports migration and is enabled + if(in_array($app, self::$providers)){ + if(OC_App::isEnabled($app)){ + $provider->import($data['app'][$app],$uid); + } + } + + } + + } + + private static function indent($json){ + + $result = ''; + $pos = 0; + $strLen = strlen($json); + $indentStr = ' '; + $newLine = "\n"; + $prevChar = ''; + $outOfQuotes = true; - $info = OC_App::getAppInfo($appid); + for ($i=0; $i<=$strLen; $i++) { - $doc = new DOMDocument(); - $appinfo = $doc->createElement('appinfo'); - $appinfo = $doc->appendChild($appinfo); - $version = $doc->createElement('version'); - $appinfo->appendChild($version); - $versionval = $doc->createTextNode($info['version']); - $version->appendChild($versionval); + // Grab the next character in the string. + $char = substr($json, $i, 1); - return $doc; - } + // Are we inside a quoted string? + if ($char == '"' && $prevChar != '\\') { + $outOfQuotes = !$outOfQuotes; + + // If this character is the end of an element, + // output a new line and indent the next line. + } else if(($char == '}' || $char == ']') && $outOfQuotes) { + $result .= $newLine; + $pos --; + for ($j=0; $j<$pos; $j++) { + $result .= $indentStr; + } + } + + // Add the character to the result string. + $result .= $char; + + // If the last character was the beginning of an element, + // output a new line and indent the next line. + if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) { + $result .= $newLine; + if ($char == '{' || $char == '[') { + $pos ++; + } + + for ($j = 0; $j < $pos; $j++) { + $result .= $indentStr; + } + } + + $prevChar = $char; + } + + return $result; + } + } diff --git a/lib/migrate/provider.php b/lib/migrate/provider.php index 9dd1b2f389..63b804b620 100644 --- a/lib/migrate/provider.php +++ b/lib/migrate/provider.php @@ -13,14 +13,15 @@ abstract class OC_Migrate_Provider{ /** * exports data for apps * @param string $uid - * @return string xml data for that app + * @return array appdata to be exported */ abstract function export($uid); /** * imports data for the app - * @param string $query - * @return array An array of OC_Search_Result's + * @param $data array of data. eg: array('info'=> APPINFO, 'data'=>APPDATA ARRAY) + * @param $info array of info of the source install + * @return void */ - //abstract function import($data); + abstract function import($data,$uid); } From c3dfcc5b21620476b6e5bf356b42aee9f0da5874 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 9 Mar 2012 23:33:11 +0000 Subject: [PATCH 016/302] First basic implementation of migration.db. --- lib/migrate.php | 326 +++++++++++++++++++++++++-------------- lib/migrate/provider.php | 15 +- 2 files changed, 215 insertions(+), 126 deletions(-) diff --git a/lib/migrate.php b/lib/migrate.php index f3dd3080d3..da7a5ea34f 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -25,7 +25,10 @@ * provides an interface to all search providers */ class OC_Migrate{ + + static private $MDB2=false; static private $providers=array(); + static private $schema=false; /** * register a new migration provider @@ -36,140 +39,225 @@ class OC_Migrate{ } /** - * export app data for a user - * @param string userid - * @return string xml of app data + * @breif creates a migration.db in the users data dir with their app data in + * @param @uid string userid of the user to export for + * @return bool whether operation was successfull */ - public static function export($uid){ + public static function export( $uid ){ // Only export database users, otherwise we get chaos - if(OC_User_Database::userExists($uid)){ - - $data = array(); - $data['userid'] = OC_User::getUser(); - - $query = OC_DB::prepare( "SELECT uid, password FROM *PREFIX*users WHERE uid LIKE ?" ); - $result = $query->execute( array( $uid)); - - $row = $result->fetchRow(); - if($row){ - $data['hash'] = $row['password']; - } else { - return false; - exit(); - } - - foreach(self::$providers as $provider){ - - $data['apps'][$prodider->appid]['info'] = OC_App::getAppInfo($provider->appid); - $data['apps'][$provider->appid]['data'] = $provider->export($uid); - - } - - return self::indent(json_encode($data)); - - } else { + if(!OC_User_Database::userExists( $uid )){ return false; } + + // Foreach provider + foreach( $providers as $provider ){ + + self::createAppTables( $provider->id ); + // Run the export function + $provider->export( $uid ); + + } + + return true; } /** - * @breif imports a new user - * @param $data json data for the user - * @param $uid optional uid to use - * @return json reply - */ - public function import($data,$uid=null){ - - // Import the data - $data = json_decode($data); - if(is_null($data)){ - // TODO LOG - return false; - exit(); - } - - // Specified user or use original - $uid = !is_null($uid) ? $uid : $data['userid']; - - // Check if userid exists - if(OC_User::userExists($uid)){ - // TODO LOG - return false; - exit(); - } - - // Create the user - $query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" ); - $result = $query->execute( array( $uid, $data['hash'])); - if(!$result){ - // TODO LOG + * @breif imports a new user + * @param $uid optional uid to use + * @return bool if the import succedded + */ + public static function import( $uid=null ){ + + self::$uid = $uid; + + // Connect to the db + if(!self::connectDB()){ + return false; + } + + // Create the user + if(!self::createUser($uid, $hash)){ + return false; + } + + // Now get the list of apps to import from migration.db + // Then check for migrate.php for these apps + // If present, run the import function for them. + + return treu; + + } + + // @breif connects to migration.db, or creates if not found + // @return bool whether the operation was successful + private static function connectDB(){ + + // Already connected + if(!self::$MDB2){ + require_once('MDB2.php'); + + $datadir = OC_Config::getValue( "datadirectory", "$SERVERROOT/data" ); + + // Prepare options array + $options = array( + 'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE), + 'log_line_break' => '
', + 'idxname_format' => '%s', + 'debug' => true, + 'quote_identifier' => true + ); + $dsn = array( + 'phptype' => 'sqlite', + 'database' => $datadir.'/'.self::$uid.'/migration.db', + 'mode' => '0644' + ); + + // Try to establish connection + self::$MDB2 = MDB2::factory( $dsn, $options ); + + // Die if we could not connect + if( PEAR::isError( self::$MDB2 )){ + OC_Log::write('migration', 'Failed to create migration.db',OC_Log::FATAL); + OC_Log::write('migration',self::$MDB2->getUserInfo(),OC_Log::FATAL); + OC_Log::write('migration',self::$MDB2->getMessage(),OC_Log::FATAL); + return false; + } + + // We always, really always want associative arrays + self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC); + } + return true; + + } + + // @breif prepares the db + // @param $query the sql query to prepare + public static function prepareDB( $query ){ + + // Optimize the query + $query = self::processQuery( $query ); + + // Optimize the query + $query = self::$MDB2->prepare( $query ); + + // Die if we have an error (error means: bad query, not 0 results!) + if( PEAR::isError( $query )) { + $entry = 'DB Error: "'.$result->getMessage().'"
'; + $entry .= 'Offending command was: '.$query.'
'; + OC_Log::write('migration',$entry,OC_Log::FATAL); return false; - exit(); + } else { + return true; } - - foreach($data['app'] as $app){ - // Check if supports migration and is enabled - if(in_array($app, self::$providers)){ - if(OC_App::isEnabled($app)){ - $provider->import($data['app'][$app],$uid); - } - } - - } - - } - - private static function indent($json){ + + } + + // @breif processes the db query + // @param $query the query to process + // @return string of processed query + private static function processQuery( $query ){ + + self::connectDB(); + $type = 'sqlite'; + $prefix = ''; + + $query = str_replace( '`', '\'', $query ); + $query = str_replace( 'NOW()', 'datetime(\'now\')', $query ); + $query = str_replace( 'now()', 'datetime(\'now\')', $query ); - $result = ''; - $pos = 0; - $strLen = strlen($json); - $indentStr = ' '; - $newLine = "\n"; - $prevChar = ''; - $outOfQuotes = true; + // replace table name prefix + $query = str_replace( '*PREFIX*', $prefix, $query ); + + return $query; - for ($i=0; $i<=$strLen; $i++) { + } + + // @breif creates the tables in migration.db from an apps database.xml + // @param $appid string id of the app + // @return bool whether the operation was successful + private static function createAppTables( $appid ){ + $file = OC::$SERVERROOT.'/apps/'.$appid.'appinfo/database.xml'; + if(file_exists( $file )){ + // There is a database.xml file + $content = file_get_contents( $file ); + + $file2 = 'static://db_scheme'; + $content = str_replace( '*dbname*', 'migration', $content ); + $content = str_replace( '*dbprefix*', '', $content ); + + file_put_contents( $file2, $content ); + + // Try to create tables + $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); + + unlink( $file2 ); + + // Die in case something went wrong + if( $definition instanceof MDB2_Schema_Error ){ + OC_Log::write('migration','Failed to parse database.xml for: '.$appid,OC_Log::FATAL); + OC_Log::write('migration',$definition->getMessage().': '.$definition->getUserInfo(),OC_Log::FATAL); + return false; + } + + $definition['overwrite'] = true; + + $ret = self::$schema->createDatabase( $definition ); + // Die in case something went wrong + + if( $ret instanceof MDB2_Error ){ + OC_Log::write('migration','Failed to create tables for: '.$appid,OC_Log::FATAL); + OC_Log::write('migration',$ret->getMessage().': '.$ret->getUserInfo(),OC_Log::FATAL); + return false; + } + return true; + + } else { + // No database.xml + return false; + } + } + + + /** + * @brief connects to a MDB2 database scheme + * @returns true/false + * + * Connects to a MDB2 database scheme + */ + private static function connectScheme(){ + // We need a mdb2 database connection + self::connectDB(); + self::$MDB2->loadModule( 'Manager' ); + self::$MDB2->loadModule( 'Reverse' ); + + // Connect if this did not happen before + if( !self::$schema ){ + require_once('MDB2/Schema.php'); + self::$schema=MDB2_Schema::factory( self::$MDB2 ); + } + + return true; + } + + // @breif creates a new user in the database + // @param $uid string user_id of the user to be created + // @param $hash string hash of the user to be created + // @return bool result of user creation + private static function createUser( $uid, $hash ){ - // Grab the next character in the string. - $char = substr($json, $i, 1); - - // Are we inside a quoted string? - if ($char == '"' && $prevChar != '\\') { - $outOfQuotes = !$outOfQuotes; - - // If this character is the end of an element, - // output a new line and indent the next line. - } else if(($char == '}' || $char == ']') && $outOfQuotes) { - $result .= $newLine; - $pos --; - for ($j=0; $j<$pos; $j++) { - $result .= $indentStr; - } - } - - // Add the character to the result string. - $result .= $char; - - // If the last character was the beginning of an element, - // output a new line and indent the next line. - if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) { - $result .= $newLine; - if ($char == '{' || $char == '[') { - $pos ++; - } - - for ($j = 0; $j < $pos; $j++) { - $result .= $indentStr; - } - } - - $prevChar = $char; + // Check if userid exists + if(OC_User::userExists( $uid )){ + return false; } - return $result; - } + // Create the user + $query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" ); + $result = $query->execute( array( $uid, $data['hash'])); + + return $result ? true : false; + + } } diff --git a/lib/migrate/provider.php b/lib/migrate/provider.php index 63b804b620..9c03639b7c 100644 --- a/lib/migrate/provider.php +++ b/lib/migrate/provider.php @@ -3,22 +3,23 @@ * provides search functionalty */ abstract class OC_Migrate_Provider{ - public $appid; - public function __construct($appid){ - $this->appid = $appid; - OC_Migrate::registerProvider($this); + public $id; + + public function __construct( $appid ){ + $this->id = $appid; + OC_Migrate::registerProvider( $this ); } - //public static $appid; + /** - * exports data for apps + * @breif exports data for apps * @param string $uid * @return array appdata to be exported */ abstract function export($uid); /** - * imports data for the app + * @breif imports data for the app * @param $data array of data. eg: array('info'=> APPINFO, 'data'=>APPDATA ARRAY) * @param $info array of info of the source install * @return void From 3ca76d24a9b27101661c454be84c7126315315d6 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 10 Mar 2012 15:52:38 +0000 Subject: [PATCH 017/302] Add OC_Migrate::copyRows() method --- apps/bookmarks/lib/migrate.php | 89 ++++++++++++---------------------- lib/migrate.php | 59 +++++++++++++++++++++- 2 files changed, 90 insertions(+), 58 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index f50a8c4633..7d8ad8bfc5 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -2,73 +2,48 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ // Create the xml for the user supplied - function export($uid){ + function export( $uid ){ - $bookmarks = array(); - - $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks WHERE *PREFIX*bookmarks.user_id = ?"); - $bookmarksdata =& $query->execute(array($uid)); - // Foreach bookmark - while ($row = $bookmarksdata->fetchRow()) { - - // Get the tags - $query = OC_DB::prepare("SELECT * FROM *PREFIX*bookmarks_tags WHERE *PREFIX*bookmarks_tags.bookmark_id = ?"); - $tagsdata =& $query->execute(array($row['id'])); + $options = array( + 'table'=>'bookmarks', + 'matchcol'=>'user_id', + 'matchval'=>$uid, + 'idcol'=>'id' + ); + $ids = OC_Migrate::copyRows( $options ); - $tags = array(); - // Foreach tag - while ($row = $tagsdata->fetchRow()) { - $tags[] = $row['tag']; - } - - $bookmarks[] = array( - 'url' => $row['url'], - 'title' => $row['title'], - 'public' => $row['public'], - 'added' => $row['added'], - 'lastmodified' => $row['lastmodified'], - 'clickcount' => $row['clickcount'], - 'tags' => $tags - ); - - } + $options = array( + 'table'=>'bookmarks_tags', + 'matchcol'=>'id', + 'matchval'=>$ids + ); - return array('bookmarks' => $bookmarks); + // Export tags + OC_Migrate::copyRows( $options ); } // Import function for bookmarks - function import($data,$uid){ + function import( $data, $uid ){ - // Different import code for different versions of the app - switch($data['info']['version']){ - default: - // Foreach bookmark - foreach($data['data']['bookmarks'] as $bookmark){ - - $query = OC_DB::prepare( "INSERT INTO `*PREFIX*bookmarks` ( `url`, `title`, `user_id`, `public`, `added`, `lastmodified`, `clickcount` ) VALUES( ?, ?, ?, ?, ?, ?, ? )" ); - $result = $query->execute( array( - $bookmark['url'], - $bookmark['title'], - $uid, - $bookmark['public'], - $bookmark['added'], - $bookmark['lastmodified'], - $bookmark['clickcount'] - ) ); - // Now add the tags - $id = OC_DB::insertid(); - foreach($bookmark['tags'] as $tag){ - $query = OC_DB::prepare( "INSERT INTO `*PREFIX*bookmarks_tags` ( `id`, `tag` ) VALUES( ?, ? )" ); - $result = $query->execute( array( $id, $tag)); - } - - } - break; + // new id mapping + $newids = array(); + + // Import bookmarks + foreach($data['bookmarks'] as $bookmark){ + $bookmark['user_id'] = $uid; + // import to the db now + $newids[$bookmark['id']] = OC_DB::insertid(); + } + + // Import tags + foreach($data['bookmarks_tags'] as $tag){ + // Map the new ids + $tag['id'] = $newids[$tag['id']]; + // Import to the db now using OC_DB } - // Finished import } } -new OC_Migrate_Provider_Bookmarks('bookmarks'); \ No newline at end of file +new OC_Migrate_Provider_Bookmarks( 'bookmarks' ); \ No newline at end of file diff --git a/lib/migrate.php b/lib/migrate.php index da7a5ea34f..b09626d11b 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -134,7 +134,7 @@ class OC_Migrate{ // @breif prepares the db // @param $query the sql query to prepare - public static function prepareDB( $query ){ + public static function prepare( $query ){ // Optimize the query $query = self::processQuery( $query ); @@ -174,12 +174,69 @@ class OC_Migrate{ } + // @brief copys rows to migration.db from the main database + // @param $options array of options. + // @return bool + public static function copyRows( $options ){ + if( !array_key_exists( 'table', $options ) ){ + return false; + } + + // Need to include 'where' in the query? + if( array_key_exists( 'matchval', $options ) && array_key_exists( 'matchcol', $options ) ){ + foreach( $options['matchval'] as $matchval ){ + // Run the query for this match value (where x = y value) + $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?" ); + $results = $query->execute( array( $matchval ) ); + self::insertData( $results, $options ); + + } + + } else { + // Just get everything + $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] ); + $results = $query->execute(); + self::insertData( $results, $options ); + + } + + return true; + + } + + // @breif saves a sql data set into migration.db + // @param $data a sql data set returned from self::prepare()->query() + // @param $options array of copyRows options + // @return void + private static function insertData( $data, $options ){ + while( $data = $result->fetchRow() ){ + // Now save all this to the migration.db + foreach($row as $field=>$value){ + $fields[] = $field; + $values[] = $value; + } + + // Generate some sql + $sql = "INSERT INTO `*PREFIX*" . $options['table'] . '` ( `'; + $fieldssql = implode( '`, `', $fields ); + $sql .= $fieldssql . "` ) VALUES( "; + $valuessql = substr( str_repeat( '?, ', count( $fields ) ),0,-1 ); + $sql .= $valuessql . " )"; + // Make the query + $query = self::prepare( $sql ); + $query->execute( $values ); + } + } + // @breif creates the tables in migration.db from an apps database.xml // @param $appid string id of the app // @return bool whether the operation was successful private static function createAppTables( $appid ){ $file = OC::$SERVERROOT.'/apps/'.$appid.'appinfo/database.xml'; if(file_exists( $file )){ + + self::connectScheme(); + // There is a database.xml file $content = file_get_contents( $file ); From d712d7f52c667e70edefcbd64c036afada678863 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 10 Mar 2012 18:18:58 +0000 Subject: [PATCH 018/302] Lots of fixes, improve copyRows() method and update settings page. --- apps/bookmarks/lib/migrate.php | 4 +-- apps/user_migrate/settings.php | 19 +++++----- lib/migrate.php | 64 +++++++++++++++++++++++----------- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 7d8ad8bfc5..9493f2cae8 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -11,10 +11,10 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ 'idcol'=>'id' ); $ids = OC_Migrate::copyRows( $options ); - + $ids = array('1'); $options = array( 'table'=>'bookmarks_tags', - 'matchcol'=>'id', + 'matchcol'=>'bookmark_id', 'matchval'=>$ids ); diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index ed08abe79d..c35d46b351 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -25,7 +25,6 @@ OC_Util::checkAppEnabled('user_migrate'); if (isset($_POST['user_migrate'])) { // Looks like they want to migrate - $errors = array(); $root = OC::$SERVERROOT . "/"; $user = OC_User::getUser(); $zip = new ZipArchive(); @@ -49,21 +48,21 @@ if (isset($_POST['user_migrate'])) { // adding owncloud system files OC_Log::write('user_migrate',"Adding app data to user export file",OC_Log::INFO); // Call to OC_Migrate for the xml file. - $appdatafile = $tempdir . "/userexport.json"; - $appdata = OC_Migrate::export(OC_User::getUser()); - file_put_contents($appdatafile, $appdata); - $zip->addFile($appdatafile, "userexport.json"); + // Create migration.db + var_dump(OC_Migrate::export(OC_User::getUser())); + // Add export db to zip + $zip->addFile($root.'data/'.$user.'/migration.db', "migration.db"); } $zip->close(); - header("Content-Type: application/zip"); - header("Content-Disposition: attachment; filename=" . basename($filename)); - header("Content-Length: " . filesize($filename)); - readfile($filename); - unlink($filename); + //header("Content-Type: application/zip"); + //header("Content-Disposition: attachment; filename=" . basename($filename)); + //header("Content-Length: " . filesize($filename)); + //readfile($filename); + //unlink($filename); } else { // fill template diff --git a/lib/migrate.php b/lib/migrate.php index b09626d11b..a0d702b4e2 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -29,6 +29,7 @@ class OC_Migrate{ static private $MDB2=false; static private $providers=array(); static private $schema=false; + static private $uid=false; /** * register a new migration provider @@ -49,17 +50,27 @@ class OC_Migrate{ if(!OC_User_Database::userExists( $uid )){ return false; } - + + self::$uid = $uid; + self::connectDB(); + $ok = true; + $return = array(); + // Foreach provider - foreach( $providers as $provider ){ - - self::createAppTables( $provider->id ); - // Run the export function - $provider->export( $uid ); - + foreach( self::$providers as $provider ){ + // Check for database.xml + if(file_exists(OC::$SERVERROOT.'/apps/'.$provider->id.'/appinfo/database.xml')){ + if(!self::createAppTables( $provider->id )){ + $ok = false; + OC_Log::write('migration','failed to create migration tables for: '.$provider->id,OC_Log::INFO); + } + } + if($ok){ + $return[$provider->id]['success'] = $provider->export( $uid ); + } } - return true; + return $return; } @@ -86,7 +97,7 @@ class OC_Migrate{ // Then check for migrate.php for these apps // If present, run the import function for them. - return treu; + return true; } @@ -98,7 +109,7 @@ class OC_Migrate{ if(!self::$MDB2){ require_once('MDB2.php'); - $datadir = OC_Config::getValue( "datadirectory", "$SERVERROOT/data" ); + $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); // Prepare options array $options = array( @@ -109,22 +120,22 @@ class OC_Migrate{ 'quote_identifier' => true ); $dsn = array( - 'phptype' => 'sqlite', + 'phptype' => 'sqlite3', 'database' => $datadir.'/'.self::$uid.'/migration.db', 'mode' => '0644' ); // Try to establish connection self::$MDB2 = MDB2::factory( $dsn, $options ); - // Die if we could not connect if( PEAR::isError( self::$MDB2 )){ + die(self::$MDB2->getMessage()); OC_Log::write('migration', 'Failed to create migration.db',OC_Log::FATAL); OC_Log::write('migration',self::$MDB2->getUserInfo(),OC_Log::FATAL); OC_Log::write('migration',self::$MDB2->getMessage(),OC_Log::FATAL); return false; + } else { } - // We always, really always want associative arrays self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC); } @@ -149,7 +160,7 @@ class OC_Migrate{ OC_Log::write('migration',$entry,OC_Log::FATAL); return false; } else { - return true; + return $query; } } @@ -160,7 +171,6 @@ class OC_Migrate{ private static function processQuery( $query ){ self::connectDB(); - $type = 'sqlite'; $prefix = ''; $query = str_replace( '`', '\'', $query ); @@ -184,6 +194,12 @@ class OC_Migrate{ // Need to include 'where' in the query? if( array_key_exists( 'matchval', $options ) && array_key_exists( 'matchcol', $options ) ){ + + // If only one matchval, create an array + if(!is_array($options['matchval'])){ + $options['matchval'] = array( $options['matchval'] ); + } + foreach( $options['matchval'] as $matchval ){ // Run the query for this match value (where x = y value) $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?" ); @@ -209,7 +225,7 @@ class OC_Migrate{ // @param $options array of copyRows options // @return void private static function insertData( $data, $options ){ - while( $data = $result->fetchRow() ){ + while( $row = $data->fetchRow() ){ // Now save all this to the migration.db foreach($row as $field=>$value){ $fields[] = $field; @@ -217,14 +233,18 @@ class OC_Migrate{ } // Generate some sql - $sql = "INSERT INTO `*PREFIX*" . $options['table'] . '` ( `'; + $sql = "INSERT INTO `" . $options['table'] . '` ( `'; $fieldssql = implode( '`, `', $fields ); $sql .= $fieldssql . "` ) VALUES( "; - $valuessql = substr( str_repeat( '?, ', count( $fields ) ),0,-1 ); + $valuessql = substr( str_repeat( '?, ', count( $fields ) ),0,-2 ); $sql .= $valuessql . " )"; // Make the query $query = self::prepare( $sql ); - $query->execute( $values ); + if(!$query){ + OC_Log::write('migration','Invalid sql produced: '.$sql,OC_Log::FATAL); + } else { + $query->execute( $values ); + } } } @@ -232,10 +252,12 @@ class OC_Migrate{ // @param $appid string id of the app // @return bool whether the operation was successful private static function createAppTables( $appid ){ - $file = OC::$SERVERROOT.'/apps/'.$appid.'appinfo/database.xml'; + $file = OC::$SERVERROOT.'/apps/'.$appid.'/appinfo/database.xml'; if(file_exists( $file )){ - self::connectScheme(); + if(!self::connectScheme()){ + return false; + } // There is a database.xml file $content = file_get_contents( $file ); From fa5a5649c6f7a08b319b15bd537d898e5e77ee98 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 11 Mar 2012 22:09:16 +0000 Subject: [PATCH 019/302] Fix copyRows() and sqlite connection --- apps/bookmarks/lib/migrate.php | 4 +-- lib/migrate.php | 65 ++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 9493f2cae8..6e0b5c4cc4 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -3,7 +3,7 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ // Create the xml for the user supplied function export( $uid ){ - + OC_Log::write('migration','starting export for bookmarks',OC_Log::INFO); $options = array( 'table'=>'bookmarks', 'matchcol'=>'user_id', @@ -11,7 +11,7 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ 'idcol'=>'id' ); $ids = OC_Migrate::copyRows( $options ); - $ids = array('1'); + $options = array( 'table'=>'bookmarks_tags', 'matchcol'=>'bookmark_id', diff --git a/lib/migrate.php b/lib/migrate.php index a0d702b4e2..28329a8170 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -45,13 +45,20 @@ class OC_Migrate{ * @return bool whether operation was successfull */ public static function export( $uid ){ - + // Only export database users, otherwise we get chaos if(!OC_User_Database::userExists( $uid )){ return false; } self::$uid = $uid; + + if(empty(self::$uid)){ + OC_Log::write('migration','Invalid uid passed',OC_Log::FATAL); + return false; + exit(); + } + self::connectDB(); $ok = true; $return = array(); @@ -60,13 +67,16 @@ class OC_Migrate{ foreach( self::$providers as $provider ){ // Check for database.xml if(file_exists(OC::$SERVERROOT.'/apps/'.$provider->id.'/appinfo/database.xml')){ - if(!self::createAppTables( $provider->id )){ - $ok = false; - OC_Log::write('migration','failed to create migration tables for: '.$provider->id,OC_Log::INFO); - } + $ok = self::createAppTables( $provider->id ); } if($ok){ + // Run the export function provided by the providor $return[$provider->id]['success'] = $provider->export( $uid ); + } else { + // Log the error + OC_Log::write('migration','failed to create migration tables for: '.$provider->id,OC_Log::INFO); + $return[$provider->id]['success'] = 'false'; + $return[$provider->id]['message'] = 'failed to create the app tables'; } } @@ -79,10 +89,16 @@ class OC_Migrate{ * @param $uid optional uid to use * @return bool if the import succedded */ - public static function import( $uid=null ){ + public static function import( $uid=false ){ self::$uid = $uid; + if(!self::$uid){ + OC_Log::write('migration','Tried to import without passing a uid',OC_Log::FATAL); + return false; + exit(); + } + // Connect to the db if(!self::connectDB()){ return false; @@ -104,7 +120,12 @@ class OC_Migrate{ // @breif connects to migration.db, or creates if not found // @return bool whether the operation was successful private static function connectDB(){ - + OC_Log::write('migration','connecting to migration.db for user: '.self::$uid,OC_Log::INFO); + // Fail if no user is set + if(!self::$uid){ + OC_Log::write('migration','connectDB() called without self::$uid being set',OC_Log::INFO); + return false; + } // Already connected if(!self::$MDB2){ require_once('MDB2.php'); @@ -130,7 +151,7 @@ class OC_Migrate{ // Die if we could not connect if( PEAR::isError( self::$MDB2 )){ die(self::$MDB2->getMessage()); - OC_Log::write('migration', 'Failed to create migration.db',OC_Log::FATAL); + OC_Log::write('migration', 'Failed to create/connect to migration.db',OC_Log::FATAL); OC_Log::write('migration',self::$MDB2->getUserInfo(),OC_Log::FATAL); OC_Log::write('migration',self::$MDB2->getMessage(),OC_Log::FATAL); return false; @@ -191,7 +212,9 @@ class OC_Migrate{ if( !array_key_exists( 'table', $options ) ){ return false; } - + + $return = array(); + // Need to include 'where' in the query? if( array_key_exists( 'matchval', $options ) && array_key_exists( 'matchcol', $options ) ){ @@ -204,19 +227,19 @@ class OC_Migrate{ // Run the query for this match value (where x = y value) $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?" ); $results = $query->execute( array( $matchval ) ); - self::insertData( $results, $options ); - + $return = self::insertData( $results, $options ); + //$return = array_merge( $return, $newreturns ); } } else { // Just get everything $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] ); $results = $query->execute(); - self::insertData( $results, $options ); + $return = self::insertData( $results, $options ); } - return true; + return $return; } @@ -225,8 +248,11 @@ class OC_Migrate{ // @param $options array of copyRows options // @return void private static function insertData( $data, $options ){ + $return = array(); while( $row = $data->fetchRow() ){ // Now save all this to the migration.db + $fields = array(); + $values = array(); foreach($row as $field=>$value){ $fields[] = $field; $values[] = $value; @@ -242,10 +268,21 @@ class OC_Migrate{ $query = self::prepare( $sql ); if(!$query){ OC_Log::write('migration','Invalid sql produced: '.$sql,OC_Log::FATAL); + return false; + exit(); } else { $query->execute( $values ); + // Do we need to return some values? + if( array_key_exists( 'idcol', $options ) ){ + // Yes we do + $return[] = $row[$options['idcol']]; + } else { + // Take a guess and return the first field :) + $return[] = reset($row); + } } } + return $return; } // @breif creates the tables in migration.db from an apps database.xml @@ -263,7 +300,7 @@ class OC_Migrate{ $content = file_get_contents( $file ); $file2 = 'static://db_scheme'; - $content = str_replace( '*dbname*', 'migration', $content ); + $content = str_replace( '*dbname*', self::$uid.'/migration', $content ); $content = str_replace( '*dbprefix*', '', $content ); file_put_contents( $file2, $content ); From a2d7e9c6e8b951848c53dc2b159e1ff4b92642c5 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 11 Mar 2012 22:13:50 +0000 Subject: [PATCH 020/302] Merge returns from insertData() --- lib/migrate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/migrate.php b/lib/migrate.php index 28329a8170..b8dbcc475e 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -227,8 +227,8 @@ class OC_Migrate{ // Run the query for this match value (where x = y value) $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?" ); $results = $query->execute( array( $matchval ) ); - $return = self::insertData( $results, $options ); - //$return = array_merge( $return, $newreturns ); + $newreturns = self::insertData( $results, $options ); + $return = array_merge( $return, $newreturns ); } } else { From 9c032ecc33d52438280bdf7d53fd904883ba664f Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 11 Mar 2012 22:20:01 +0000 Subject: [PATCH 021/302] Fix return value of export --- apps/bookmarks/lib/migrate.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 6e0b5c4cc4..451699ec15 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -19,7 +19,15 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ ); // Export tags - OC_Migrate::copyRows( $options ); + $ids2 = OC_Migrate::copyRows( $options ); + + // If both returned some ids then they worked + if( is_array( $ids ) && is_array( $ids2 ) ) + { + return true; + } else { + return false; + } } From 1cdb4396a4bc71ee564e73144bfdcd74a1a7493b Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Mon, 12 Mar 2012 18:40:14 +0000 Subject: [PATCH 022/302] Fix copyRows() return value. Generate app info and oc info on return --- apps/user_migrate/settings.php | 2 +- lib/migrate.php | 121 +++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 51 deletions(-) diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index c35d46b351..c1121f2ddf 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -50,7 +50,7 @@ if (isset($_POST['user_migrate'])) { // Call to OC_Migrate for the xml file. // Create migration.db - var_dump(OC_Migrate::export(OC_User::getUser())); + OC_Migrate::export(OC_User::getUser()); // Add export db to zip $zip->addFile($root.'data/'.$user.'/migration.db', "migration.db"); diff --git a/lib/migrate.php b/lib/migrate.php index b8dbcc475e..95da4514fc 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -65,20 +65,42 @@ class OC_Migrate{ // Foreach provider foreach( self::$providers as $provider ){ - // Check for database.xml + + $failed = false; + + // Does this app use the database? if(file_exists(OC::$SERVERROOT.'/apps/'.$provider->id.'/appinfo/database.xml')){ - $ok = self::createAppTables( $provider->id ); + // Create some app tables + $tables = self::createAppTables( $provider->id ); + if( is_array( $tables ) ){ + // Save the table names + foreach($tables as $table){ + $return['app'][$provider->id]['tables'][] = $table; + } + } else { + // It failed to create the tables + $failed = true; + } } - if($ok){ - // Run the export function provided by the providor - $return[$provider->id]['success'] = $provider->export( $uid ); + + // Run the import function? + if( !$failed ){ + $return['app'][$provider->id]['success'] = $provider->export( $uid ); } else { - // Log the error - OC_Log::write('migration','failed to create migration tables for: '.$provider->id,OC_Log::INFO); - $return[$provider->id]['success'] = 'false'; - $return[$provider->id]['message'] = 'failed to create the app tables'; + $return['app'][$provider->id]['success'] = false; + $return['app'][$provider->id]['message'] = 'failed to create the app tables'; } + + // Now add some app info the the return array + $appinfo = OC_App::getAppInfo( $provider->id ); + $return['app'][$provider->id]['version'] = $appinfo['version']; + } + + + // Add some general info to the return array + $return['migrateinfo']['uid'] = $uid; + $return['migrateinfo']['ocversion'] = OC_Util::getVersionString(); return $return; @@ -289,50 +311,49 @@ class OC_Migrate{ // @param $appid string id of the app // @return bool whether the operation was successful private static function createAppTables( $appid ){ - $file = OC::$SERVERROOT.'/apps/'.$appid.'/appinfo/database.xml'; - if(file_exists( $file )){ - if(!self::connectScheme()){ - return false; - } - - // There is a database.xml file - $content = file_get_contents( $file ); - - $file2 = 'static://db_scheme'; - $content = str_replace( '*dbname*', self::$uid.'/migration', $content ); - $content = str_replace( '*dbprefix*', '', $content ); - - file_put_contents( $file2, $content ); - - // Try to create tables - $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); - - unlink( $file2 ); - - // Die in case something went wrong - if( $definition instanceof MDB2_Schema_Error ){ - OC_Log::write('migration','Failed to parse database.xml for: '.$appid,OC_Log::FATAL); - OC_Log::write('migration',$definition->getMessage().': '.$definition->getUserInfo(),OC_Log::FATAL); - return false; - } - - $definition['overwrite'] = true; - - $ret = self::$schema->createDatabase( $definition ); - // Die in case something went wrong - - if( $ret instanceof MDB2_Error ){ - OC_Log::write('migration','Failed to create tables for: '.$appid,OC_Log::FATAL); - OC_Log::write('migration',$ret->getMessage().': '.$ret->getUserInfo(),OC_Log::FATAL); - return false; - } - return true; - - } else { - // No database.xml + if(!self::connectScheme()){ return false; } + + // There is a database.xml file + $content = file_get_contents( OC::$SERVERROOT . '/apps/' . $appid . '/appinfo/database.xml' ); + + $file2 = 'static://db_scheme'; + $content = str_replace( '*dbname*', self::$uid.'/migration', $content ); + $content = str_replace( '*dbprefix*', '', $content ); + + $xml = new SimpleXMLElement($content); + foreach($xml->table as $table){ + $tables[] = (string)$table->name; + } + + file_put_contents( $file2, $content ); + + // Try to create tables + $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); + + unlink( $file2 ); + + // Die in case something went wrong + if( $definition instanceof MDB2_Schema_Error ){ + OC_Log::write('migration','Failed to parse database.xml for: '.$appid,OC_Log::FATAL); + OC_Log::write('migration',$definition->getMessage().': '.$definition->getUserInfo(),OC_Log::FATAL); + return false; + } + + $definition['overwrite'] = true; + + $ret = self::$schema->createDatabase( $definition ); + // Die in case something went wrong + + if( $ret instanceof MDB2_Error ){ + OC_Log::write('migration','Failed to create tables for: '.$appid,OC_Log::FATAL); + OC_Log::write('migration',$ret->getMessage().': '.$ret->getUserInfo(),OC_Log::FATAL); + return false; + } + return $tables; + } From d108bdc7c7940d23355c9a16f3f355387bbb66ef Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Mon, 12 Mar 2012 21:41:32 +0000 Subject: [PATCH 023/302] Improved import function. Added param to connectDB() to load the db from the import --- lib/migrate.php | 66 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/lib/migrate.php b/lib/migrate.php index 95da4514fc..075358c72b 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -30,6 +30,7 @@ class OC_Migrate{ static private $providers=array(); static private $schema=false; static private $uid=false; + static private $database=false; /** * register a new migration provider @@ -108,40 +109,75 @@ class OC_Migrate{ /** * @breif imports a new user + * @param $db string path to migration.db + * @param $migrateinfo string path to the migration info json file * @param $uid optional uid to use * @return bool if the import succedded */ - public static function import( $uid=false ){ - - self::$uid = $uid; - + public static function import( $db, $migrateinfo, $uid=false ){ + if(!self::$uid){ OC_Log::write('migration','Tried to import without passing a uid',OC_Log::FATAL); return false; exit(); } - // Connect to the db - if(!self::connectDB()){ - return false; + // Check if the db exists + if( file_exists( $db ) ){ + // Connect to the db + if(!self::connectDB( $db )){ + return false; + exit(); + } + } else { + OC_Log::write('migration','Migration.db not found at: '.$db, OC_Log::FATAL ); + return false; + exit(); } + // Load the json info + if( file_exists( $migrateinfo ) ){ + + } else { + OC_Log::write( 'migration', 'Migration information file not found at: '.$migrateinfo, OC_Log::FATAL ); + return false; + exit(); + } + + // Process migration info + $info = file_get_contents( $migrateinfo ); + $info = json_decode( $info ); + + // Set the user id + self::$uid = !$uid : $info['migrateinfo']['uid'] ? $uid; + // Create the user if(!self::createUser($uid, $hash)){ return false; + exit(); } + + $apps = $info['apps']; - // Now get the list of apps to import from migration.db - // Then check for migrate.php for these apps - // If present, run the import function for them. + foreach( self::$providers as $provider){ + // Is the app in the export? + if( array_key_exists( $provider->id, $apps ) ){ + // Did it succeed? + if( $app[$provider->id] ){ + // Then do the import + $provider->import(); + } + } + } return true; } // @breif connects to migration.db, or creates if not found + // @param $db optional path to migration.db, defaults to user data dir // @return bool whether the operation was successful - private static function connectDB(){ + private static function connectDB( $db=null ){ OC_Log::write('migration','connecting to migration.db for user: '.self::$uid,OC_Log::INFO); // Fail if no user is set if(!self::$uid){ @@ -152,6 +188,8 @@ class OC_Migrate{ if(!self::$MDB2){ require_once('MDB2.php'); + self::$database = !is_null( $db ) ? $db : $datadir.'/'.self::$uid.'/migration.db'; + $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); // Prepare options array @@ -164,7 +202,7 @@ class OC_Migrate{ ); $dsn = array( 'phptype' => 'sqlite3', - 'database' => $datadir.'/'.self::$uid.'/migration.db', + 'database' => self::$database, 'mode' => '0644' ); @@ -392,7 +430,9 @@ class OC_Migrate{ // Create the user $query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" ); $result = $query->execute( array( $uid, $data['hash'])); - + if( !$result ){ + OC_Log::write('migration', 'Failed to create the new user "'.$uid.""); + } return $result ? true : false; } From 4d5646a59f813e09455c78d840d0f62397ec60ad Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 16:21:17 +0000 Subject: [PATCH 024/302] Find migrate.php even for disabled apps. Improve ui for user and admin migrations --- apps/admin_export/settings.php | 17 ++--- apps/bookmarks/appinfo/app.php | 4 -- apps/bookmarks/lib/migrate.php | 1 + apps/user_migrate/settings.php | 91 ++++++++++++++---------- apps/user_migrate/templates/settings.php | 17 +++-- lib/migrate.php | 49 ++++++------- 6 files changed, 100 insertions(+), 79 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 5584181fbb..b60557f350 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -74,15 +74,16 @@ if (isset($_POST['admin_export'])) { } if (isset($_POST['user_files'])) { - // needs to handle data outside of the default data dir. - // adding user files - $zip->addFile($root . '/data/.htaccess', "data/.htaccess"); - $zip->addFile($root . '/data/index.html', "data/index.html"); - foreach (OC_User::getUsers() as $i) { - OC_Log::write('admin_export',"Adding owncloud user files of $i to export",OC_Log::INFO); - zipAddDir($root . "data/" . $i, $zip, true, "/data/"); - } + // needs to handle data outside of the default data dir. + // adding user files + $zip->addFile($root . '/data/.htaccess', "data/.htaccess"); + $zip->addFile($root . '/data/index.html', "data/index.html"); + foreach (OC_User::getUsers() as $i) { + OC_Log::write('admin_export',"Adding owncloud user files of $i to export",OC_Log::INFO); + zipAddDir($root . "data/" . $i, $zip, true, "/data/"); + } } + $zip->close(); header("Content-Type: application/zip"); header("Content-Disposition: attachment; filename=" . basename($filename)); diff --git a/apps/bookmarks/appinfo/app.php b/apps/bookmarks/appinfo/app.php index 13d76e0817..b9c308ca05 100644 --- a/apps/bookmarks/appinfo/app.php +++ b/apps/bookmarks/appinfo/app.php @@ -18,8 +18,4 @@ OC_App::addNavigationEntry( array( 'id' => 'bookmarks_index', 'order' => 70, 'hr OC_App::registerPersonal('bookmarks', 'settings'); OC_Util::addScript('bookmarks','bookmarksearch'); -// Include the migration provider - -require_once('apps/bookmarks/lib/migrate.php'); - OC_Search::registerProvider('OC_Search_Provider_Bookmarks'); diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 451699ec15..8387f70603 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -54,4 +54,5 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ } +// Load the provider new OC_Migrate_Provider_Bookmarks( 'bookmarks' ); \ No newline at end of file diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index c1121f2ddf..3b82e148b5 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -3,6 +3,8 @@ /** * ownCloud - user_migrate * + * @author Thomas Schmidt + * @copyright 2011 Thomas Schmidt tom@opensuse.org * @author Tom Needham * @copyright 2012 Tom Needham tom@owncloud.com * @@ -22,52 +24,67 @@ */ OC_Util::checkAppEnabled('user_migrate'); +define('DS', '/'); -if (isset($_POST['user_migrate'])) { - // Looks like they want to migrate - $root = OC::$SERVERROOT . "/"; - $user = OC_User::getUser(); +if (isset($_POST['user_export'])) { + + // Setup the export $zip = new ZipArchive(); - $tempdir = get_temp_dir(); - $filename = $tempdir . "/" . $user . "_export_" . date("y-m-d_H-i-s") . ".zip"; - OC_Log::write('user_migrate',"Creating user export file at: " . $filename,OC_Log::INFO); + $tmp = get_temp_dir(); + $user = OC_User::getUser(); + // Create owncoud dir + if( !file_exists( $tmp . '/owncloud' ) ){ + if( !mkdir( $tmp . '/owncloud' ) ){ + die('Failed to create the owncloud tmp directory'); + } + } + // Create the export dir + $exportdir = $tmp . '/owncloud' . '/export_' . $user . '_' . date("y-m-d_H-i-s"); + if( !file_exists( $exportdir ) ){ + if( !mkdir( $exportdir ) ){ + die('Failed to create the owncloud export directory'); + } + } + $filename = $exportdir . '/owncloud_export_' . $user . '_' . date("y-m-d_H-i-s") . ".zip"; + OC_Log::write('user_migrate',"Creating export file at: " . $filename,OC_Log::INFO); if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { exit("Cannot open <$filename>\n"); } + + // Migrate the app info + $info = OC_Migrate::export( $user ); + $infofile = $exportdir . '/exportinfo.json'; + if( !file_put_contents( $infofile, $info ) ){ + die('Failed to save the export info'); + } + $zip->addFile( $infofile, "exportinfo.json"); + $zip->addFile(OC::$SERVERROOT . '/data/' . $user . '/migration.db', "migration.db"); - // Does the user want to include their files? - if (isset($_POST['user_files'])) { - // needs to handle data outside of the default data dir. - // adding user files - OC_Log::write('user_migrate',"Adding owncloud user files of $user to export",OC_Log::INFO); - zipAddDir($root . "data/" . $user, $zip, true, "files/"); - } - - // Does the user want their app data? - if (isset($_POST['user_appdata'])) { - // adding owncloud system files - OC_Log::write('user_migrate',"Adding app data to user export file",OC_Log::INFO); - // Call to OC_Migrate for the xml file. - - // Create migration.db - OC_Migrate::export(OC_User::getUser()); - // Add export db to zip - $zip->addFile($root.'data/'.$user.'/migration.db', "migration.db"); - - } - + // Add the data dir + zipAddDir(OC::$SERVERROOT . "/data/" . $user, $zip, true, "files/"); + + // Save the zip $zip->close(); - - //header("Content-Type: application/zip"); - //header("Content-Disposition: attachment; filename=" . basename($filename)); - //header("Content-Length: " . filesize($filename)); - //readfile($filename); - //unlink($filename); -} else { -// fill template + // Send the zip + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($filename)); + header("Content-Length: " . filesize($filename)); + @ob_end_clean(); + readfile($filename); + // Cleanup + unlink($filename); + unlink($infofile); + rmdir($exportdir); + +} if( isset( $_POST['user_import'] ) ){ + // TODO +}else { + + // fill template $tmpl = new OC_Template('user_migrate', 'settings'); return $tmpl->fetchPage(); + } function zipAddDir($dir, $zip, $recursive=true, $internalDir='') { @@ -89,6 +106,6 @@ function zipAddDir($dir, $zip, $recursive=true, $internalDir='') { } closedir($dirhandle); } else { - OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); + OC_Log::write('user_migrate',"Was not able to open directory: " . $dir,OC_Log::ERROR); } } diff --git a/apps/user_migrate/templates/settings.php b/apps/user_migrate/templates/settings.php index ece8f70e06..59a27a926d 100644 --- a/apps/user_migrate/templates/settings.php +++ b/apps/user_migrate/templates/settings.php @@ -1,12 +1,17 @@
t('Export your user account');?> -

t('This will create a compressed file that contains the data of owncloud account. - Please choose which components should be included:');?> +

t('This will create a compressed file that contains your ownCloud account.');?>

-


-
-

- + +
+ +
+
+ t('Import user account');?> +

+

+

+
diff --git a/lib/migrate.php b/lib/migrate.php index 075358c72b..88c0e7cfc2 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -26,11 +26,16 @@ */ class OC_Migrate{ + // Holds the db object static private $MDB2=false; + // Array of OC_Migration_Provider objects static private $providers=array(); + // Schema db object static private $schema=false; + // User id of the user to import/export static private $uid=false; - static private $database=false; + // Path to the sqlite db + static private $dbpath=false; /** * register a new migration provider @@ -64,9 +69,18 @@ class OC_Migrate{ $ok = true; $return = array(); + // Find the providers + $apps = OC_App::getAllApps(); + + foreach($apps as $app){ + $path = OC::$SERVERROOT . '/apps/' . $app . '/lib/migrate.php'; + if( file_exists( $path ) ){ + include( $path ); + } + } + // Foreach provider foreach( self::$providers as $provider ){ - $failed = false; // Does this app use the database? @@ -110,7 +124,7 @@ class OC_Migrate{ /** * @breif imports a new user * @param $db string path to migration.db - * @param $migrateinfo string path to the migration info json file + * @param $migrateinfo array of migration ino * @param $uid optional uid to use * @return bool if the import succedded */ @@ -135,27 +149,14 @@ class OC_Migrate{ exit(); } - // Load the json info - if( file_exists( $migrateinfo ) ){ - - } else { - OC_Log::write( 'migration', 'Migration information file not found at: '.$migrateinfo, OC_Log::FATAL ); + if( !is_array( $migrateinfo ) ){ + OC_Log::write('migration','$migrateinfo is not an array', OC_Log::FATAL); return false; exit(); } - // Process migration info - $info = file_get_contents( $migrateinfo ); - $info = json_decode( $info ); - // Set the user id - self::$uid = !$uid : $info['migrateinfo']['uid'] ? $uid; - - // Create the user - if(!self::createUser($uid, $hash)){ - return false; - exit(); - } + self::$uid = $info['migrateinfo']['uid']; $apps = $info['apps']; @@ -177,7 +178,7 @@ class OC_Migrate{ // @breif connects to migration.db, or creates if not found // @param $db optional path to migration.db, defaults to user data dir // @return bool whether the operation was successful - private static function connectDB( $db=null ){ + private static function connectDB( $dbpath=null ){ OC_Log::write('migration','connecting to migration.db for user: '.self::$uid,OC_Log::INFO); // Fail if no user is set if(!self::$uid){ @@ -188,10 +189,10 @@ class OC_Migrate{ if(!self::$MDB2){ require_once('MDB2.php'); - self::$database = !is_null( $db ) ? $db : $datadir.'/'.self::$uid.'/migration.db'; - $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + self::$dbpath = $datadir.'/'.self::$uid.'/migration.db';//!is_null( $dbpath ) ? $dbpath : $datadir.'/'.self::$uid.'/migration.db'; + // Prepare options array $options = array( 'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE), @@ -202,10 +203,10 @@ class OC_Migrate{ ); $dsn = array( 'phptype' => 'sqlite3', - 'database' => self::$database, + 'database' => self::$dbpath, 'mode' => '0644' ); - + // Try to establish connection self::$MDB2 = MDB2::factory( $dsn, $options ); // Die if we could not connect From 111af7fed5a03d45793536c23ecc5c25bfc1d36f Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 17:04:49 +0000 Subject: [PATCH 025/302] json encode the output --- apps/user_migrate/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 3b82e148b5..edad7357a2 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -52,7 +52,7 @@ if (isset($_POST['user_export'])) { } // Migrate the app info - $info = OC_Migrate::export( $user ); + $info = json_encode( OC_Migrate::export( $user ) ); $infofile = $exportdir . '/exportinfo.json'; if( !file_put_contents( $infofile, $info ) ){ die('Failed to save the export info'); From 7e3b35a57c53b6e90cb8560d7b4c90e3aba2a3b5 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 17:08:20 +0000 Subject: [PATCH 026/302] fix structure of user export zip --- apps/user_migrate/settings.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index edad7357a2..00f46660cf 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -53,15 +53,11 @@ if (isset($_POST['user_export'])) { // Migrate the app info $info = json_encode( OC_Migrate::export( $user ) ); - $infofile = $exportdir . '/exportinfo.json'; - if( !file_put_contents( $infofile, $info ) ){ - die('Failed to save the export info'); - } - $zip->addFile( $infofile, "exportinfo.json"); - $zip->addFile(OC::$SERVERROOT . '/data/' . $user . '/migration.db', "migration.db"); + $infofile = OC::$SERVERROOT . '/data/' . $user . '/exportinfo.json'; + file_put_contents( $infofile, $info ); - // Add the data dir - zipAddDir(OC::$SERVERROOT . "/data/" . $user, $zip, true, "files/"); + // Add the data dir (which includes migration.db and exportinfo.json) + zipAddDir(OC::$SERVERROOT . "/data/" . $user, $zip, true, "/"); // Save the zip $zip->close(); From 0f3eebbbd95b75e3dfe8f9322d8fd486925ac54a Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 17:18:42 +0000 Subject: [PATCH 027/302] added cleanUp() method to OC_Migrate --- apps/user_migrate/settings.php | 2 +- lib/migrate.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 00f46660cf..c017ba226c 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -70,8 +70,8 @@ if (isset($_POST['user_export'])) { readfile($filename); // Cleanup unlink($filename); - unlink($infofile); rmdir($exportdir); + OC_Migrate::cleanUp(); } if( isset( $_POST['user_import'] ) ){ // TODO diff --git a/lib/migrate.php b/lib/migrate.php index 88c0e7cfc2..1be229d8fd 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -438,4 +438,19 @@ class OC_Migrate{ } + /** + * @breif removes migration.db and exportinfo.json from the users data dir + * @return void + */ + static public function cleanUp(){ + if( !self::$uid ){ + OC_Log::write('migration', 'Failed to cleanup after migration', OC_Log::ERROR); + return false; + } + // Remove migration.db + unlink( OC::$SERVERROOT . '/data/' . self::$uid . '/migration.db' ); + // Remove exportinfo.json + unlink( OC::$SERVERROOT . '/data/' . self::$uid . '/exportinfo.json' ); + return true; + } } From cd2f75fdad6629f850bb07f07cd45e04d75de97d Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 17:27:47 +0000 Subject: [PATCH 028/302] Use data dir from config.php --- apps/admin_export/settings.php | 4 ++-- apps/user_migrate/settings.php | 22 +++++----------------- lib/migrate.php | 5 +++-- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index b60557f350..73a4209d3f 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -31,6 +31,7 @@ define('DS', '/'); if (isset($_POST['admin_export'])) { $root = OC::$SERVERROOT . "/"; + $datadir = OC_Config::getValue( 'datadirectory' ); $zip = new ZipArchive(); $tempdir = get_temp_dir(); $filename = $tempdir . "/owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; @@ -70,7 +71,6 @@ if (isset($_POST['admin_export'])) { OC_Log::write('admin_export',"Adding owncloud config to export",OC_Log::INFO); zipAddDir($root . "config/", $zip, true, "/"); - $zip->addFile($root . '/data/.htaccess', "data/owncloud.db"); } if (isset($_POST['user_files'])) { @@ -80,7 +80,7 @@ if (isset($_POST['admin_export'])) { $zip->addFile($root . '/data/index.html', "data/index.html"); foreach (OC_User::getUsers() as $i) { OC_Log::write('admin_export',"Adding owncloud user files of $i to export",OC_Log::INFO); - zipAddDir($root . "data/" . $i, $zip, true, "/data/"); + zipAddDir($datadir . '/' . $i, $zip, true, "/data/"); } } diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index c017ba226c..5e8ac9c21d 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -32,20 +32,9 @@ if (isset($_POST['user_export'])) { $zip = new ZipArchive(); $tmp = get_temp_dir(); $user = OC_User::getUser(); - // Create owncoud dir - if( !file_exists( $tmp . '/owncloud' ) ){ - if( !mkdir( $tmp . '/owncloud' ) ){ - die('Failed to create the owncloud tmp directory'); - } - } - // Create the export dir - $exportdir = $tmp . '/owncloud' . '/export_' . $user . '_' . date("y-m-d_H-i-s"); - if( !file_exists( $exportdir ) ){ - if( !mkdir( $exportdir ) ){ - die('Failed to create the owncloud export directory'); - } - } - $filename = $exportdir . '/owncloud_export_' . $user . '_' . date("y-m-d_H-i-s") . ".zip"; + + $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . $user; + $filename = $userdatadir . '/owncloud_export_' . $user . '_' . date("y-m-d_H-i-s") . ".zip"; OC_Log::write('user_migrate',"Creating export file at: " . $filename,OC_Log::INFO); if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { exit("Cannot open <$filename>\n"); @@ -53,11 +42,11 @@ if (isset($_POST['user_export'])) { // Migrate the app info $info = json_encode( OC_Migrate::export( $user ) ); - $infofile = OC::$SERVERROOT . '/data/' . $user . '/exportinfo.json'; + $infofile = $userdatadir . '/exportinfo.json'; file_put_contents( $infofile, $info ); // Add the data dir (which includes migration.db and exportinfo.json) - zipAddDir(OC::$SERVERROOT . "/data/" . $user, $zip, true, "/"); + zipAddDir( $userdatadir, $zip, true, "/" ); // Save the zip $zip->close(); @@ -70,7 +59,6 @@ if (isset($_POST['user_export'])) { readfile($filename); // Cleanup unlink($filename); - rmdir($exportdir); OC_Migrate::cleanUp(); } if( isset( $_POST['user_import'] ) ){ diff --git a/lib/migrate.php b/lib/migrate.php index 1be229d8fd..a6a6432d0d 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -447,10 +447,11 @@ class OC_Migrate{ OC_Log::write('migration', 'Failed to cleanup after migration', OC_Log::ERROR); return false; } + $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . self::$uid; // Remove migration.db - unlink( OC::$SERVERROOT . '/data/' . self::$uid . '/migration.db' ); + unlink( $userdatadir . '/migration.db' ); // Remove exportinfo.json - unlink( OC::$SERVERROOT . '/data/' . self::$uid . '/exportinfo.json' ); + unlink( $userdatadir . '/exportinfo.json' ); return true; } } From a919a136c418f99bded289034090b4e8d4e23533 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 21:24:07 +0000 Subject: [PATCH 029/302] Finish import function for bookmarks --- apps/bookmarks/lib/migrate.php | 42 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 8387f70603..4f11bc5bde 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -32,24 +32,36 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ } // Import function for bookmarks - function import( $data, $uid ){ + function import( $info ){ - // new id mapping - $newids = array(); - - // Import bookmarks - foreach($data['bookmarks'] as $bookmark){ - $bookmark['user_id'] = $uid; - // import to the db now - $newids[$bookmark['id']] = OC_DB::insertid(); + switch( $info['appversion'] ){ + default: + // All versions of the app have had the same db structure, so all can use the same import function + $query = OC_Migrate::prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); + $results = $query->execute( array( $info['olduid'] ) ); + $idmap = array(); + while( $row = $data->fetchRow() ){ + // Import each bookmark, saving its id into the map + $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks(url, title, user_id, public, added, lastmodified) VALUES (?, ?, ?, ?, ?, ?)" ); + $query->execute( array( $row['url'], $row['title'], $info['newuid'], $row['public'], $row['added'], $row['lastmodified'] ) ); + // Map the id + $idmap[$row['id']] = OC_DB::insertid(); + } + // Now tags + foreach($idmap as $oldid => $newid){ + $query = OC_Migrate::prepare( "SELECT * FROM bookmarks_tags WHERE user_id LIKE ?" ); + $results = $query->execute( array( $oldid ) ); + while( $row = $data->fetchRow() ){ + // Import the tags for this bookmark, using the new bookmark id + $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks_tags(bookmark_id, tag) VALUES (?, ?)" ); + $query->execute( array( $newid, $row['tag'] ) ); + } + } + // All done! + break; } - // Import tags - foreach($data['bookmarks_tags'] as $tag){ - // Map the new ids - $tag['id'] = $newids[$tag['id']]; - // Import to the db now using OC_DB - } + return true; } } From fa8b66ca4f266ebc72ca204e85cf0e69c4c4aa25 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 21:28:53 +0000 Subject: [PATCH 030/302] Add getApps() method to return apps supporting migration --- lib/migrate.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/migrate.php b/lib/migrate.php index a6a6432d0d..863cf261f4 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -121,6 +121,21 @@ class OC_Migrate{ } + /** + * @breif returns an array of apps that support migration + * @return array + */ + static public function getApps(){ + $allapps = OC_App::getAllApps(); + foreach($allapps as $app){ + $path = OC::$SERVERROOT . '/apps/' . $app . '/lib/migrate.php'; + if( file_exists( $path ) ){ + $supportsmigration[] = $app; + } + } + reutrn $supportsmigration; + } + /** * @breif imports a new user * @param $db string path to migration.db From 5a50144a16fa9b5d8caf9ee261e3c4a39eaa04bc Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 21:29:31 +0000 Subject: [PATCH 031/302] typo :/ --- lib/migrate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migrate.php b/lib/migrate.php index 863cf261f4..acc01ec7bb 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -133,7 +133,7 @@ class OC_Migrate{ $supportsmigration[] = $app; } } - reutrn $supportsmigration; + return $supportsmigration; } /** From a310a81053c31205abd6d62491304705b1f565e2 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 13 Mar 2012 23:09:43 +0000 Subject: [PATCH 032/302] move zip creation inside OC_Migrate --- apps/user_migrate/settings.php | 77 +++++------------------ lib/migrate.php | 112 +++++++++++++++++++++++++++------ lib/migrate/provider.php | 5 +- 3 files changed, 111 insertions(+), 83 deletions(-) diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 5e8ac9c21d..9fbb4da9e5 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -24,72 +24,27 @@ */ OC_Util::checkAppEnabled('user_migrate'); -define('DS', '/'); - if (isset($_POST['user_export'])) { - - // Setup the export - $zip = new ZipArchive(); - $tmp = get_temp_dir(); - $user = OC_User::getUser(); - - $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . $user; - $filename = $userdatadir . '/owncloud_export_' . $user . '_' . date("y-m-d_H-i-s") . ".zip"; - OC_Log::write('user_migrate',"Creating export file at: " . $filename,OC_Log::INFO); - if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { - exit("Cannot open <$filename>\n"); - } - - // Migrate the app info - $info = json_encode( OC_Migrate::export( $user ) ); - $infofile = $userdatadir . '/exportinfo.json'; - file_put_contents( $infofile, $info ); - - // Add the data dir (which includes migration.db and exportinfo.json) - zipAddDir( $userdatadir, $zip, true, "/" ); - - // Save the zip - $zip->close(); - - // Send the zip - header("Content-Type: application/zip"); - header("Content-Disposition: attachment; filename=" . basename($filename)); - header("Content-Length: " . filesize($filename)); - @ob_end_clean(); - readfile($filename); - // Cleanup - unlink($filename); - OC_Migrate::cleanUp(); - + // Create the export zip + $user = OC_User::getUser(); + $path = OC_Config::getValue( 'datadirectory' ) . '/' . OC_User::getUser() . '/'; + if( OC_Migrate::createExportFile( $user, $path ) ){ + // Download it then + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($path)); + header("Content-Length: " . filesize($path)); + @ob_end_clean(); + readfile($path); + OC_Migrate::cleanUp(); + } else { + die('error'); + } } if( isset( $_POST['user_import'] ) ){ // TODO }else { - // fill template - $tmpl = new OC_Template('user_migrate', 'settings'); - return $tmpl->fetchPage(); - + $tmpl = new OC_Template('user_migrate', 'settings'); + return $tmpl->fetchPage(); } -function zipAddDir($dir, $zip, $recursive=true, $internalDir='') { - $dirname = basename($dir); - $zip->addEmptyDir($internalDir . $dirname); - $internalDir.=$dirname.='/'; - if ($dirhandle = opendir($dir)) { - while (false !== ( $file = readdir($dirhandle))) { - - if (( $file != '.' ) && ( $file != '..' )) { - - if (is_dir($dir . '/' . $file) && $recursive) { - zipAddDir($dir . '/' . $file, $zip, $recursive, $internalDir); - } elseif (is_file($dir . '/' . $file)) { - $zip->addFile($dir . '/' . $file, $internalDir . $file); - } - } - } - closedir($dirhandle); - } else { - OC_Log::write('user_migrate',"Was not able to open directory: " . $dir,OC_Log::ERROR); - } -} diff --git a/lib/migrate.php b/lib/migrate.php index acc01ec7bb..b4c4d635ff 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -36,6 +36,10 @@ class OC_Migrate{ static private $uid=false; // Path to the sqlite db static private $dbpath=false; + // Holds the ZipArchive object + static private $zip=false; + // String path to export + static private $zippath=false; /** * register a new migration provider @@ -47,24 +51,10 @@ class OC_Migrate{ /** * @breif creates a migration.db in the users data dir with their app data in - * @param @uid string userid of the user to export for * @return bool whether operation was successfull */ - public static function export( $uid ){ - - // Only export database users, otherwise we get chaos - if(!OC_User_Database::userExists( $uid )){ - return false; - } - - self::$uid = $uid; - - if(empty(self::$uid)){ - OC_Log::write('migration','Invalid uid passed',OC_Log::FATAL); - return false; - exit(); - } - + private static function exportAppData( ){ + self::connectDB(); $ok = true; $return = array(); @@ -100,7 +90,7 @@ class OC_Migrate{ // Run the import function? if( !$failed ){ - $return['app'][$provider->id]['success'] = $provider->export( $uid ); + $return['app'][$provider->id]['success'] = $provider->export( self::$uid ); } else { $return['app'][$provider->id]['success'] = false; $return['app'][$provider->id]['message'] = 'failed to create the app tables'; @@ -114,13 +104,95 @@ class OC_Migrate{ // Add some general info to the return array - $return['migrateinfo']['uid'] = $uid; + $return['migrateinfo']['uid'] = self::$uid; $return['migrateinfo']['ocversion'] = OC_Util::getVersionString(); return $return; } + /** + * @breif creates a zip user export + * @param $uid string user id of the user to export + * @param $path string path to folder to create file in (with trailing slash) + * @return bool success + */ + static public function createExportFile( $uid, $path ){ + // Is a directory + if( !is_dir( $path ) ){ + OC_Log::write('migration', 'Path supplied to createExportFile() is not a directory', OC_Log::ERROR); + return false; + exit(); + } + // Is writeable + if( !is_writeable( $path ) ){ + OC_Log::write('migration', 'Path supplied to createExportFile() is not writeable', OC_Log::ERROR); + return false; + exit(); + } + // Is a database user? + if( !OC_User_Database::userExists( $uid ) ){ + OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR); + return false; + exit(); + } + + self::$uid = $uid; + self::$zip = new ZipArchive; + + // Get some info + $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . self::$uid; + self::$zippath = $path . 'owncloud_export_' . self::$uid . '_' . date("y-m-d_H-i-s") . ".zip"; + if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE ) !== TRUE ) { + OC_Log::write('migration','Cannot create a zip file at: '.self::$zippath, OC_Log::ERROR); + return false; + exit(); + } + + // Export the app info + $info = json_encode( self::exportAppData() ); + file_put_contents( $userdatadir . '/exportinfo.json', $info ); + + // Add the data dir (which includes migration.db and exportinfo.json) + self::addDirToZip( $userdatadir, '/' ); + + // All done! + if( !self::$zip->close() ){ + OC_Log::write('migration', 'Failed to save the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); + return false; + exit(); + } else { + OC_Log::write('migration', 'Created export file for: '.self::$uid, OC_Log::INFO); + return true; + } + + + } + + /** + * @breif adds a directory to the zip object + * @return void + */ + static private function addDirToZip( $dir, $recursive=true, $internalDir='' ){ + $dirname = basename($dir); + self::$zip->addEmptyDir($internalDir . $dirname); + $internalDir.=$dirname.='/'; + if ($dirhandle = opendir($dir)) { + while (false !== ( $file = readdir($dirhandle))) { + if (( $file != '.' ) && ( $file != '..' )) { + if (is_dir($dir . '/' . $file) && $recursive) { + self::addDirToZip($dir . '/' . $file, $recursive, $internalDir); + } elseif (is_file($dir . '/' . $file)) { + self::$zip->addFile($dir . '/' . $file, $internalDir . $file); + } + } + } + closedir($dirhandle); + } else { + OC_Log::write('migration',"Was not able to open directory: " . $dir,OC_Log::ERROR); + } + } + /** * @breif returns an array of apps that support migration * @return array @@ -143,7 +215,7 @@ class OC_Migrate{ * @param $uid optional uid to use * @return bool if the import succedded */ - public static function import( $db, $migrateinfo, $uid=false ){ + public static function importAppData( $db, $migrateinfo, $uid=false ){ if(!self::$uid){ OC_Log::write('migration','Tried to import without passing a uid',OC_Log::FATAL); @@ -467,6 +539,8 @@ class OC_Migrate{ unlink( $userdatadir . '/migration.db' ); // Remove exportinfo.json unlink( $userdatadir . '/exportinfo.json' ); + // Remove the zip + unlink(self::$zippath); return true; } } diff --git a/lib/migrate/provider.php b/lib/migrate/provider.php index 9c03639b7c..e2e01b3b5a 100644 --- a/lib/migrate/provider.php +++ b/lib/migrate/provider.php @@ -20,9 +20,8 @@ abstract class OC_Migrate_Provider{ /** * @breif imports data for the app - * @param $data array of data. eg: array('info'=> APPINFO, 'data'=>APPDATA ARRAY) - * @param $info array of info of the source install + * @param $info array of info including exportinfo.json * @return void */ - abstract function import($data,$uid); + abstract function import( $info ); } From c9be325af2707b256f83cafbda3f7e3713f97876 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Wed, 14 Mar 2012 16:43:06 +0000 Subject: [PATCH 033/302] Fix zip creation. Add param to cleanUp() method. Add defaults to createExportFile() method. --- apps/user_migrate/settings.php | 15 ++-- lib/migrate.php | 122 ++++++++++++++++++--------------- 2 files changed, 75 insertions(+), 62 deletions(-) diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 9fbb4da9e5..04aca51f51 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -26,22 +26,21 @@ OC_Util::checkAppEnabled('user_migrate'); if (isset($_POST['user_export'])) { // Create the export zip - $user = OC_User::getUser(); - $path = OC_Config::getValue( 'datadirectory' ) . '/' . OC_User::getUser() . '/'; - if( OC_Migrate::createExportFile( $user, $path ) ){ - // Download it then + if( !$path = OC_Migrate::createExportFile() ){ + // Error + die('error'); + } else { + // Download it header("Content-Type: application/zip"); header("Content-Disposition: attachment; filename=" . basename($path)); header("Content-Length: " . filesize($path)); @ob_end_clean(); readfile($path); - OC_Migrate::cleanUp(); - } else { - die('error'); + OC_Migrate::cleanUp( $path ); } } if( isset( $_POST['user_import'] ) ){ // TODO -}else { +} else { // fill template $tmpl = new OC_Template('user_migrate', 'settings'); return $tmpl->fetchPage(); diff --git a/lib/migrate.php b/lib/migrate.php index b4c4d635ff..728f15e1f6 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -113,85 +113,100 @@ class OC_Migrate{ /** * @breif creates a zip user export - * @param $uid string user id of the user to export - * @param $path string path to folder to create file in (with trailing slash) - * @return bool success + * @param optional $uid string user id of the user to export (defaults to current) + * @param optional $path string path to folder to create file in (with trailing slash) (defaults to current user's data dir) + * @return false on failure | string path on success */ - static public function createExportFile( $uid, $path ){ - // Is a directory - if( !is_dir( $path ) ){ - OC_Log::write('migration', 'Path supplied to createExportFile() is not a directory', OC_Log::ERROR); - return false; - exit(); - } - // Is writeable - if( !is_writeable( $path ) ){ - OC_Log::write('migration', 'Path supplied to createExportFile() is not writeable', OC_Log::ERROR); - return false; - exit(); - } + static public function createExportFile( $uid=null, $path=null ){ + // User passed? + $uid = is_null( $uid ) ? OC_User::getUser() : $uid ; // Is a database user? if( !OC_User_Database::userExists( $uid ) ){ OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR); return false; exit(); } - + // Set the uid self::$uid = $uid; + // Create the zip object self::$zip = new ZipArchive; - - // Get some info - $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . self::$uid; - self::$zippath = $path . 'owncloud_export_' . self::$uid . '_' . date("y-m-d_H-i-s") . ".zip"; - if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE ) !== TRUE ) { - OC_Log::write('migration','Cannot create a zip file at: '.self::$zippath, OC_Log::ERROR); - return false; - exit(); + // Calculate users data dir + $user = OC_User::getUser(); + $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . $user . '/'; + // Calculate zip name + $zipname = "owncloud_userexport_" . $user . '_' . date("y-m-d_H-i-s") . ".zip"; + // Calculate destination + if( !is_null( $path ) ){ + // Path given + // Is a directory? + if( !is_dir( $path ) ){ + OC_Log::write('migration', 'Path supplied to createExportFile() is not a directory', OC_Log::ERROR); + return false; + exit(); + } + // Is writeable + if( !is_writeable( $path ) ){ + OC_Log::write('migration', 'Path supplied to createExportFile() is not writeable', OC_Log::ERROR); + return false; + exit(); + } + self::$zippath = $path . $zipname; + } else { + // Save in users data dir + self::$zippath = $userdatadir . $zipname; } - - // Export the app info + if (self::$zip->open(self::$zippath, ZIPARCHIVE::CREATE) !== TRUE) { + // TODO ADD LOGGING + exit("Cannot open <$filename>\n"); + } + // Export the app info $info = json_encode( self::exportAppData() ); file_put_contents( $userdatadir . '/exportinfo.json', $info ); - - // Add the data dir (which includes migration.db and exportinfo.json) - self::addDirToZip( $userdatadir, '/' ); - - // All done! + // Add the data dir to the zip + self::addDirToZip( $userdatadir ); + // All done! if( !self::$zip->close() ){ OC_Log::write('migration', 'Failed to save the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); return false; exit(); } else { OC_Log::write('migration', 'Created export file for: '.self::$uid, OC_Log::INFO); - return true; + //return true; } - - + return self::$zippath; } /** * @breif adds a directory to the zip object - * @return void + * @param $dir string path of the directory to add + * @param $recursive bool + * @param $internaldir string path of folder to add dir to in zip + * @return bool */ - static private function addDirToZip( $dir, $recursive=true, $internalDir='' ){ - $dirname = basename($dir); - self::$zip->addEmptyDir($internalDir . $dirname); - $internalDir.=$dirname.='/'; - if ($dirhandle = opendir($dir)) { + static private function addDirToZip($dir, $recursive=true, $internaldir='') { + $dirname = basename($dir); + self::$zip->addEmptyDir($internaldir . $dirname); + $internaldir.=$dirname.='/'; + + if ($dirhandle = opendir($dir)) { while (false !== ( $file = readdir($dirhandle))) { + if (( $file != '.' ) && ( $file != '..' )) { + if (is_dir($dir . '/' . $file) && $recursive) { - self::addDirToZip($dir . '/' . $file, $recursive, $internalDir); + self::addDirToZip($dir . '/' . $file, $recursive, $internaldir); } elseif (is_file($dir . '/' . $file)) { - self::$zip->addFile($dir . '/' . $file, $internalDir . $file); + self::$zip->addFile($dir . '/' . $file, $internaldir . $file); } } } closedir($dirhandle); - } else { - OC_Log::write('migration',"Was not able to open directory: " . $dir,OC_Log::ERROR); - } - } + } else { + OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); + return false; + } + return true; + } /** * @breif returns an array of apps that support migration @@ -483,7 +498,7 @@ class OC_Migrate{ } - /** + /** * @brief connects to a MDB2 database scheme * @returns true/false * @@ -527,20 +542,19 @@ class OC_Migrate{ /** * @breif removes migration.db and exportinfo.json from the users data dir + * @param optional $path string path to the export zip to delete * @return void */ - static public function cleanUp(){ - if( !self::$uid ){ - OC_Log::write('migration', 'Failed to cleanup after migration', OC_Log::ERROR); - return false; - } + static public function cleanUp( $path=null ){ $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . self::$uid; // Remove migration.db unlink( $userdatadir . '/migration.db' ); // Remove exportinfo.json unlink( $userdatadir . '/exportinfo.json' ); // Remove the zip - unlink(self::$zippath); + if( !is_null( $path ) ){ + unlink( $path ); + } return true; } } From 50233d075c47c86a5a26d4f946f8aa09f703cb15 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Thu, 15 Mar 2012 20:52:43 +0000 Subject: [PATCH 034/302] Improve admin_export ui and move system export cde to OC_Migrate --- apps/admin_export/settings.php | 78 +++--------- apps/admin_export/templates/settings.php | 11 +- apps/user_migrate/settings.php | 2 +- lib/migrate.php | 150 +++++++++++++++++++---- 4 files changed, 151 insertions(+), 90 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 73a4209d3f..9db1d75db9 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -28,70 +28,22 @@ OC_Util::checkAppEnabled('admin_export'); define('DS', '/'); - +// Export? if (isset($_POST['admin_export'])) { - $root = OC::$SERVERROOT . "/"; - $datadir = OC_Config::getValue( 'datadirectory' ); - $zip = new ZipArchive(); - $tempdir = get_temp_dir(); - $filename = $tempdir . "/owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; - OC_Log::write('admin_export',"Creating export file at: " . $filename,OC_Log::INFO); - if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { - exit("Cannot open <$filename>\n"); - } - - if (isset($_POST['owncloud_system'])) { - // adding owncloud system files - OC_Log::write('admin_export',"Adding owncloud system files to export",OC_Log::INFO); - zipAddDir($root, $zip, false); - foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dirname) { - zipAddDir($root . $dirname, $zip, true, "/"); - } - } - - if (isset($_POST['owncloud_config'])) { - // adding owncloud config - // todo: add database export - $dbfile = $tempdir . "/dbexport.xml"; - OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); - - // Now add in *dbname* and *dbtableprefix* - $dbexport = file_get_contents( $dbfile ); - - $dbnamestring = "\n\n " . OC_Config::getValue( "dbname", "owncloud" ); - $dbtableprefixstring = "
\n\n " . OC_Config::getValue( "dbtableprefix", "_oc" ); - - $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); - $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbprefix*", $dbexport ); - - // Write the new db export file - file_put_contents( $dbfile, $dbexport ); - - $zip->addFile($dbfile, "dbexport.xml"); - - OC_Log::write('admin_export',"Adding owncloud config to export",OC_Log::INFO); - zipAddDir($root . "config/", $zip, true, "/"); - } - - if (isset($_POST['user_files'])) { - // needs to handle data outside of the default data dir. - // adding user files - $zip->addFile($root . '/data/.htaccess', "data/.htaccess"); - $zip->addFile($root . '/data/index.html', "data/index.html"); - foreach (OC_User::getUsers() as $i) { - OC_Log::write('admin_export',"Adding owncloud user files of $i to export",OC_Log::INFO); - zipAddDir($datadir . '/' . $i, $zip, true, "/data/"); - } - } - - $zip->close(); - header("Content-Type: application/zip"); - header("Content-Disposition: attachment; filename=" . basename($filename)); - header("Content-Length: " . filesize($filename)); - @ob_end_clean(); - readfile($filename); - unlink($filename); - unlink($dbfile); + // Create the export zip + if( !$path = OC_Migrate::createSysExportFile( $_POST['export_type'] ) ){ + // Error + die('error'); + } else { + // Download it + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($path)); + header("Content-Length: " . filesize($path)); + @ob_end_clean(); + readfile($path); + OC_Migrate::cleanUp( $path ); + } +// Import? } else if( isset($_POST['admin_import']) ){ $root = OC::$SERVERROOT . "/"; diff --git a/apps/admin_export/templates/settings.php b/apps/admin_export/templates/settings.php index 9f0845bf55..15a19b7c63 100644 --- a/apps/admin_export/templates/settings.php +++ b/apps/admin_export/templates/settings.php @@ -2,12 +2,13 @@
t('Export this ownCloud instance');?>

t('This will create a compressed file that contains the data of this owncloud instance. - Please choose which components should be included:');?> -

-


-
- + Please choose the export type:');?>

+

What would you like to export?

+

+ ownCloud instance ( suitable for import )
+ ownCloud system files
+ Just user files

diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 04aca51f51..d862ac5a82 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -26,7 +26,7 @@ OC_Util::checkAppEnabled('user_migrate'); if (isset($_POST['user_export'])) { // Create the export zip - if( !$path = OC_Migrate::createExportFile() ){ + if( !$path = OC_Migrate::createUserExportFile() ){ // Error die('error'); } else { diff --git a/lib/migrate.php b/lib/migrate.php index 728f15e1f6..8f26ea7ae6 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -111,20 +111,120 @@ class OC_Migrate{ } + /** + * @breif creates an export file for the whole system + * @param optional $exporttype string export type ('instance','system' or 'userfiles') + * @param optional $path string path to zip destination (with trailing slash) + * @return path to the zip or false if there was a problem + */ + static public function createSysExportFile( $exporttype='instance', $path=null ){ + // Calculate zip name + $zipname = "owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; + // Get the data dir + $datadir = OC_Config::getValue( 'datadirectory' ); + // Calculate destination + if( !is_null( $path ) ){ + // Path given + // Is a directory? + if( !is_dir( $path ) ){ + OC_Log::write('migration', 'Path supplied to createSysExportFile() is not a directory', OC_Log::ERROR); + return false; + } + // Is writeable + if( !is_writeable( $path ) ){ + OC_Log::write('migration', 'Path supplied to createSysExportFile() is not writeable', OC_Log::ERROR); + return false; + } + self::$zippath = $path . $zipname; + } else { + // Save in tmp dir + $structure = sys_get_temp_dir() . '/owncloudexports/'; + if( !file_exists( $structure ) ){ + if ( !mkdir( $structure ) ) { + OC_Log::write('migration', 'Could not create the temporary export at: '.$structure, OC_Log::ERROR); + return false; + } + } + self::$zippath = $structure . $zipname; + } + // Create the zip object + self::$zip = new ZipArchive; + // Try to create the zip + if( !self::createZip() ){ + return false; + } + // Handle export types + if( $exporttype == 'instance' ){ + // Creates a zip that is compatable with the import function + /* + $dbfile = self:: . "/dbexport.xml"; + OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); + + // Now add in *dbname* and *dbtableprefix* + $dbexport = file_get_contents( $dbfile ); + + $dbnamestring = "\n\n " . OC_Config::getValue( "dbname", "owncloud" ); + $dbtableprefixstring = "
\n\n " . OC_Config::getValue( "dbtableprefix", "_oc" ); + + $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); + $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbprefix*", $dbexport ); + + // Write the new db export file + file_put_contents( $dbfile, $dbexport ); + + $zip->addFile($dbfile, "dbexport.xml"); + */ + } else if( $exporttype == 'system' ){ + // Creates a zip with the owncloud system files + self::addDirToZip( OC::$SERVERROOT . '/', false); + foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dir) { + self::addDirToZip( OC::$SERVERROOT . '/' . $dir, true, "/"); + } + } else if ( $exporttype == 'userfiles' ){ + // Creates a zip with all of the users files + foreach(OC_User::getUsers() as $user){ + self::addDirToZip( $datadir . '/' . $user . '/', true, "/" . $user); + } + } else { + // Invalid export type supplied + OC_Log::write('migration', 'Invalid export type supplied to createSysExportFile() "'.$exporttype.'"', OC_Log::ERROR); + return false; + } + // Close the zip + if( !self::closeZip() ){ + return false; + } + return self::$zippath; + + } + + /** + * @breif tried to finalise the zip + * @return bool + */ + static private function closeZip(){ + if( !self::$zip->close() ){ + OC_Log::write('migration', 'Failed to save the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); + return false; + } else { + OC_Log::write('migration', 'Created export file for: '.self::$uid, OC_Log::INFO); + return true; + } + } + /** * @breif creates a zip user export * @param optional $uid string user id of the user to export (defaults to current) * @param optional $path string path to folder to create file in (with trailing slash) (defaults to current user's data dir) * @return false on failure | string path on success */ - static public function createExportFile( $uid=null, $path=null ){ + static public function createUserExportFile( $uid=null, $path=null ){ // User passed? $uid = is_null( $uid ) ? OC_User::getUser() : $uid ; // Is a database user? if( !OC_User_Database::userExists( $uid ) ){ OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR); return false; - exit(); } // Set the uid self::$uid = $uid; @@ -140,41 +240,53 @@ class OC_Migrate{ // Path given // Is a directory? if( !is_dir( $path ) ){ - OC_Log::write('migration', 'Path supplied to createExportFile() is not a directory', OC_Log::ERROR); + OC_Log::write('migration', 'Path supplied to createUserExportFile() is not a directory', OC_Log::ERROR); return false; - exit(); } // Is writeable if( !is_writeable( $path ) ){ - OC_Log::write('migration', 'Path supplied to createExportFile() is not writeable', OC_Log::ERROR); + OC_Log::write('migration', 'Path supplied to createUserExportFile() is not writeable', OC_Log::ERROR); return false; - exit(); } self::$zippath = $path . $zipname; } else { // Save in users data dir self::$zippath = $userdatadir . $zipname; } - if (self::$zip->open(self::$zippath, ZIPARCHIVE::CREATE) !== TRUE) { - // TODO ADD LOGGING - exit("Cannot open <$filename>\n"); + // Try to create the zip + if( !self::createZip() ){ + return false; } // Export the app info $info = json_encode( self::exportAppData() ); file_put_contents( $userdatadir . '/exportinfo.json', $info ); // Add the data dir to the zip self::addDirToZip( $userdatadir ); - // All done! - if( !self::$zip->close() ){ - OC_Log::write('migration', 'Failed to save the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); - return false; - exit(); - } else { - OC_Log::write('migration', 'Created export file for: '.self::$uid, OC_Log::INFO); - //return true; + // Close the zip + if( !self::closeZip() ){ + return false; } + // All good return self::$zippath; } + + /** + * @breif tries to create the zip + * @return bool + */ + static private function createZip(){ + // Check if properties are set + if( !self::$zip || !self::$zippath ){ + OC_Log::write('migration', 'createZip() called but $zip and/or $zippath have not been set', OC_Log::ERROR); + return false; + } + if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE ) !== TRUE ) { + OC_Log::write('migration', 'Failed to create the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); + return false; + } else { + return true; + } + } /** * @breif adds a directory to the zip object @@ -235,7 +347,6 @@ class OC_Migrate{ if(!self::$uid){ OC_Log::write('migration','Tried to import without passing a uid',OC_Log::FATAL); return false; - exit(); } // Check if the db exists @@ -243,18 +354,15 @@ class OC_Migrate{ // Connect to the db if(!self::connectDB( $db )){ return false; - exit(); } } else { OC_Log::write('migration','Migration.db not found at: '.$db, OC_Log::FATAL ); return false; - exit(); } if( !is_array( $migrateinfo ) ){ OC_Log::write('migration','$migrateinfo is not an array', OC_Log::FATAL); return false; - exit(); } // Set the user id From cfc41942e443a585e549d194f2411586df18f450 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 16 Mar 2012 16:00:12 +0100 Subject: [PATCH 035/302] make Files settings work --- files/admin.php | 8 ++++---- files/templates/admin.php | 3 +++ lib/app.php | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/files/admin.php b/files/admin.php index 861b6037f3..ea846202ab 100644 --- a/files/admin.php +++ b/files/admin.php @@ -25,7 +25,7 @@ // Init owncloud require_once('../lib/base.php'); -OC_User::checkAdminUser(); +OC_Util::checkAdminUser(); $htaccessWorking=(getenv('htaccessWorking')=='true'); if(isset($_POST['maxUploadSize'])){ @@ -38,10 +38,10 @@ if(isset($_POST['maxUploadSize'])){ } OC_App::setActiveNavigationEntry( "files_administration" ); -// return template -$tmpl = new OC_Template( "files", "admin", "user" ); + +$tmpl = new OC_Template( 'files', 'admin' ); $tmpl->assign( 'htaccessWorking', $htaccessWorking ); $tmpl->assign( 'uploadMaxFilesize', $maxUploadFilesize); -$tmpl->printPage(); +return $tmpl->fetchPage(); ?> diff --git a/files/templates/admin.php b/files/templates/admin.php index 0122865ee7..eb65544704 100644 --- a/files/templates/admin.php +++ b/files/templates/admin.php @@ -1,10 +1,13 @@
+
+ t('File handling');?> '/>
No settings currently available. +
diff --git a/lib/app.php b/lib/app.php index f841180eba..64cbe8894e 100644 --- a/lib/app.php +++ b/lib/app.php @@ -325,6 +325,7 @@ class OC_App{ $source=self::$settingsForms; break; case 'admin': + $forms[] = include 'files/admin.php'; //hardcode own apps $source=self::$adminForms; break; case 'personal': @@ -371,7 +372,7 @@ class OC_App{ } return $apps; } - + /** * check if any apps need updating and update those */ @@ -390,7 +391,7 @@ class OC_App{ } } } - + /** * update the database for the app and call the update script * @param string appid From a77edf88c6da02456569b2810830c19f7d2648b3 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 16 Mar 2012 16:25:15 +0100 Subject: [PATCH 036/302] check if selected files for zip archive are not too large offer config option --- files/admin.php | 18 ++++++++++------ files/templates/admin.php | 11 +++++----- lib/files.php | 44 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/files/admin.php b/files/admin.php index ea846202ab..7e410652cf 100644 --- a/files/admin.php +++ b/files/admin.php @@ -28,13 +28,20 @@ require_once('../lib/base.php'); OC_Util::checkAdminUser(); $htaccessWorking=(getenv('htaccessWorking')=='true'); -if(isset($_POST['maxUploadSize'])){ - $maxUploadFilesize=$_POST['maxUploadSize']; - OC_Files::setUploadLimit(OC_Helper::computerFileSize($maxUploadFilesize)); +if($_POST) { + if(isset($_POST['maxUploadSize'])){ + $maxUploadFilesize=$_POST['maxUploadSize']; + OC_Files::setUploadLimit(OC_Helper::computerFileSize($maxUploadFilesize)); + } + if(isset($_POST['maxZipInputSize'])) { + $maxZipInputSize=$_POST['maxZipInputSize']; + OC_Preferences::setValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize($maxZipInputSize)); + } }else{ $upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); $post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); $maxUploadFilesize = min($upload_max_filesize, $post_max_size); + $maxZipInputSize = OC_Helper::humanfilesize(OC_Preferences::getValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize('800 MB'))); } OC_App::setActiveNavigationEntry( "files_administration" ); @@ -42,6 +49,5 @@ OC_App::setActiveNavigationEntry( "files_administration" ); $tmpl = new OC_Template( 'files', 'admin' ); $tmpl->assign( 'htaccessWorking', $htaccessWorking ); $tmpl->assign( 'uploadMaxFilesize', $maxUploadFilesize); -return $tmpl->fetchPage(); - -?> +$tmpl->assign( 'maxZipInputSize', $maxZipInputSize); +return $tmpl->fetchPage(); \ No newline at end of file diff --git a/files/templates/admin.php b/files/templates/admin.php index eb65544704..8c3ba56ad5 100644 --- a/files/templates/admin.php +++ b/files/templates/admin.php @@ -3,11 +3,10 @@
t('File handling');?> - - '/>
- - - No settings currently available. - + + '/>
+ + '/>
+
diff --git a/lib/files.php b/lib/files.php index 1f8331afb2..50223df1d3 100644 --- a/lib/files.php +++ b/lib/files.php @@ -59,6 +59,9 @@ class OC_Files { } if(is_array($files)){ + self::checkZipInputSize($dir,$files); + $executionTime = intval(ini_get('max_execution_time')); + set_time_limit(0); $zip = new ZipArchive(); $filename = get_temp_dir()."/ownCloud.zip"; if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { @@ -75,7 +78,11 @@ class OC_Files { } } $zip->close(); + set_time_limit($executionTime); }elseif(OC_Filesystem::is_dir($dir.'/'.$files)){ + self::checkZipInputSize($dir,$files); + $executionTime = intval(ini_get('max_execution_time')); + set_time_limit(0); $zip = new ZipArchive(); $filename = get_temp_dir()."/ownCloud.zip"; if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { @@ -84,6 +91,7 @@ class OC_Files { $file=$dir.'/'.$files; self::zipAddDir($file,$zip); $zip->close(); + set_time_limit($executionTime); }else{ $zip=false; $filename=$dir.'/'.$files; @@ -209,6 +217,40 @@ class OC_Files { } } + /** + * checks if the selected files are within the size constraint. If not, outputs an error page. + * + * @param dir $dir + * @param files $files + */ + static function checkZipInputSize($dir, $files) { + $zipLimit = OC_Preferences::getValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize('800 MB')); + if($zipLimit > 0) { + $totalsize = 0; + if(is_array($files)){ + foreach($files as $file){ + $totalsize += OC_Filesystem::filesize($dir.'/'.$file); + } + }else{ + $totalsize += OC_Filesystem::filesize($dir.'/'.$files); + } + if($totalsize > $zipLimit) { + $l = new OC_L10N('files'); + header("HTTP/1.0 409 Conflict"); + $tmpl = new OC_Template( '', 'error', 'user' ); + $errors = array( + array( + 'error' => $l->t('Selected files too large to generate zip file.'), + 'hint' => 'Download the files in smaller chunks, seperately or kindly ask your administrator.
' . $l->t('Back to Files') . '', + ) + ); + $tmpl->assign('errors', $errors); + $tmpl->printPage(); + exit; + } + } + } + /** * try to detect the mime type of a file * @@ -256,7 +298,7 @@ class OC_Files { return false; } } - + /** * set the maximum upload size limit for apache hosts using .htaccess * @param int size filesisze in bytes From 3267d91bdaed42633656723ec4e239d1fe460824 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 16 Mar 2012 16:25:41 +0100 Subject: [PATCH 037/302] it may take time for zip file download, so give a message --- files/js/files.js | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/files/js/files.js b/files/js/files.js index a678e12cc2..539d559889 100644 --- a/files/js/files.js +++ b/files/js/files.js @@ -3,11 +3,11 @@ $(document).ready(function() { //little hack to set unescape filenames in attribute $(this).attr('data-file',decodeURIComponent($(this).attr('data-file'))); }); - + if($('tr[data-file]').length==0){ $('.file_upload_filename').addClass('highlight'); } - + $('#file_action_panel').attr('activeAction', false); //drag/drop of files @@ -16,7 +16,7 @@ $(document).ready(function() { $('div.crumb').droppable(crumbDropOptions); $('ul#apps>li:first-child').data('dir',''); $('ul#apps>li:first-child').droppable(crumbDropOptions); - + // Triggers invisible file input $('.file_upload_button_wrapper').live('click', function() { $(this).parent().children('.file_upload_start').trigger('click'); @@ -81,9 +81,9 @@ $(document).ready(function() { } } } - + }); - + // Sets the select_all checkbox behaviour : $('#select_all').click(function() { if($(this).attr('checked')){ @@ -97,7 +97,7 @@ $(document).ready(function() { } procesSelection(); }); - + $('td.filename input:checkbox').live('click',function(event) { if (event.shiftKey) { var last = $(lastChecked).parent().parent().prevAll().length; @@ -126,23 +126,22 @@ $(document).ready(function() { } procesSelection(); }); - + $('#file_newfolder_name').click(function(){ if($('#file_newfolder_name').val() == 'New Folder'){ $('#file_newfolder_name').val(''); } }); - + $('.download').click('click',function(event) { var files=getSelectedFiles('name').join(';'); - - //send the browser to the download location var dir=$('#dir').val()||'/'; -// alert(files); + $('#notification').text(t('files','generating ZIP-file, it may take some time.')); + $('#notification').fadeIn(); window.location='ajax/download.php?files='+encodeURIComponent(files)+'&dir='+encodeURIComponent(dir); return false; }); - + $('.delete').click(function(event) { var files=getSelectedFiles('name'); event.preventDefault(); @@ -228,7 +227,7 @@ $(document).ready(function() { form.hide(); } }); - + //add multiply file upload attribute to all browsers except konqueror (which crashes when it's used) if(navigator.userAgent.search(/konqueror/i)==-1){ $('.file_upload_start').attr('multiple','multiple') @@ -255,7 +254,7 @@ $(document).ready(function() { text=text.substr(0,text.length-6)+'...'; crumb.text(text); } - + $(window).click(function(){ $('#new>ul').hide(); $('#new').removeClass('active'); @@ -279,14 +278,14 @@ $(document).ready(function() { if($(this).children('p').length==0){ return; } - + $('#new li').each(function(i,element){ if($(element).children('p').length==0){ $(element).children('input').remove(); $(element).append('

'+$(element).data('text')+'

'); } }); - + var type=$(this).data('type'); var text=$(this).children('p').text(); $(this).data('text',text); @@ -348,7 +347,7 @@ $(document).ready(function() { tr.find('td.filename').attr('style','background-image:url('+path+')'); }); }else{ - + } } ); From c442a06a0217afa8ff284333b0560aeb87db7a55 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 16 Mar 2012 21:09:36 +0000 Subject: [PATCH 038/302] Fix export for admin and users. Added 3 admin export types --- apps/admin_export/settings.php | 9 +- apps/admin_export/templates/settings.php | 2 +- apps/user_migrate/settings.php | 2 +- lib/migrate.php | 144 +++++++++++++---------- 4 files changed, 90 insertions(+), 67 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 9db1d75db9..33fca26630 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -40,12 +40,12 @@ if (isset($_POST['admin_export'])) { header("Content-Disposition: attachment; filename=" . basename($path)); header("Content-Length: " . filesize($path)); @ob_end_clean(); - readfile($path); - OC_Migrate::cleanUp( $path ); + readfile( $path ); + unlink( $path ); } // Import? } else if( isset($_POST['admin_import']) ){ - + /* $root = OC::$SERVERROOT . "/"; $importname = "owncloud_import_" . date("y-m-d_H-i-s"); @@ -85,7 +85,8 @@ if (isset($_POST['admin_export'])) { exit(); } - OC_DB::replaceDB( get_temp_dir() . '/' . $importname . '/dbexport.xml' ); + OC_DB::replaceDB( get_temp_dir() . '/' . $importname . '/dbexport.xml' ); + */ } else { // fill template $tmpl = new OC_Template('admin_export', 'settings'); diff --git a/apps/admin_export/templates/settings.php b/apps/admin_export/templates/settings.php index 15a19b7c63..6d848048c4 100644 --- a/apps/admin_export/templates/settings.php +++ b/apps/admin_export/templates/settings.php @@ -8,7 +8,7 @@

ownCloud instance ( suitable for import )
ownCloud system files
- Just user files + Just user files
diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index d862ac5a82..2d200c0f76 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -36,7 +36,7 @@ if (isset($_POST['user_export'])) { header("Content-Length: " . filesize($path)); @ob_end_clean(); readfile($path); - OC_Migrate::cleanUp( $path ); + unlink( $path ); } } if( isset( $_POST['user_import'] ) ){ // TODO diff --git a/lib/migrate.php b/lib/migrate.php index 8f26ea7ae6..522d8da843 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -40,6 +40,10 @@ class OC_Migrate{ static private $zip=false; // String path to export static private $zippath=false; + // Stores the type of export + static private $exporttype=false; + // Array of temp files to be deleted after zip creation + static private $tmpfiles=array(); /** * register a new migration provider @@ -138,14 +142,7 @@ class OC_Migrate{ self::$zippath = $path . $zipname; } else { // Save in tmp dir - $structure = sys_get_temp_dir() . '/owncloudexports/'; - if( !file_exists( $structure ) ){ - if ( !mkdir( $structure ) ) { - OC_Log::write('migration', 'Could not create the temporary export at: '.$structure, OC_Log::ERROR); - return false; - } - } - self::$zippath = $structure . $zipname; + self::$zippath = sys_get_temp_dir() . '/' . $zipname; } // Create the zip object self::$zip = new ZipArchive; @@ -154,42 +151,48 @@ class OC_Migrate{ return false; } // Handle export types - if( $exporttype == 'instance' ){ - // Creates a zip that is compatable with the import function - /* - $dbfile = self:: . "/dbexport.xml"; - OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); - - // Now add in *dbname* and *dbtableprefix* - $dbexport = file_get_contents( $dbfile ); - - $dbnamestring = "\n\n " . OC_Config::getValue( "dbname", "owncloud" ); - $dbtableprefixstring = "

\n\n " . OC_Config::getValue( "dbtableprefix", "_oc" ); - - $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); - $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbprefix*", $dbexport ); - - // Write the new db export file - file_put_contents( $dbfile, $dbexport ); - - $zip->addFile($dbfile, "dbexport.xml"); - */ - } else if( $exporttype == 'system' ){ - // Creates a zip with the owncloud system files - self::addDirToZip( OC::$SERVERROOT . '/', false); - foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dir) { - self::addDirToZip( OC::$SERVERROOT . '/' . $dir, true, "/"); - } - } else if ( $exporttype == 'userfiles' ){ - // Creates a zip with all of the users files - foreach(OC_User::getUsers() as $user){ - self::addDirToZip( $datadir . '/' . $user . '/', true, "/" . $user); - } - } else { - // Invalid export type supplied - OC_Log::write('migration', 'Invalid export type supplied to createSysExportFile() "'.$exporttype.'"', OC_Log::ERROR); + $exporttypes = array( 'userfiles', 'instance', 'system' ); + self::$exporttype = in_array( $exporttype, $exporttypes ) ? $exporttype : false; + if( !self::$exporttype ){ + OC_Log::write( 'migration', 'Export type: '.$exporttype.' is not supported.', OC_Log::ERROR); return false; } + switch( self::$exporttype ){ + case 'instance': + // Creates a zip that is compatable with the import function + $dbfile = tempnam( "/tmp", "owncloud_export_data_" ); + OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); + + // Now add in *dbname* and *dbprefix* + $dbexport = file_get_contents( $dbfile ); + $dbnamestring = "\n\n " . OC_Config::getValue( "dbname", "owncloud" ); + $dbtableprefixstring = "
\n\n " . OC_Config::getValue( "dbtableprefix", "oc_" ); + $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); + $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbprefix*", $dbexport ); + // Write the new db export file + file_put_contents( $dbfile, $dbexport ); + self::$zip->addFile( $dbfile, "dbexport.xml" ); + // Add user data + foreach(OC_User::getUsers() as $user){ + self::addDirToZip( $datadir . '/' . $user . '/', true, "/userdata/" ); + } + break; + case 'userfiles': + // Creates a zip with all of the users files + foreach(OC_User::getUsers() as $user){ + self::addDirToZip( $datadir . '/' . $user . '/', true, "/" ); + } + break; + case 'system': + // Creates a zip with the owncloud system files + self::addDirToZip( OC::$SERVERROOT . '/', false, '/'); + foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dir) { + self::addDirToZip( OC::$SERVERROOT . '/' . $dir, true, "/"); + } + break; + } + // Add export info + self::addExportInfo(); // Close the zip if( !self::closeZip() ){ return false; @@ -198,6 +201,30 @@ class OC_Migrate{ } + /** + * @breif adds a json file with infomation on the export to the zips root (used on import) + * @return bool + */ + static private function addExportInfo(){ + $info = array( + 'ocversion' => OC_Util::getVersion(), + 'exporttime' => time(), + 'exportedby' => OC_User::getUser(), + 'exporttype' => self::$exporttype + ); + // Create json + $json = json_encode( $info ); + $tmpfile = tempnam("/tmp", "oc_export_info_"); + self::$tmpfiles[] = $tmpfile; + if( !file_put_contents( $tmpfile, $json ) ){ + return false; + } else { + self::$zip->addFile( $tmpfile, "export_info.json" ); + return true; + } + } + + /** * @breif tried to finalise the zip * @return bool @@ -205,13 +232,25 @@ class OC_Migrate{ static private function closeZip(){ if( !self::$zip->close() ){ OC_Log::write('migration', 'Failed to save the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); + self::cleanup(); return false; } else { - OC_Log::write('migration', 'Created export file for: '.self::$uid, OC_Log::INFO); + OC_Log::write('migration', 'Export zip created ok', OC_Log::INFO); + self::cleanup(); return true; } } + /** + * @breif cleans up after the zip + */ + static private function cleanup(){ + // Delete tmp files + foreach(self::$tmpfiles as $i){ + unlink( $i ); + } + } + /** * @breif creates a zip user export * @param optional $uid string user id of the user to export (defaults to current) @@ -647,22 +686,5 @@ class OC_Migrate{ return $result ? true : false; } - - /** - * @breif removes migration.db and exportinfo.json from the users data dir - * @param optional $path string path to the export zip to delete - * @return void - */ - static public function cleanUp( $path=null ){ - $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . self::$uid; - // Remove migration.db - unlink( $userdatadir . '/migration.db' ); - // Remove exportinfo.json - unlink( $userdatadir . '/exportinfo.json' ); - // Remove the zip - if( !is_null( $path ) ){ - unlink( $path ); - } - return true; - } + } From 5332c319a2140563478d83047d9f717c0d3e179f Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 16 Mar 2012 22:50:35 +0000 Subject: [PATCH 039/302] Migration info is an object. Other fixes --- apps/bookmarks/lib/migrate.php | 6 +++--- lib/migrate.php | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 4f11bc5bde..46b68ad5ba 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -34,16 +34,16 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ // Import function for bookmarks function import( $info ){ - switch( $info['appversion'] ){ + switch( $info->appversion ){ default: // All versions of the app have had the same db structure, so all can use the same import function $query = OC_Migrate::prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); - $results = $query->execute( array( $info['olduid'] ) ); + $results = $query->execute( array( $info->olduid ) ); $idmap = array(); while( $row = $data->fetchRow() ){ // Import each bookmark, saving its id into the map $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks(url, title, user_id, public, added, lastmodified) VALUES (?, ?, ?, ?, ?, ?)" ); - $query->execute( array( $row['url'], $row['title'], $info['newuid'], $row['public'], $row['added'], $row['lastmodified'] ) ); + $query->execute( array( $row['url'], $row['title'], $info->newuid, $row['public'], $row['added'], $row['lastmodified'] ) ); // Map the id $idmap[$row['id']] = OC_DB::insertid(); } diff --git a/lib/migrate.php b/lib/migrate.php index 522d8da843..28c36e9616 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -377,11 +377,11 @@ class OC_Migrate{ /** * @breif imports a new user * @param $db string path to migration.db - * @param $migrateinfo array of migration ino + * @param $info array of migration ino * @param $uid optional uid to use * @return bool if the import succedded */ - public static function importAppData( $db, $migrateinfo, $uid=false ){ + public static function importAppData( $db, $info, $uid=false ){ if(!self::$uid){ OC_Log::write('migration','Tried to import without passing a uid',OC_Log::FATAL); @@ -399,15 +399,15 @@ class OC_Migrate{ return false; } - if( !is_array( $migrateinfo ) ){ + if( !is_array( $info ) ){ OC_Log::write('migration','$migrateinfo is not an array', OC_Log::FATAL); return false; } // Set the user id - self::$uid = $info['migrateinfo']['uid']; + self::$uid = $info->migrateinfo->uid; - $apps = $info['apps']; + $apps = $info->app; foreach( self::$providers as $provider){ // Is the app in the export? @@ -415,7 +415,7 @@ class OC_Migrate{ // Did it succeed? if( $app[$provider->id] ){ // Then do the import - $provider->import(); + $provider->import( $info ); } } } From 222bb2303fb0825b4e279024ff723db84e23153d Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 16 Mar 2012 22:54:37 +0000 Subject: [PATCH 040/302] Added prototype of user import --- apps/admin_export/settings.php | 4 +- apps/admin_export/templates/settings.php | 2 +- apps/user_migrate/settings.php | 117 +++++++++++++++++++++++ 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 33fca26630..e7de74f758 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -45,7 +45,7 @@ if (isset($_POST['admin_export'])) { } // Import? } else if( isset($_POST['admin_import']) ){ - /* + $root = OC::$SERVERROOT . "/"; $importname = "owncloud_import_" . date("y-m-d_H-i-s"); @@ -86,7 +86,7 @@ if (isset($_POST['admin_export'])) { } OC_DB::replaceDB( get_temp_dir() . '/' . $importname . '/dbexport.xml' ); - */ + } else { // fill template $tmpl = new OC_Template('admin_export', 'settings'); diff --git a/apps/admin_export/templates/settings.php b/apps/admin_export/templates/settings.php index 6d848048c4..36eec84d48 100644 --- a/apps/admin_export/templates/settings.php +++ b/apps/admin_export/templates/settings.php @@ -14,7 +14,7 @@
- t('Import an ownCloud instance THIS WILL DELETE ALL CURRENT OWNCLOUD DATA');?> + t('Import an ownCloud instance. THIS WILL DELETE ALL CURRENT OWNCLOUD DATA');?>

t('All current ownCloud data will be replaced by the ownCloud instance that is uploaded.');?>

diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 2d200c0f76..3efe9228a1 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -40,10 +40,127 @@ if (isset($_POST['user_export'])) { } } if( isset( $_POST['user_import'] ) ){ // TODO + $root = OC::$SERVERROOT . "/"; + $importname = "owncloud_import_" . date("y-m-d_H-i-s"); + + // Save data dir for later + $datadir = OC_Config::getValue( 'datadirectory' ); + + // Copy the uploaded file + $from = $_FILES['owncloud_import']['tmp_name']; + $to = get_temp_dir().'/'.$importname.'.zip'; + if( !move_uploaded_file( $from, $to ) ){ + OC_Log::write('admin_export',"Failed to copy the uploaded file",OC_Log::INFO); + exit(); + } + + // Extract zip + $zip = new ZipArchive(); + if ($zip->open(get_temp_dir().'/'.$importname.'.zip') != TRUE) { + OC_Log::write('admin_export',"Failed to open zip file",OC_Log::INFO); + exit(); + } + $zip->extractTo(get_temp_dir().'/'.$importname.'/'); + $zip->close(); + + $importdir = get_temp_dir() . '/' . $importname; + + // Delete uploaded file + unlink( $importdir . '.zip' ); + + // Find folder + $files = scandir( $importdir ); + unset($files[0]); + unset($files[1]); + + // Get the user + if( count($files) != 1 ){ + OC_Log::write('migration', 'Invalid import file', OC_Log::ERROR); + die('invalid import'); + } + + $user = reset($files); + + // Check for dbexport.xml and export info and data dir + $files = scandir( $importdir . '/' . $user ); + $required = array( 'migration.db', 'exportinfo.json', 'files'); + foreach($required as $require){ + if( !in_array( $require, $files) ){ + OC_Log::write('migration', 'Invlaid import file', OC_Log::ERROR); + die('invalid import'); + } + } + + $migrateinfo = $importdir . '/' . $user . '/exportinfo.json'; + $migrateinfo = json_decode( file_get_contents( $migrateinfo ) ); + $olduid = $migrateinfo->migrateinfo->uid; + + // Check if uid is available + if( OC_User::UserExists( $olduid ) ){ + OC_Log::write('migration','Username exists', OC_Log::ERROR); + die('user exists'); + } + + // Create the user + if( !OC_Migrate::createUser( $olduid, $migrateinfo->migrateinfo->hash ) ){ + OC_Log::write('migration', 'Failed to create the new user', OC_Log::ERROR); + die('coundlt create new user'); + } + + $datadir = OC_Config::getValue( 'datadirectory' ); + // Copy data + if( !copy_r( $importdir . '/files', $datadir . '/' ) ){ + OC_Log::write('migration','Failed to copy user files to destination', OC_Log::ERROR); + die('failed to copy user files'); + } + + // Import user data + if( !OC_Migrate::importUser( $importdir . '/migration.db', $migrateinfo ) ){ + OC_Log::write('migration','Failed to import user data', OC_Log::ERROR); + die('failed to import user data'); + } + + // All done! + die('done'); + } else { // fill template $tmpl = new OC_Template('user_migrate', 'settings'); return $tmpl->fetchPage(); } +function copy_r( $path, $dest ) + { + if( is_dir($path) ) + { + @mkdir( $dest ); + $objects = scandir($path); + if( sizeof($objects) > 0 ) + { + foreach( $objects as $file ) + { + if( $file == "." || $file == ".." ) + continue; + // go on + if( is_dir( $path.DS.$file ) ) + { + copy_r( $path.DS.$file, $dest.DS.$file ); + } + else + { + copy( $path.DS.$file, $dest.DS.$file ); + } + } + } + return true; + } + elseif( is_file($path) ) + { + return copy($path, $dest); + } + else + { + return false; + } + } From 8e22823501e702727daae4b6cd25fb81965c6f83 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sat, 17 Mar 2012 13:30:18 +0100 Subject: [PATCH 041/302] bredcrumb fix --- apps/gallery/lib/scanner.php | 1 - apps/gallery/templates/index.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/gallery/lib/scanner.php b/apps/gallery/lib/scanner.php index 5791662522..929f8b7a2e 100644 --- a/apps/gallery/lib/scanner.php +++ b/apps/gallery/lib/scanner.php @@ -131,4 +131,3 @@ class OC_Gallery_Scanner { } } -?> diff --git a/apps/gallery/templates/index.php b/apps/gallery/templates/index.php index 54fbcfd015..dc5852733b 100644 --- a/apps/gallery/templates/index.php +++ b/apps/gallery/templates/index.php @@ -18,7 +18,7 @@ $l = new OC_L10N('gallery');

-
+
From 122f7e4c2f4a95f904d052059e81ac855fbd2c3c Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 17 Mar 2012 12:41:16 +0000 Subject: [PATCH 042/302] Dont truncate the time format --- apps/calendar/templates/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/calendar/templates/settings.php b/apps/calendar/templates/settings.php index f74a45203e..fb2a04a649 100644 --- a/apps/calendar/templates/settings.php +++ b/apps/calendar/templates/settings.php @@ -31,7 +31,7 @@
" } content += "
- " name="timeformat"> From 7ba8dbb92041d01ca70fca105897d3c374502343 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 17 Mar 2012 12:51:11 +0000 Subject: [PATCH 043/302] Don't truncate users email --- settings/css/settings.css | 1 + 1 file changed, 1 insertion(+) diff --git a/settings/css/settings.css b/settings/css/settings.css index e80de0f1ad..42576953d0 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -5,6 +5,7 @@ input#openid, input#webdav { width:20em; } #passworderror { display:none; } #passwordchanged { display:none; } input#identity { width:20em; } +#email { width: 17em; } .msg.success{ color:#fff; background-color:#0f0; padding:3px; text-shadow:1px 1px #000; } .msg.error{ color:#fff; background-color:#f00; padding:3px; text-shadow:1px 1px #000; } From 27bf34f7be0e7c3c1516cdf6c44877ee77962dca Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 17 Mar 2012 13:30:58 +0000 Subject: [PATCH 044/302] Move user import to the admin --- apps/user_migrate/admin.php | 118 ++++++++++++++++++++++ apps/user_migrate/appinfo/app.php | 8 ++ apps/user_migrate/settings.php | 123 +---------------------- apps/user_migrate/templates/admin.php | 9 ++ apps/user_migrate/templates/settings.php | 11 +- 5 files changed, 137 insertions(+), 132 deletions(-) create mode 100644 apps/user_migrate/admin.php create mode 100644 apps/user_migrate/templates/admin.php diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php new file mode 100644 index 0000000000..56fe887514 --- /dev/null +++ b/apps/user_migrate/admin.php @@ -0,0 +1,118 @@ +. + * + */ +OC_Util::checkAdminUser(); +OC_Util::checkAppEnabled('user_migrate'); + +// Import? +if (isset($_POST['userimport'])) { + + $root = OC::$SERVERROOT . "/"; + $importname = "owncloud_import_" . date("y-m-d_H-i-s"); + + // Save data dir for later + $datadir = OC_Config::getValue( 'datadirectory' ); + + // Copy the uploaded file + $from = $_FILES['owncloud_import']['tmp_name']; + $to = get_temp_dir().'/'.$importname.'.zip'; + if( !move_uploaded_file( $from, $to ) ){ + OC_Log::write('admin_export',"Failed to copy the uploaded file",OC_Log::INFO); + exit(); + } + + // Extract zip + $zip = new ZipArchive(); + if ($zip->open(get_temp_dir().'/'.$importname.'.zip') != TRUE) { + OC_Log::write('admin_export',"Failed to open zip file",OC_Log::INFO); + exit(); + } + $zip->extractTo(get_temp_dir().'/'.$importname.'/'); + $zip->close(); + + $importdir = get_temp_dir() . '/' . $importname; + + // Delete uploaded file + unlink( $importdir . '.zip' ); + + // Find folder + $files = scandir( $importdir ); + unset($files[0]); + unset($files[1]); + + // Get the user + if( count($files) != 1 ){ + OC_Log::write('migration', 'Invalid import file', OC_Log::ERROR); + die('invalid import'); + } + + $user = reset($files); + + // Check for dbexport.xml and export info and data dir + $files = scandir( $importdir . '/' . $user ); + $required = array( 'migration.db', 'exportinfo.json', 'files'); + foreach($required as $require){ + if( !in_array( $require, $files) ){ + OC_Log::write('migration', 'Invlaid import file', OC_Log::ERROR); + die('invalid import'); + } + } + + $migrateinfo = $importdir . '/' . $user . '/exportinfo.json'; + $migrateinfo = json_decode( file_get_contents( $migrateinfo ) ); + $olduid = $migrateinfo->migrateinfo->uid; + + // Check if uid is available + if( OC_User::UserExists( $olduid ) ){ + OC_Log::write('migration','Username exists', OC_Log::ERROR); + die('user exists'); + } + + // Create the user + if( !OC_Migrate::createUser( $olduid, $migrateinfo->migrateinfo->hash ) ){ + OC_Log::write('migration', 'Failed to create the new user', OC_Log::ERROR); + die('coundlt create new user'); + } + + $datadir = OC_Config::getValue( 'datadirectory' ); + // Copy data + if( !copy_r( $importdir . '/files', $datadir . '/' ) ){ + OC_Log::write('migration','Failed to copy user files to destination', OC_Log::ERROR); + die('failed to copy user files'); + } + + // Import user data + if( !OC_Migrate::importUser( $importdir . '/migration.db', $migrateinfo ) ){ + OC_Log::write('migration','Failed to import user data', OC_Log::ERROR); + die('failed to import user data'); + } + + // All done! + die('done'); + +} else { +// fill template + $tmpl = new OC_Template('user_migrate', 'admin'); + return $tmpl->fetchPage(); +} \ No newline at end of file diff --git a/apps/user_migrate/appinfo/app.php b/apps/user_migrate/appinfo/app.php index 4a795a5474..18b97b93df 100644 --- a/apps/user_migrate/appinfo/app.php +++ b/apps/user_migrate/appinfo/app.php @@ -22,5 +22,13 @@ */ OC_APP::registerPersonal('user_migrate','settings'); +OC_APP::registerAdmin('user_migrate','admin'); +// add settings page to navigation +$entry = array( + 'id' => "user_migrate_settings", + 'order'=>1, + 'href' => OC_Helper::linkTo( "user_migrate", "admin.php" ), + 'name' => 'Import' +); ?> \ No newline at end of file diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 3efe9228a1..62f5e3f20d 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -38,129 +38,8 @@ if (isset($_POST['user_export'])) { readfile($path); unlink( $path ); } -} if( isset( $_POST['user_import'] ) ){ - // TODO - $root = OC::$SERVERROOT . "/"; - $importname = "owncloud_import_" . date("y-m-d_H-i-s"); - - // Save data dir for later - $datadir = OC_Config::getValue( 'datadirectory' ); - - // Copy the uploaded file - $from = $_FILES['owncloud_import']['tmp_name']; - $to = get_temp_dir().'/'.$importname.'.zip'; - if( !move_uploaded_file( $from, $to ) ){ - OC_Log::write('admin_export',"Failed to copy the uploaded file",OC_Log::INFO); - exit(); - } - - // Extract zip - $zip = new ZipArchive(); - if ($zip->open(get_temp_dir().'/'.$importname.'.zip') != TRUE) { - OC_Log::write('admin_export',"Failed to open zip file",OC_Log::INFO); - exit(); - } - $zip->extractTo(get_temp_dir().'/'.$importname.'/'); - $zip->close(); - - $importdir = get_temp_dir() . '/' . $importname; - - // Delete uploaded file - unlink( $importdir . '.zip' ); - - // Find folder - $files = scandir( $importdir ); - unset($files[0]); - unset($files[1]); - - // Get the user - if( count($files) != 1 ){ - OC_Log::write('migration', 'Invalid import file', OC_Log::ERROR); - die('invalid import'); - } - - $user = reset($files); - - // Check for dbexport.xml and export info and data dir - $files = scandir( $importdir . '/' . $user ); - $required = array( 'migration.db', 'exportinfo.json', 'files'); - foreach($required as $require){ - if( !in_array( $require, $files) ){ - OC_Log::write('migration', 'Invlaid import file', OC_Log::ERROR); - die('invalid import'); - } - } - - $migrateinfo = $importdir . '/' . $user . '/exportinfo.json'; - $migrateinfo = json_decode( file_get_contents( $migrateinfo ) ); - $olduid = $migrateinfo->migrateinfo->uid; - - // Check if uid is available - if( OC_User::UserExists( $olduid ) ){ - OC_Log::write('migration','Username exists', OC_Log::ERROR); - die('user exists'); - } - - // Create the user - if( !OC_Migrate::createUser( $olduid, $migrateinfo->migrateinfo->hash ) ){ - OC_Log::write('migration', 'Failed to create the new user', OC_Log::ERROR); - die('coundlt create new user'); - } - - $datadir = OC_Config::getValue( 'datadirectory' ); - // Copy data - if( !copy_r( $importdir . '/files', $datadir . '/' ) ){ - OC_Log::write('migration','Failed to copy user files to destination', OC_Log::ERROR); - die('failed to copy user files'); - } - - // Import user data - if( !OC_Migrate::importUser( $importdir . '/migration.db', $migrateinfo ) ){ - OC_Log::write('migration','Failed to import user data', OC_Log::ERROR); - die('failed to import user data'); - } - - // All done! - die('done'); - } else { // fill template $tmpl = new OC_Template('user_migrate', 'settings'); return $tmpl->fetchPage(); -} - -function copy_r( $path, $dest ) - { - if( is_dir($path) ) - { - @mkdir( $dest ); - $objects = scandir($path); - if( sizeof($objects) > 0 ) - { - foreach( $objects as $file ) - { - if( $file == "." || $file == ".." ) - continue; - // go on - if( is_dir( $path.DS.$file ) ) - { - copy_r( $path.DS.$file, $dest.DS.$file ); - } - else - { - copy( $path.DS.$file, $dest.DS.$file ); - } - } - } - return true; - } - elseif( is_file($path) ) - { - return copy($path, $dest); - } - else - { - return false; - } - } - +} \ No newline at end of file diff --git a/apps/user_migrate/templates/admin.php b/apps/user_migrate/templates/admin.php new file mode 100644 index 0000000000..b5a9951841 --- /dev/null +++ b/apps/user_migrate/templates/admin.php @@ -0,0 +1,9 @@ + +
+ t('Import user account');?> +

+

+

+ +
+ diff --git a/apps/user_migrate/templates/settings.php b/apps/user_migrate/templates/settings.php index 59a27a926d..389de563a6 100644 --- a/apps/user_migrate/templates/settings.php +++ b/apps/user_migrate/templates/settings.php @@ -5,13 +5,4 @@

- -
-
- t('Import user account');?> -

-

-

- -
-
+ \ No newline at end of file From 5234e66bab0ebc6fd7eeef8170cf9f61f035124d Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 17 Mar 2012 13:53:00 +0000 Subject: [PATCH 045/302] Add exportinfo to user exports. --- apps/user_migrate/admin.php | 10 ++++------ lib/migrate.php | 37 +++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index 56fe887514..da2e53d2a1 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -1,10 +1,8 @@ open(get_temp_dir().'/'.$importname.'.zip') != TRUE) { - OC_Log::write('admin_export',"Failed to open zip file",OC_Log::INFO); + OC_Log::write('migration',"Failed to open zip file",OC_Log::INFO); exit(); } $zip->extractTo(get_temp_dir().'/'.$importname.'/'); diff --git a/lib/migrate.php b/lib/migrate.php index 28c36e9616..f5fb808f17 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -84,7 +84,7 @@ class OC_Migrate{ if( is_array( $tables ) ){ // Save the table names foreach($tables as $table){ - $return['app'][$provider->id]['tables'][] = $table; + $return['apps'][$provider->id]['tables'][] = $table; } } else { // It failed to create the tables @@ -94,22 +94,17 @@ class OC_Migrate{ // Run the import function? if( !$failed ){ - $return['app'][$provider->id]['success'] = $provider->export( self::$uid ); + $return['apps'][$provider->id]['success'] = $provider->export( self::$uid ); } else { - $return['app'][$provider->id]['success'] = false; - $return['app'][$provider->id]['message'] = 'failed to create the app tables'; + $return['apps'][$provider->id]['success'] = false; + $return['apps'][$provider->id]['message'] = 'failed to create the app tables'; } // Now add some app info the the return array $appinfo = OC_App::getAppInfo( $provider->id ); - $return['app'][$provider->id]['version'] = $appinfo['version']; + $return['apps'][$provider->id]['version'] = $appinfo['version']; } - - - // Add some general info to the return array - $return['migrateinfo']['uid'] = self::$uid; - $return['migrateinfo']['ocversion'] = OC_Util::getVersionString(); return $return; @@ -205,13 +200,27 @@ class OC_Migrate{ * @breif adds a json file with infomation on the export to the zips root (used on import) * @return bool */ - static private function addExportInfo(){ + static private function addExportInfo( $array=array() ){ $info = array( 'ocversion' => OC_Util::getVersion(), 'exporttime' => time(), 'exportedby' => OC_User::getUser(), 'exporttype' => self::$exporttype ); + // Add hash if user export + if( self::$exporttype = 'user' ){ + $query = OC_DB::prepare( "SELECT password FROM *PREFIX*users WHERE uid LIKE ?" ); + $result = $query->execute( array( self::$uid ) ); + $row = $result->fetchRow(); + $hash = $row ? $row['password'] : false; + if( !$hash ){ + OC_Log::write( 'migration', 'Failed to get the users password hash', OC_log::ERROR); + return false; + } + $info['hash'] = $hash; + } + // Merge in other data + $info = array_merge( $info, $array ); // Create json $json = json_encode( $info ); $tmpfile = tempnam("/tmp", "oc_export_info_"); @@ -297,8 +306,8 @@ class OC_Migrate{ return false; } // Export the app info - $info = json_encode( self::exportAppData() ); - file_put_contents( $userdatadir . '/exportinfo.json', $info ); + $exportinfo = json_encode( self::addExportInfo( self::exportAppData() ) ); + file_put_contents( $userdatadir . '/exportinfo.json', $exportinfo ); // Add the data dir to the zip self::addDirToZip( $userdatadir ); // Close the zip @@ -670,7 +679,7 @@ class OC_Migrate{ // @param $uid string user_id of the user to be created // @param $hash string hash of the user to be created // @return bool result of user creation - private static function createUser( $uid, $hash ){ + public static function createUser( $uid, $hash ){ // Check if userid exists if(OC_User::userExists( $uid )){ From 247b25e7a97fcbe8386c63b1318537e669d40480 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 17 Mar 2012 15:01:08 +0000 Subject: [PATCH 046/302] Fix structure of export zip --- lib/migrate.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/migrate.php b/lib/migrate.php index f5fb808f17..44d28297d4 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -228,7 +228,7 @@ class OC_Migrate{ if( !file_put_contents( $tmpfile, $json ) ){ return false; } else { - self::$zip->addFile( $tmpfile, "export_info.json" ); + self::$zip->addFile( $tmpfile, "/" . self::$uid . "/export_info.json" ); return true; } } @@ -278,6 +278,8 @@ class OC_Migrate{ self::$uid = $uid; // Create the zip object self::$zip = new ZipArchive; + // Set export type + self::$exporttype = 'user'; // Calculate users data dir $user = OC_User::getUser(); $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . $user . '/'; @@ -306,8 +308,9 @@ class OC_Migrate{ return false; } // Export the app info - $exportinfo = json_encode( self::addExportInfo( self::exportAppData() ) ); - file_put_contents( $userdatadir . '/exportinfo.json', $exportinfo ); + $appinfo = self::exportAppData(); + // Save the migration results + self::addExportInfo( $appinfo ); // Add the data dir to the zip self::addDirToZip( $userdatadir ); // Close the zip From bc085c3ff40bd7980bb28a20238bf18a754ffba2 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 17 Mar 2012 16:25:14 +0000 Subject: [PATCH 047/302] Create new user, create new data dir, copy files, import app data --- apps/admin_export/settings.php | 1 - apps/bookmarks/lib/migrate.php | 9 +++-- apps/user_migrate/admin.php | 27 ++++++++------ lib/migrate.php | 65 +++++++++++++++++++--------------- lib/migrate/provider.php | 3 +- 5 files changed, 59 insertions(+), 46 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index e7de74f758..1c98bb552f 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -144,7 +144,6 @@ function unlinkRecursive($dir, $deleteRootToo) return; } - function copy_r( $path, $dest ) { diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 46b68ad5ba..ffc5e9f838 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -32,18 +32,17 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ } // Import function for bookmarks - function import( $info ){ - - switch( $info->appversion ){ + function import( $app, $info ){ + switch( $app->version ){ default: // All versions of the app have had the same db structure, so all can use the same import function $query = OC_Migrate::prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); - $results = $query->execute( array( $info->olduid ) ); + $results = $query->execute( array( $info['olduid'] ) ); $idmap = array(); while( $row = $data->fetchRow() ){ // Import each bookmark, saving its id into the map $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks(url, title, user_id, public, added, lastmodified) VALUES (?, ?, ?, ?, ?, ?)" ); - $query->execute( array( $row['url'], $row['title'], $info->newuid, $row['public'], $row['added'], $row['lastmodified'] ) ); + $query->execute( array( $row['url'], $row['title'], $info['newuid'], $row['public'], $row['added'], $row['lastmodified'] ) ); // Map the id $idmap[$row['id']] = OC_DB::insertid(); } diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index da2e53d2a1..6f3565788e 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -62,14 +62,15 @@ if (isset($_POST['user_import'])) { // Get the user if( count($files) != 1 ){ OC_Log::write('migration', 'Invalid import file', OC_Log::ERROR); - die('invalid import'); + die('invalid import, no user included'); } - $user = reset($files); + $olduser = reset($files); // Check for dbexport.xml and export info and data dir - $files = scandir( $importdir . '/' . $user ); - $required = array( 'migration.db', 'exportinfo.json', 'files'); + $files = scandir( $importdir . '/' . $olduser ); + + $required = array( 'migration.db', 'export_info.json', 'files'); foreach($required as $require){ if( !in_array( $require, $files) ){ OC_Log::write('migration', 'Invlaid import file', OC_Log::ERROR); @@ -77,31 +78,37 @@ if (isset($_POST['user_import'])) { } } - $migrateinfo = $importdir . '/' . $user . '/exportinfo.json'; + $migrateinfo = $importdir . '/' . $olduser . '/export_info.json'; $migrateinfo = json_decode( file_get_contents( $migrateinfo ) ); - $olduid = $migrateinfo->migrateinfo->uid; // Check if uid is available - if( OC_User::UserExists( $olduid ) ){ + if( OC_User::UserExists( $olduser ) ){ OC_Log::write('migration','Username exists', OC_Log::ERROR); die('user exists'); } // Create the user - if( !OC_Migrate::createUser( $olduid, $migrateinfo->migrateinfo->hash ) ){ + if( !OC_Migrate::createUser( $olduser, $migrateinfo->hash ) ){ OC_Log::write('migration', 'Failed to create the new user', OC_Log::ERROR); die('coundlt create new user'); } $datadir = OC_Config::getValue( 'datadirectory' ); + // Make the new users data dir + $path = $datadir . '/' . $olduser . '/files/'; + if( !mkdir( $path, 0755, true ) ){ + OC_Log::write('migration','Failed to create users data dir: '.$path, OC_Log::ERROR); + die('failed to create users data dir'); + } + // Copy data - if( !copy_r( $importdir . '/files', $datadir . '/' ) ){ + if( !copy_r( $importdir . '/' . $olduser . '/files', $datadir . '/' . $olduser . '/files' ) ){ OC_Log::write('migration','Failed to copy user files to destination', OC_Log::ERROR); die('failed to copy user files'); } // Import user data - if( !OC_Migrate::importUser( $importdir . '/migration.db', $migrateinfo ) ){ + if( !OC_Migrate::importAppData( $importdir . '/' . $olduser . '/migration.db', $migrateinfo ) ){ OC_Log::write('migration','Failed to import user data', OC_Log::ERROR); die('failed to import user data'); } diff --git a/lib/migrate.php b/lib/migrate.php index 44d28297d4..415c33e5be 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -53,6 +53,21 @@ class OC_Migrate{ self::$providers[]=$provider; } + /** + * @breif finds and loads the providers + */ + static private function findProviders(){ + // Find the providers + $apps = OC_App::getAllApps(); + + foreach($apps as $app){ + $path = OC::$SERVERROOT . '/apps/' . $app . '/lib/migrate.php'; + if( file_exists( $path ) ){ + include( $path ); + } + } + } + /** * @breif creates a migration.db in the users data dir with their app data in * @return bool whether operation was successfull @@ -64,14 +79,7 @@ class OC_Migrate{ $return = array(); // Find the providers - $apps = OC_App::getAllApps(); - - foreach($apps as $app){ - $path = OC::$SERVERROOT . '/apps/' . $app . '/lib/migrate.php'; - if( file_exists( $path ) ){ - include( $path ); - } - } + self::findProviders(); // Foreach provider foreach( self::$providers as $provider ){ @@ -217,7 +225,8 @@ class OC_Migrate{ OC_Log::write( 'migration', 'Failed to get the users password hash', OC_log::ERROR); return false; } - $info['hash'] = $hash; + $info['hash'] = $hash; + $info['exporteduser'] = self::$uid; } // Merge in other data $info = array_merge( $info, $array ); @@ -393,17 +402,15 @@ class OC_Migrate{ * @param $uid optional uid to use * @return bool if the import succedded */ - public static function importAppData( $db, $info, $uid=false ){ + public static function importAppData( $db, $info, $uid=null ){ - if(!self::$uid){ - OC_Log::write('migration','Tried to import without passing a uid',OC_Log::FATAL); - return false; - } + self::$uid = !is_null( $uid ) ? $uid : $info->exporteduser; // Check if the db exists if( file_exists( $db ) ){ // Connect to the db if(!self::connectDB( $db )){ + OC_Log::write('migration','Failed to connect to migration.db',OC_Log::ERROR); return false; } } else { @@ -411,25 +418,25 @@ class OC_Migrate{ return false; } - if( !is_array( $info ) ){ - OC_Log::write('migration','$migrateinfo is not an array', OC_Log::FATAL); - return false; - } - - // Set the user id - self::$uid = $info->migrateinfo->uid; - - $apps = $info->app; - + // Find providers + self::findProviders(); + + // Generate importinfo array + $importinfo = array( + 'olduid' => $info->exporteduser, + 'newuid' => self::$uid + ); + foreach( self::$providers as $provider){ // Is the app in the export? - if( array_key_exists( $provider->id, $apps ) ){ + $id = $provider->id; + if( isset( $info->apps->$id ) ){ // Did it succeed? - if( $app[$provider->id] ){ + if( $info->apps->$id->success ){ // Then do the import - $provider->import( $info ); + $provider->import( $info->apps->$id, $importinfo ); } - } + } } return true; @@ -691,7 +698,7 @@ class OC_Migrate{ // Create the user $query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" ); - $result = $query->execute( array( $uid, $data['hash'])); + $result = $query->execute( array( $uid, $hash)); if( !$result ){ OC_Log::write('migration', 'Failed to create the new user "'.$uid.""); } diff --git a/lib/migrate/provider.php b/lib/migrate/provider.php index e2e01b3b5a..7ac3cf97ca 100644 --- a/lib/migrate/provider.php +++ b/lib/migrate/provider.php @@ -20,8 +20,9 @@ abstract class OC_Migrate_Provider{ /** * @breif imports data for the app + * @param $appinfo object with the data that the app exported * @param $info array of info including exportinfo.json * @return void */ - abstract function import( $info ); + abstract function import( $appinfo, $info ); } From 77f6872ea4859e13637efbc6d051072a5085394f Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 17 Mar 2012 17:45:39 +0000 Subject: [PATCH 048/302] Shorten export zip names --- lib/migrate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/migrate.php b/lib/migrate.php index 415c33e5be..84eafcd4cd 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -126,7 +126,7 @@ class OC_Migrate{ */ static public function createSysExportFile( $exporttype='instance', $path=null ){ // Calculate zip name - $zipname = "owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; + $zipname = "oc_export_" . date("y-m-d_H-i-s") . ".zip"; // Get the data dir $datadir = OC_Config::getValue( 'datadirectory' ); // Calculate destination @@ -293,7 +293,7 @@ class OC_Migrate{ $user = OC_User::getUser(); $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . $user . '/'; // Calculate zip name - $zipname = "owncloud_userexport_" . $user . '_' . date("y-m-d_H-i-s") . ".zip"; + $zipname = "oc_userexport_" . $user . '_' . date("y-m-d_H-i-s") . ".zip"; // Calculate destination if( !is_null( $path ) ){ // Path given From de09883d860d6507a2d287d0b8bae394963c4b94 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sat, 17 Mar 2012 23:41:10 +0100 Subject: [PATCH 049/302] creating and retrive thumbnails for intermediate galleries --- apps/gallery/ajax/galleryOp.php | 2 ++ apps/gallery/lib/album.php | 16 ++++++++++++---- apps/gallery/lib/scanner.php | 9 +++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/gallery/ajax/galleryOp.php b/apps/gallery/ajax/galleryOp.php index 25976fa9e3..459c30f6ac 100644 --- a/apps/gallery/ajax/galleryOp.php +++ b/apps/gallery/ajax/galleryOp.php @@ -97,6 +97,8 @@ function handleGetGallery($path) { while ($r = $result->fetchRow()) { $album_name = $r['album_name']; $size=OC_Gallery_Album::getAlbumSize($r['album_id']); + // this is a fallback mechanism and seems expensive + if ($size == 0) $size = OC_Gallery_Album::getIntermediateGallerySize($r['album_path']); $a[] = array('name' => utf8_encode($album_name), 'numOfItems' => min($size, 10),'path'=>substr($r['album_path'], $pathLen)); } diff --git a/apps/gallery/lib/album.php b/apps/gallery/lib/album.php index e8698590e6..070afdd6cd 100644 --- a/apps/gallery/lib/album.php +++ b/apps/gallery/lib/album.php @@ -98,11 +98,19 @@ class OC_Gallery_Album { } public static function getAlbumSize($id){ - $sql = 'SELECT COUNT(*) as size FROM *PREFIX*gallery_photos WHERE album_id = ?'; - $stmt = OC_DB::prepare($sql); - $result=$stmt->execute(array($id))->fetchRow(); - return $result['size']; + $sql = 'SELECT COUNT(*) as size FROM *PREFIX*gallery_photos WHERE album_id = ?'; + $stmt = OC_DB::prepare($sql); + $result=$stmt->execute(array($id))->fetchRow(); + return $result['size']; } + + public static function getIntermediateGallerySize($path) { + $path .= '%'; + $sql = 'SELECT COUNT(*) as size FROM *PREFIX*gallery_photos photos, *PREFIX*gallery_albums albums WHERE photos.album_id = albums.album_id AND uid_owner = ? AND file_path LIKE ?'; + $stmt = OC_DB::prepare($sql); + $result = $stmt->execute(array(OC_User::getUser(), $path))->fetchRow(); + return $result['size']; + } } ?> diff --git a/apps/gallery/lib/scanner.php b/apps/gallery/lib/scanner.php index 929f8b7a2e..c8825c267e 100644 --- a/apps/gallery/lib/scanner.php +++ b/apps/gallery/lib/scanner.php @@ -95,6 +95,15 @@ class OC_Gallery_Scanner { foreach ($a as $e) { $p .= ($p == '/'?'':'/').$e; OC_Gallery_Album::create(OC_User::getUser(), $e, $p); + $arr = OC_FileCache::searchByMime('image','', OC_Filesystem::getRoot().$p); + $step = floor(count($arr)/10); + if ($step == 0) $step = 1; + $na = array(); + for ($j = 0; $j < count($arr); $j+=$step) { + $na[] = $p.$arr[$j]; + } + if (count($na)) + self::createThumbnails($e, $na); } } } From 03120959e9829eb1e33a2d1035b976438578fed3 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 19 Mar 2012 09:09:18 +0100 Subject: [PATCH 050/302] add a proper email address for lostpassword service - bugfix for oc-178 --- core/lostpassword/index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/lostpassword/index.php b/core/lostpassword/index.php index 30caa2d23d..da0428e3ce 100644 --- a/core/lostpassword/index.php +++ b/core/lostpassword/index.php @@ -21,7 +21,8 @@ if (isset($_POST['user'])) { $tmpl->assign('link', $link); $msg = $tmpl->fetchPage(); $l = new OC_L10N('core'); - mail($email, $l->t('Owncloud password reset'), $msg); + $from = 'lostpassword-noreply@' . $_SERVER['HTTP_HOST']; + mail($email, $l->t('Owncloud password reset'), $msg, 'From:' . $from); } OC_Template::printGuestPage('core/lostpassword', 'lostpassword', array('error' => false, 'requested' => true)); } else { From 194211500b601d8582fa1e8396fb4c8e4970224d Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 19 Mar 2012 09:58:44 +0100 Subject: [PATCH 051/302] register text/x-vcard as a fileaction for vcard import --- apps/contacts/js/loader.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/contacts/js/loader.js b/apps/contacts/js/loader.js index eb59185d72..95fd7dc94e 100644 --- a/apps/contacts/js/loader.js +++ b/apps/contacts/js/loader.js @@ -77,5 +77,7 @@ $(document).ready(function(){ if(typeof FileActions !== 'undefined'){ FileActions.register('text/vcard','importaddressbook', '', Contacts_Import.importdialog); FileActions.setDefault('text/vcard','importaddressbook'); + FileActions.register('text/x-vcard','importaddressbook', '', Contacts_Import.importdialog); + FileActions.setDefault('text/x-vcard','importaddressbook'); }; }); \ No newline at end of file From b13ab2b17eb485aec13c7fb1e88e25e1c9901d72 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 19 Mar 2012 11:56:02 +0100 Subject: [PATCH 052/302] enable admin to turn off ZIP downloads user interface offers multi-file/folder downloads only if available make function name more clear --- files/admin.php | 3 +++ files/js/admin.js | 28 ++++++++++++++++++---------- files/js/fileactions.js | 15 +++++++++++---- files/templates/admin.php | 5 ++++- files/templates/index.php | 7 ++++++- lib/files.php | 21 ++++++++++++++++++--- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/files/admin.php b/files/admin.php index 7e410652cf..b9c26c465f 100644 --- a/files/admin.php +++ b/files/admin.php @@ -37,10 +37,12 @@ if($_POST) { $maxZipInputSize=$_POST['maxZipInputSize']; OC_Preferences::setValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize($maxZipInputSize)); } + OC_Preferences::setValue('', 'files', 'allowZipDownload', isset($_POST['allowZipDownload'])); }else{ $upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); $post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); $maxUploadFilesize = min($upload_max_filesize, $post_max_size); + $allowZipDownload = intval(OC_Preferences::getValue('', 'files', 'allowZipDownload', 1)); $maxZipInputSize = OC_Helper::humanfilesize(OC_Preferences::getValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize('800 MB'))); } @@ -49,5 +51,6 @@ OC_App::setActiveNavigationEntry( "files_administration" ); $tmpl = new OC_Template( 'files', 'admin' ); $tmpl->assign( 'htaccessWorking', $htaccessWorking ); $tmpl->assign( 'uploadMaxFilesize', $maxUploadFilesize); +$tmpl->assign( 'allowZipDownload', $allowZipDownload); $tmpl->assign( 'maxZipInputSize', $maxZipInputSize); return $tmpl->fetchPage(); \ No newline at end of file diff --git a/files/js/admin.js b/files/js/admin.js index 5cbb2b9f5a..bfa9667063 100644 --- a/files/js/admin.js +++ b/files/js/admin.js @@ -1,15 +1,23 @@ -function switchPublicFolder() +function switchPublicFolder() { - var publicEnable = $('#publicEnable').is(':checked'); - var sharingaimGroup = $('input:radio[name=sharingaim]'); //find all radiobuttons of that group - $.each(sharingaimGroup, function(index, sharingaimItem) { - sharingaimItem.disabled = !publicEnable; //set all buttons to the correct state - }); + var publicEnable = $('#publicEnable').is(':checked'); + var sharingaimGroup = $('input:radio[name=sharingaim]'); //find all radiobuttons of that group + $.each(sharingaimGroup, function(index, sharingaimItem) { + sharingaimItem.disabled = !publicEnable; //set all buttons to the correct state + }); } $(document).ready(function(){ - switchPublicFolder(); // Execute the function after loading DOM tree - $('#publicEnable').click(function(){ - switchPublicFolder(); // To get rid of onClick() - }); + switchPublicFolder(); // Execute the function after loading DOM tree + $('#publicEnable').click(function(){ + switchPublicFolder(); // To get rid of onClick() + }); + + $('#allowZipDownload').bind('change', function() { + if($('#allowZipDownload').attr('checked')) { + $('#maxZipInputSize').removeAttr('disabled'); + } else { + $('#maxZipInputSize').attr('disabled', 'disabled'); + } + }); }); diff --git a/files/js/fileactions.js b/files/js/fileactions.js index b5dd398280..b33be280ad 100644 --- a/files/js/fileactions.js +++ b/files/js/fileactions.js @@ -106,7 +106,7 @@ FileActions={ element.hide(); parent.parent().children().last().append(element); } - $('#fileList .action').css('-o-transition-property','none');//temporarly disable + $('#fileList .action').css('-o-transition-property','none');//temporarly disable $('#fileList .action').fadeIn(200,function(){ $('#fileList .action').css('-o-transition-property','opacity'); }); @@ -128,8 +128,15 @@ FileActions={ } } -FileActions.register('all','Download',function(){return OC.imagePath('core','actions/download')},function(filename){ - window.location='ajax/download.php?files='+encodeURIComponent(filename)+'&dir='+encodeURIComponent($('#dir').val()); +$(document).ready(function(){ + if($('#allowZipDownload').val() == 1){ + var downloadScope = 'all'; + } else { + var downloadScope = 'file'; + } + FileActions.register(downloadScope,'Download',function(){return OC.imagePath('core','actions/download')},function(filename){ + window.location='ajax/download.php?files='+encodeURIComponent(filename)+'&dir='+encodeURIComponent($('#dir').val()); + }); }); FileActions.register('all','Delete',function(){return OC.imagePath('core','actions/delete')},function(filename){ @@ -144,4 +151,4 @@ FileActions.register('dir','Open','',function(filename){ window.location='index.php?dir='+encodeURIComponent($('#dir').val()).replace(/%2F/g, '/')+'/'+encodeURIComponent(filename); }); -FileActions.setDefault('dir','Open'); +FileActions.setDefault('dir','Open'); diff --git a/files/templates/admin.php b/files/templates/admin.php index 8c3ba56ad5..fd86d02e1e 100644 --- a/files/templates/admin.php +++ b/files/templates/admin.php @@ -6,7 +6,10 @@ '/>
- '/>
+ />
+
+ ' title="t( '0 is unlimited' ); ?>" />
+
diff --git a/files/templates/index.php b/files/templates/index.php index 7fc51c288e..497a0f36c0 100644 --- a/files/templates/index.php +++ b/files/templates/index.php @@ -40,7 +40,9 @@ t( 'Name' ); ?> - Download" /> + + Download" /> + @@ -66,3 +68,6 @@ t('Current scanning');?>

+ + + \ No newline at end of file diff --git a/lib/files.php b/lib/files.php index 50223df1d3..662f0b5972 100644 --- a/lib/files.php +++ b/lib/files.php @@ -59,7 +59,7 @@ class OC_Files { } if(is_array($files)){ - self::checkZipInputSize($dir,$files); + self::validateZipDownload($dir,$files); $executionTime = intval(ini_get('max_execution_time')); set_time_limit(0); $zip = new ZipArchive(); @@ -80,7 +80,7 @@ class OC_Files { $zip->close(); set_time_limit($executionTime); }elseif(OC_Filesystem::is_dir($dir.'/'.$files)){ - self::checkZipInputSize($dir,$files); + self::validateZipDownload($dir,$files); $executionTime = intval(ini_get('max_execution_time')); set_time_limit(0); $zip = new ZipArchive(); @@ -223,7 +223,22 @@ class OC_Files { * @param dir $dir * @param files $files */ - static function checkZipInputSize($dir, $files) { + static function validateZipDownload($dir, $files) { + if(!OC_Preferences::getValue('', 'files', 'allowZipDownload', 1)) { + $l = new OC_L10N('files'); + header("HTTP/1.0 409 Conflict"); + $tmpl = new OC_Template( '', 'error', 'user' ); + $errors = array( + array( + 'error' => $l->t('ZIP download is turned off.'), + 'hint' => $l->t('Files need to be downloaded one by one.') . '
' . $l->t('Back to Files') . '', + ) + ); + $tmpl->assign('errors', $errors); + $tmpl->printPage(); + exit; + } + $zipLimit = OC_Preferences::getValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize('800 MB')); if($zipLimit > 0) { $totalsize = 0; From fa104fee17b36e619792599ab7fb921f95285833 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 19 Mar 2012 12:41:10 +0100 Subject: [PATCH 053/302] fix mimetype detection --- lib/helper.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/helper.php b/lib/helper.php index 0c6c73aa76..75942f092c 100644 --- a/lib/helper.php +++ b/lib/helper.php @@ -284,9 +284,7 @@ class OC_Helper { $isWrapped=(strpos($path,'://')!==false) and (substr($path,0,7)=='file://'); $mimeType='application/octet-stream'; if ($mimeType=='application/octet-stream') { - if(count(self::$mimetypes)>0){ - self::$mimetypes = include('mimetypes.fixlist.php'); - } + self::$mimetypes = include('mimetypes.fixlist.php'); $extention=strtolower(strrchr(basename($path), ".")); $extention=substr($extention,1);//remove leading . $mimeType=(isset(self::$mimetypes[$extention]))?self::$mimetypes[$extention]:'application/octet-stream'; From 145d6f35660669397eaee08988ffbad1b65daff0 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Mon, 19 Mar 2012 20:44:20 +0000 Subject: [PATCH 054/302] Add OC_Migration_Content class to help app devs. Restructure OC_Migrate. --- apps/admin_export/settings.php | 134 +----- apps/bookmarks/lib/migrate.php | 20 +- apps/user_migrate/settings.php | 2 +- lib/migrate.php | 781 ++++++++++++--------------------- lib/migrate/provider.php | 28 -- lib/migration/content.php | 239 ++++++++++ lib/migration/provider.php | 49 +++ 7 files changed, 593 insertions(+), 660 deletions(-) delete mode 100644 lib/migrate/provider.php create mode 100644 lib/migration/content.php create mode 100644 lib/migration/provider.php diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 1c98bb552f..af8dd0dbf5 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -27,11 +27,10 @@ OC_Util::checkAppEnabled('admin_export'); define('DS', '/'); - // Export? if (isset($_POST['admin_export'])) { // Create the export zip - if( !$path = OC_Migrate::createSysExportFile( $_POST['export_type'] ) ){ + if( !$path = OC_Migrate::export( $_POST['export_type'] ) ){ // Error die('error'); } else { @@ -46,136 +45,11 @@ if (isset($_POST['admin_export'])) { // Import? } else if( isset($_POST['admin_import']) ){ - $root = OC::$SERVERROOT . "/"; - $importname = "owncloud_import_" . date("y-m-d_H-i-s"); - - // Save data dir for later - $datadir = OC_Config::getValue( 'datadirectory' ); - - // Copy the uploaded file - $from = $_FILES['owncloud_import']['tmp_name']; - $to = get_temp_dir().'/'.$importname.'.zip'; - if( !move_uploaded_file( $from, $to ) ){ - OC_Log::write('admin_export',"Failed to copy the uploaded file",OC_Log::INFO); - exit(); - } - - // Extract zip - $zip = new ZipArchive(); - if ($zip->open(get_temp_dir().'/'.$importname.'.zip') != TRUE) { - OC_Log::write('admin_export',"Failed to open zip file",OC_Log::INFO); - exit(); - } - $zip->extractTo(get_temp_dir().'/'.$importname.'/'); - $zip->close(); - - // Delete uploaded file - unlink( get_temp_dir() . '/' . $importname . '.zip' ); - - // Now we need to check if everything is present. Data and dbexport.xml - - - // Delete current data folder. - OC_Log::write('admin_export',"Deleting current data dir",OC_Log::INFO); - unlinkRecursive( $datadir, false ); - - // Copy over data - if( !copy_r( get_temp_dir() . '/' . $importname . '/data', $datadir ) ){ - OC_Log::write('admin_export',"Failed to copy over data directory",OC_Log::INFO); - exit(); - } - - OC_DB::replaceDB( get_temp_dir() . '/' . $importname . '/dbexport.xml' ); + // TODO + // OC_Migrate::import( $pathtozipfile ); } else { // fill template $tmpl = new OC_Template('admin_export', 'settings'); return $tmpl->fetchPage(); -} - -function zipAddDir($dir, $zip, $recursive=true, $internalDir='') { - $dirname = basename($dir); - $zip->addEmptyDir($internalDir . $dirname); - $internalDir.=$dirname.='/'; - - if ($dirhandle = opendir($dir)) { - while (false !== ( $file = readdir($dirhandle))) { - - if (( $file != '.' ) && ( $file != '..' )) { - - if (is_dir($dir . '/' . $file) && $recursive) { - zipAddDir($dir . '/' . $file, $zip, $recursive, $internalDir); - } elseif (is_file($dir . '/' . $file)) { - $zip->addFile($dir . '/' . $file, $internalDir . $file); - } - } - } - closedir($dirhandle); - } else { - OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); - } -} - -function unlinkRecursive($dir, $deleteRootToo) -{ - if(!$dh = @opendir($dir)) - { - return; - } - while (false !== ($obj = readdir($dh))) - { - if($obj == '.' || $obj == '..') - { - continue; - } - - if (!@unlink($dir . '/' . $obj)) - { - unlinkRecursive($dir.'/'.$obj, true); - } - } - - closedir($dh); - - if ($deleteRootToo) - { - @rmdir($dir); - } - - return; -} - - function copy_r( $path, $dest ) - { - if( is_dir($path) ) - { - @mkdir( $dest ); - $objects = scandir($path); - if( sizeof($objects) > 0 ) - { - foreach( $objects as $file ) - { - if( $file == "." || $file == ".." ) - continue; - // go on - if( is_dir( $path.DS.$file ) ) - { - copy_r( $path.DS.$file, $dest.DS.$file ); - } - else - { - copy( $path.DS.$file, $dest.DS.$file ); - } - } - } - return true; - } - elseif( is_file($path) ) - { - return copy($path, $dest); - } - else - { - return false; - } - } +} \ No newline at end of file diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index ffc5e9f838..36a08c0cf4 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -1,16 +1,16 @@ 'bookmarks', 'matchcol'=>'user_id', - 'matchval'=>$uid, + 'matchval'=>$this->uid, 'idcol'=>'id' ); - $ids = OC_Migrate::copyRows( $options ); + $ids = $this->content->copyRows( $options ); $options = array( 'table'=>'bookmarks_tags', @@ -19,7 +19,7 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ ); // Export tags - $ids2 = OC_Migrate::copyRows( $options ); + $ids2 = $this->content->copyRows( $options ); // If both returned some ids then they worked if( is_array( $ids ) && is_array( $ids2 ) ) @@ -32,17 +32,17 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ } // Import function for bookmarks - function import( $app, $info ){ - switch( $app->version ){ + function import( ){ + switch( $this->appinfo->version ){ default: // All versions of the app have had the same db structure, so all can use the same import function $query = OC_Migrate::prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); - $results = $query->execute( array( $info['olduid'] ) ); + $results = $query->execute( array( $this->info['olduid'] ) ); $idmap = array(); while( $row = $data->fetchRow() ){ // Import each bookmark, saving its id into the map $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks(url, title, user_id, public, added, lastmodified) VALUES (?, ?, ?, ?, ?, ?)" ); - $query->execute( array( $row['url'], $row['title'], $info['newuid'], $row['public'], $row['added'], $row['lastmodified'] ) ); + $query->execute( array( $row['url'], $row['title'], $this->info['newuid'], $row['public'], $row['added'], $row['lastmodified'] ) ); // Map the id $idmap[$row['id']] = OC_DB::insertid(); } @@ -66,4 +66,4 @@ class OC_Migrate_Provider_Bookmarks extends OC_Migrate_Provider{ } // Load the provider -new OC_Migrate_Provider_Bookmarks( 'bookmarks' ); \ No newline at end of file +new OC_Migration_Provider_Bookmarks( 'bookmarks' ); \ No newline at end of file diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 62f5e3f20d..38eee990b4 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -26,7 +26,7 @@ OC_Util::checkAppEnabled('user_migrate'); if (isset($_POST['user_export'])) { // Create the export zip - if( !$path = OC_Migrate::createUserExportFile() ){ + if( !$path = OC_Migrate::export() ){ // Error die('error'); } else { diff --git a/lib/migrate.php b/lib/migrate.php index 84eafcd4cd..338d091af8 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -22,28 +22,31 @@ /** - * provides an interface to all search providers + * provides an interface to migrate users and whole ownclouds */ class OC_Migrate{ - // Holds the db object - static private $MDB2=false; + // Array of OC_Migration_Provider objects static private $providers=array(); - // Schema db object - static private $schema=false; // User id of the user to import/export static private $uid=false; - // Path to the sqlite db - static private $dbpath=false; // Holds the ZipArchive object static private $zip=false; - // String path to export - static private $zippath=false; // Stores the type of export static private $exporttype=false; // Array of temp files to be deleted after zip creation static private $tmpfiles=array(); + // Holds the db object + static private $MDB2=false; + // Schema db object + static private $schema=false; + // Path to the sqlite db + static private $dbpath=false; + // Holds the path to the zip file + static private $zippath=false; + // Holds the OC_Migration_Content object + static private $content=false; /** * register a new migration provider @@ -69,99 +72,78 @@ class OC_Migrate{ } /** - * @breif creates a migration.db in the users data dir with their app data in - * @return bool whether operation was successfull + * @breif exports a user, or owncloud instance + * @param ootional $type string type of export, defualts to user + * @param otional $path string path to zip output folder + * @param optional $uid string user id of user to export if export type is user, defaults to current */ - private static function exportAppData( ){ - - self::connectDB(); - $ok = true; - $return = array(); - - // Find the providers - self::findProviders(); - - // Foreach provider - foreach( self::$providers as $provider ){ - $failed = false; - - // Does this app use the database? - if(file_exists(OC::$SERVERROOT.'/apps/'.$provider->id.'/appinfo/database.xml')){ - // Create some app tables - $tables = self::createAppTables( $provider->id ); - if( is_array( $tables ) ){ - // Save the table names - foreach($tables as $table){ - $return['apps'][$provider->id]['tables'][] = $table; - } - } else { - // It failed to create the tables - $failed = true; - } - } - - // Run the import function? - if( !$failed ){ - $return['apps'][$provider->id]['success'] = $provider->export( self::$uid ); - } else { - $return['apps'][$provider->id]['success'] = false; - $return['apps'][$provider->id]['message'] = 'failed to create the app tables'; - } - - // Now add some app info the the return array - $appinfo = OC_App::getAppInfo( $provider->id ); - $return['apps'][$provider->id]['version'] = $appinfo['version']; - - } - - return $return; - - } - - /** - * @breif creates an export file for the whole system - * @param optional $exporttype string export type ('instance','system' or 'userfiles') - * @param optional $path string path to zip destination (with trailing slash) - * @return path to the zip or false if there was a problem - */ - static public function createSysExportFile( $exporttype='instance', $path=null ){ - // Calculate zip name - $zipname = "oc_export_" . date("y-m-d_H-i-s") . ".zip"; - // Get the data dir + public static function export( $type='user', $path=null, $uid=null ){ $datadir = OC_Config::getValue( 'datadirectory' ); - // Calculate destination - if( !is_null( $path ) ){ - // Path given - // Is a directory? - if( !is_dir( $path ) ){ - OC_Log::write('migration', 'Path supplied to createSysExportFile() is not a directory', OC_Log::ERROR); - return false; - } - // Is writeable - if( !is_writeable( $path ) ){ - OC_Log::write('migration', 'Path supplied to createSysExportFile() is not writeable', OC_Log::ERROR); - return false; - } - self::$zippath = $path . $zipname; - } else { - // Save in tmp dir - self::$zippath = sys_get_temp_dir() . '/' . $zipname; - } - // Create the zip object - self::$zip = new ZipArchive; - // Try to create the zip - if( !self::createZip() ){ - return false; - } - // Handle export types - $exporttypes = array( 'userfiles', 'instance', 'system' ); - self::$exporttype = in_array( $exporttype, $exporttypes ) ? $exporttype : false; - if( !self::$exporttype ){ - OC_Log::write( 'migration', 'Export type: '.$exporttype.' is not supported.', OC_Log::ERROR); - return false; - } - switch( self::$exporttype ){ - case 'instance': + // Validate export type + $types = array( 'user', 'instance', 'system', 'userfiles' ); + if( !in_array( $type, $types ) ){ + OC_Log::write( 'migration', 'Invalid export type', OC_Log::ERROR ); + return false; + } + self::$exporttype = $type; + // Userid? + if( self::$exporttype == 'user' ){ + // Check user exists + if( !is_null($uid) ){ + if( !OC_User_Database::userExists( $uid ) ){ + OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR); + return false; + } + self::$uid = $uid; + } else { + self::$uid = OC_User::getUser(); + } + } + // Calculate zipname + if( self::$exporttype == 'user' ){ + $zipname = 'oc_export_' . self::$uid . '_' . date("y-m-d_H-i-s") . '.zip'; + } else { + $zipname = 'oc_export_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '.zip'; + } + // Calculate path + if( self::$exporttype == 'user' ){ + self::$zippath = $datadir . '/' . self::$uid . '/' . $zipname; + } else { + if( !is_null( $path ) ){ + // Validate custom path + if( !file_exists( $path ) || !is_writeable( $path ) ){ + OC_Log::write( 'migration', 'Path supplied is invalid.', OC_Log::ERROR ); + return false; + } + self::$zippath = $path . $zipname; + } else { + // Default path + self::$zippath = get_temp_dir() . '/' . $zipname; + } + } + // Create the zip object + self::$zip = new ZipArchive; + if( !self::createZip() ){ + return false; + } + // Do the export + self::findProviders(); + $exportdata = array(); + switch( self::$exporttype ){ + case 'user': + // Connect to the db + self::$dbpath = $datadir . '/' . self::$uid . '/migration.db'; + if( !self::connectDB() ){ + return false; + } + self::$content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + // Export the app info + $exportdata = self::exportAppData(); + // Add the data dir to the zip + self::$content->addDir( $datadir . '/' . self::$uid, true, '/' ); + break; + case 'instance': + self::$content = new OC_Migration_Content( self::$zip ); // Creates a zip that is compatable with the import function $dbfile = tempnam( "/tmp", "owncloud_export_data_" ); OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); @@ -172,43 +154,112 @@ class OC_Migrate{ $dbtableprefixstring = "\n\n " . OC_Config::getValue( "dbtableprefix", "oc_" ); $dbexport = str_replace( $dbnamestring, "\n\n *dbname*", $dbexport ); $dbexport = str_replace( $dbtableprefixstring, "
\n\n *dbprefix*", $dbexport ); - // Write the new db export file - file_put_contents( $dbfile, $dbexport ); - self::$zip->addFile( $dbfile, "dbexport.xml" ); + // Add the export to the zip + self::$content->addFromString( $dbexport, "dbexport.xml" ); // Add user data foreach(OC_User::getUsers() as $user){ - self::addDirToZip( $datadir . '/' . $user . '/', true, "/userdata/" ); + self::$content->addDir( $datadir . '/' . $user . '/', true, "/userdata/" ); } break; case 'userfiles': + self::$content = new OC_Migration_Content( self::$zip ); // Creates a zip with all of the users files foreach(OC_User::getUsers() as $user){ - self::addDirToZip( $datadir . '/' . $user . '/', true, "/" ); + self::$content->addDir( $datadir . '/' . $user . '/', true, "/" ); } break; case 'system': + self::$content = new OC_Migration_Content( self::$zip ); // Creates a zip with the owncloud system files - self::addDirToZip( OC::$SERVERROOT . '/', false, '/'); + self::$content->addDir( OC::$SERVERROOT . '/', false, '/'); foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dir) { - self::addDirToZip( OC::$SERVERROOT . '/' . $dir, true, "/"); + self::$content->addDir( OC::$SERVERROOT . '/' . $dir, true, "/"); } - break; + break; + } + if( !$info = self::getExportInfo( $exportdata ) ){ + return false; + } + // Add the export info json to the export zip + self::$content->addFromString( $info, 'export_info.json' ); + if( !self::$content->finish() ){ + return false; + } + return self::$zippath; + } + + /** + * @brief connects to a MDB2 database scheme + * @returns bool + */ + static private function connectScheme(){ + // We need a mdb2 database connection + self::$MDB2->loadModule( 'Manager' ); + self::$MDB2->loadModule( 'Reverse' ); + + // Connect if this did not happen before + if( !self::$schema ){ + require_once('MDB2/Schema.php'); + self::$schema=MDB2_Schema::factory( self::$MDB2 ); } - // Add export info - self::addExportInfo(); - // Close the zip - if( !self::closeZip() ){ - return false; - } - return self::$zippath; - + + return true; } /** - * @breif adds a json file with infomation on the export to the zips root (used on import) - * @return bool - */ - static private function addExportInfo( $array=array() ){ + * @breif creates a migration.db in the users data dir with their app data in + * @return bool whether operation was successfull + */ + private static function exportAppData( ){ + + $success = true; + $return = array(); + + // Foreach provider + foreach( self::$providers as $provider ){ + $success = true; + // Does this app use the database? + if( file_exists( OC::$SERVERROOT.'/apps/'.$provider->getID().'/appinfo/database.xml' ) ){ + // Create some app tables + $tables = self::createAppTables( $provider->getID() ); + if( is_array( $tables ) ){ + // Save the table names + foreach($tables as $table){ + $return['apps'][$provider->getID()]['tables'][] = $table; + } + } else { + // It failed to create the tables + $success = false; + } + } + + // Run the export function? + if( $success ){ + // Set the provider properties + $provider->setData( self::$uid, self::$content ); + $return['apps'][$provider->getID()]['success'] = $provider->export(); + } else { + $return['apps'][$provider->getID()]['success'] = false; + $return['apps'][$provider->getID()]['message'] = 'failed to create the app tables'; + } + + // Now add some app info the the return array + $appinfo = OC_App::getAppInfo( $provider->getID() ); + $return['apps'][$provider->getID()]['version'] = $appinfo['version']; + + } + + return $return; + + } + + + /** + * @breif generates json containing export info, and merges any data supplied + * @param optional $array array of data to include in the returned json + * @return bool + */ + static private function getExportInfo( $array=array() ){ $info = array( 'ocversion' => OC_Util::getVersion(), 'exporttime' => time(), @@ -216,11 +267,12 @@ class OC_Migrate{ 'exporttype' => self::$exporttype ); // Add hash if user export - if( self::$exporttype = 'user' ){ + if( self::$exporttype == 'user' ){ $query = OC_DB::prepare( "SELECT password FROM *PREFIX*users WHERE uid LIKE ?" ); $result = $query->execute( array( self::$uid ) ); $row = $result->fetchRow(); $hash = $row ? $row['password'] : false; + die(var_dump($hash)); if( !$hash ){ OC_Log::write( 'migration', 'Failed to get the users password hash', OC_log::ERROR); return false; @@ -228,110 +280,122 @@ class OC_Migrate{ $info['hash'] = $hash; $info['exporteduser'] = self::$uid; } + if( !is_array( $array ) ){ + OC_Log::write( 'migration', 'Supplied $array was not an array in getExportInfo()', OC_Log::ERROR ); + } // Merge in other data - $info = array_merge( $info, $array ); + $info = array_merge( $info, (array)$array ); // Create json $json = json_encode( $info ); - $tmpfile = tempnam("/tmp", "oc_export_info_"); - self::$tmpfiles[] = $tmpfile; - if( !file_put_contents( $tmpfile, $json ) ){ - return false; - } else { - self::$zip->addFile( $tmpfile, "/" . self::$uid . "/export_info.json" ); - return true; - } - } - - - /** - * @breif tried to finalise the zip - * @return bool - */ - static private function closeZip(){ - if( !self::$zip->close() ){ - OC_Log::write('migration', 'Failed to save the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); - self::cleanup(); - return false; - } else { - OC_Log::write('migration', 'Export zip created ok', OC_Log::INFO); - self::cleanup(); - return true; - } + return true; } /** - * @breif cleans up after the zip - */ - static private function cleanup(){ - // Delete tmp files - foreach(self::$tmpfiles as $i){ - unlink( $i ); - } - } - - /** - * @breif creates a zip user export - * @param optional $uid string user id of the user to export (defaults to current) - * @param optional $path string path to folder to create file in (with trailing slash) (defaults to current user's data dir) - * @return false on failure | string path on success - */ - static public function createUserExportFile( $uid=null, $path=null ){ - // User passed? - $uid = is_null( $uid ) ? OC_User::getUser() : $uid ; - // Is a database user? - if( !OC_User_Database::userExists( $uid ) ){ - OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR); + * @breif connects to migration.db, or creates if not found + * @param $db optional path to migration.db, defaults to user data dir + * @return bool whether the operation was successful + */ + static private function connectDB( $path=null ){ + // Has the dbpath been set? + self::$dbpath = !is_null( $path ) ? $path : self::$dbpath; + if( !self::$dbpath ){ + OC_Log::write( 'migration', 'connectDB() was called without dbpath being set', OC_Log::ERROR ); return false; } - // Set the uid - self::$uid = $uid; - // Create the zip object - self::$zip = new ZipArchive; - // Set export type - self::$exporttype = 'user'; - // Calculate users data dir - $user = OC_User::getUser(); - $userdatadir = OC_Config::getValue( 'datadirectory' ) . '/' . $user . '/'; - // Calculate zip name - $zipname = "oc_userexport_" . $user . '_' . date("y-m-d_H-i-s") . ".zip"; - // Calculate destination - if( !is_null( $path ) ){ - // Path given - // Is a directory? - if( !is_dir( $path ) ){ - OC_Log::write('migration', 'Path supplied to createUserExportFile() is not a directory', OC_Log::ERROR); - return false; - } - // Is writeable - if( !is_writeable( $path ) ){ - OC_Log::write('migration', 'Path supplied to createUserExportFile() is not writeable', OC_Log::ERROR); + // Already connected + if(!self::$MDB2){ + require_once('MDB2.php'); + + $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + + // Prepare options array + $options = array( + 'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE), + 'log_line_break' => '
', + 'idxname_format' => '%s', + 'debug' => true, + 'quote_identifier' => true + ); + $dsn = array( + 'phptype' => 'sqlite3', + 'database' => self::$dbpath, + 'mode' => '0644' + ); + + // Try to establish connection + self::$MDB2 = MDB2::factory( $dsn, $options ); + // Die if we could not connect + if( PEAR::isError( self::$MDB2 ) ){ + die( self::$MDB2->getMessage() ); + OC_Log::write( 'migration', 'Failed to create/connect to migration.db', OC_Log::FATAL ); + OC_Log::write( 'migration', self::$MDB2->getUserInfo(), OC_Log::FATAL ); + OC_Log::write( 'migration', self::$MDB2->getMessage(), OC_Log::FATAL ); return false; } - self::$zippath = $path . $zipname; - } else { - // Save in users data dir - self::$zippath = $userdatadir . $zipname; + // We always, really always want associative arrays + self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC); } - // Try to create the zip - if( !self::createZip() ){ - return false; - } - // Export the app info - $appinfo = self::exportAppData(); - // Save the migration results - self::addExportInfo( $appinfo ); - // Add the data dir to the zip - self::addDirToZip( $userdatadir ); - // Close the zip - if( !self::closeZip() ){ - return false; - } - // All good - return self::$zippath; - } + return true; + + } + /** + * @breif creates the tables in migration.db from an apps database.xml + * @param $appid string id of the app + * @return bool whether the operation was successful + */ + static private function createAppTables( $appid ){ + + if( !self::connectScheme() ){ + return false; + } + + // There is a database.xml file + $content = file_get_contents( OC::$SERVERROOT . '/apps/' . $appid . '/appinfo/database.xml' ); + + $file2 = 'static://db_scheme'; + // TODO get the relative path to migration.db from the data dir + // For now just cheat + $path = pathinfo( self::$dbpath ); + $content = str_replace( '*dbname*', self::$uid.'/migration', $content ); + $content = str_replace( '*dbprefix*', '', $content ); + + $xml = new SimpleXMLElement($content); + foreach($xml->table as $table){ + $tables[] = (string)$table->name; + } + + file_put_contents( $file2, $content ); + + // Try to create tables + $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); + + unlink( $file2 ); + + // Die in case something went wrong + if( $definition instanceof MDB2_Schema_Error ){ + OC_Log::write( 'migration', 'Failed to parse database.xml for: '.$appid, OC_Log::FATAL ); + OC_Log::write( 'migration', $definition->getMessage().': '.$definition->getUserInfo(), OC_Log::FATAL ); + return false; + } + + $definition['overwrite'] = true; + + $ret = self::$schema->createDatabase( $definition ); + + // Die in case something went wrong + if( $ret instanceof MDB2_Error ){ + OC_Log::write( 'migration', 'Failed to create tables for: '.$appid, OC_Log::FATAL ); + OC_Log::write( 'migration', $ret->getMessage().': '.$ret->getUserInfo(), OC_Log::FATAL ); + return false; + } + return $tables; + + } + /** * @breif tries to create the zip + * @param $path string path to zip destination * @return bool */ static private function createZip(){ @@ -340,45 +404,13 @@ class OC_Migrate{ OC_Log::write('migration', 'createZip() called but $zip and/or $zippath have not been set', OC_Log::ERROR); return false; } - if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE ) !== TRUE ) { + if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE ) !== TRUE ) { OC_Log::write('migration', 'Failed to create the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); return false; } else { return true; } } - - /** - * @breif adds a directory to the zip object - * @param $dir string path of the directory to add - * @param $recursive bool - * @param $internaldir string path of folder to add dir to in zip - * @return bool - */ - static private function addDirToZip($dir, $recursive=true, $internaldir='') { - $dirname = basename($dir); - self::$zip->addEmptyDir($internaldir . $dirname); - $internaldir.=$dirname.='/'; - - if ($dirhandle = opendir($dir)) { - while (false !== ( $file = readdir($dirhandle))) { - - if (( $file != '.' ) && ( $file != '..' )) { - - if (is_dir($dir . '/' . $file) && $recursive) { - self::addDirToZip($dir . '/' . $file, $recursive, $internaldir); - } elseif (is_file($dir . '/' . $file)) { - self::$zip->addFile($dir . '/' . $file, $internaldir . $file); - } - } - } - closedir($dirhandle); - } else { - OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); - return false; - } - return true; - } /** * @breif returns an array of apps that support migration @@ -429,10 +461,17 @@ class OC_Migrate{ foreach( self::$providers as $provider){ // Is the app in the export? - $id = $provider->id; + $id = $provider->getID(); if( isset( $info->apps->$id ) ){ // Did it succeed? if( $info->apps->$id->success ){ + // Give the provider the content object + // TODO PASS THE PATH TO MIGRATION.DB + if( !self::connectDB() ){ + return false; + } + $content = new OC_Migration_Content( self::$zip, self::$db ); + $provider->setObject( $content ); // Then do the import $provider->import( $info->apps->$id, $importinfo ); } @@ -443,252 +482,12 @@ class OC_Migrate{ } - // @breif connects to migration.db, or creates if not found - // @param $db optional path to migration.db, defaults to user data dir - // @return bool whether the operation was successful - private static function connectDB( $dbpath=null ){ - OC_Log::write('migration','connecting to migration.db for user: '.self::$uid,OC_Log::INFO); - // Fail if no user is set - if(!self::$uid){ - OC_Log::write('migration','connectDB() called without self::$uid being set',OC_Log::INFO); - return false; - } - // Already connected - if(!self::$MDB2){ - require_once('MDB2.php'); - - $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); - - self::$dbpath = $datadir.'/'.self::$uid.'/migration.db';//!is_null( $dbpath ) ? $dbpath : $datadir.'/'.self::$uid.'/migration.db'; - - // Prepare options array - $options = array( - 'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE), - 'log_line_break' => '
', - 'idxname_format' => '%s', - 'debug' => true, - 'quote_identifier' => true - ); - $dsn = array( - 'phptype' => 'sqlite3', - 'database' => self::$dbpath, - 'mode' => '0644' - ); - - // Try to establish connection - self::$MDB2 = MDB2::factory( $dsn, $options ); - // Die if we could not connect - if( PEAR::isError( self::$MDB2 )){ - die(self::$MDB2->getMessage()); - OC_Log::write('migration', 'Failed to create/connect to migration.db',OC_Log::FATAL); - OC_Log::write('migration',self::$MDB2->getUserInfo(),OC_Log::FATAL); - OC_Log::write('migration',self::$MDB2->getMessage(),OC_Log::FATAL); - return false; - } else { - } - // We always, really always want associative arrays - self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC); - } - return true; - - } - - // @breif prepares the db - // @param $query the sql query to prepare - public static function prepare( $query ){ - - // Optimize the query - $query = self::processQuery( $query ); - - // Optimize the query - $query = self::$MDB2->prepare( $query ); - - // Die if we have an error (error means: bad query, not 0 results!) - if( PEAR::isError( $query )) { - $entry = 'DB Error: "'.$result->getMessage().'"
'; - $entry .= 'Offending command was: '.$query.'
'; - OC_Log::write('migration',$entry,OC_Log::FATAL); - return false; - } else { - return $query; - } - - } - - // @breif processes the db query - // @param $query the query to process - // @return string of processed query - private static function processQuery( $query ){ - - self::connectDB(); - $prefix = ''; - - $query = str_replace( '`', '\'', $query ); - $query = str_replace( 'NOW()', 'datetime(\'now\')', $query ); - $query = str_replace( 'now()', 'datetime(\'now\')', $query ); - - // replace table name prefix - $query = str_replace( '*PREFIX*', $prefix, $query ); - - return $query; - - } - - // @brief copys rows to migration.db from the main database - // @param $options array of options. - // @return bool - public static function copyRows( $options ){ - if( !array_key_exists( 'table', $options ) ){ - return false; - } - - $return = array(); - - // Need to include 'where' in the query? - if( array_key_exists( 'matchval', $options ) && array_key_exists( 'matchcol', $options ) ){ - - // If only one matchval, create an array - if(!is_array($options['matchval'])){ - $options['matchval'] = array( $options['matchval'] ); - } - - foreach( $options['matchval'] as $matchval ){ - // Run the query for this match value (where x = y value) - $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?" ); - $results = $query->execute( array( $matchval ) ); - $newreturns = self::insertData( $results, $options ); - $return = array_merge( $return, $newreturns ); - } - - } else { - // Just get everything - $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] ); - $results = $query->execute(); - $return = self::insertData( $results, $options ); - - } - - return $return; - - } - - // @breif saves a sql data set into migration.db - // @param $data a sql data set returned from self::prepare()->query() - // @param $options array of copyRows options - // @return void - private static function insertData( $data, $options ){ - $return = array(); - while( $row = $data->fetchRow() ){ - // Now save all this to the migration.db - $fields = array(); - $values = array(); - foreach($row as $field=>$value){ - $fields[] = $field; - $values[] = $value; - } - - // Generate some sql - $sql = "INSERT INTO `" . $options['table'] . '` ( `'; - $fieldssql = implode( '`, `', $fields ); - $sql .= $fieldssql . "` ) VALUES( "; - $valuessql = substr( str_repeat( '?, ', count( $fields ) ),0,-2 ); - $sql .= $valuessql . " )"; - // Make the query - $query = self::prepare( $sql ); - if(!$query){ - OC_Log::write('migration','Invalid sql produced: '.$sql,OC_Log::FATAL); - return false; - exit(); - } else { - $query->execute( $values ); - // Do we need to return some values? - if( array_key_exists( 'idcol', $options ) ){ - // Yes we do - $return[] = $row[$options['idcol']]; - } else { - // Take a guess and return the first field :) - $return[] = reset($row); - } - } - } - return $return; - } - - // @breif creates the tables in migration.db from an apps database.xml - // @param $appid string id of the app - // @return bool whether the operation was successful - private static function createAppTables( $appid ){ - - if(!self::connectScheme()){ - return false; - } - - // There is a database.xml file - $content = file_get_contents( OC::$SERVERROOT . '/apps/' . $appid . '/appinfo/database.xml' ); - - $file2 = 'static://db_scheme'; - $content = str_replace( '*dbname*', self::$uid.'/migration', $content ); - $content = str_replace( '*dbprefix*', '', $content ); - - $xml = new SimpleXMLElement($content); - foreach($xml->table as $table){ - $tables[] = (string)$table->name; - } - - file_put_contents( $file2, $content ); - - // Try to create tables - $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); - - unlink( $file2 ); - - // Die in case something went wrong - if( $definition instanceof MDB2_Schema_Error ){ - OC_Log::write('migration','Failed to parse database.xml for: '.$appid,OC_Log::FATAL); - OC_Log::write('migration',$definition->getMessage().': '.$definition->getUserInfo(),OC_Log::FATAL); - return false; - } - - $definition['overwrite'] = true; - - $ret = self::$schema->createDatabase( $definition ); - // Die in case something went wrong - - if( $ret instanceof MDB2_Error ){ - OC_Log::write('migration','Failed to create tables for: '.$appid,OC_Log::FATAL); - OC_Log::write('migration',$ret->getMessage().': '.$ret->getUserInfo(),OC_Log::FATAL); - return false; - } - return $tables; - - } - - - /** - * @brief connects to a MDB2 database scheme - * @returns true/false - * - * Connects to a MDB2 database scheme - */ - private static function connectScheme(){ - // We need a mdb2 database connection - self::connectDB(); - self::$MDB2->loadModule( 'Manager' ); - self::$MDB2->loadModule( 'Reverse' ); - - // Connect if this did not happen before - if( !self::$schema ){ - require_once('MDB2/Schema.php'); - self::$schema=MDB2_Schema::factory( self::$MDB2 ); - } - - return true; - } - - // @breif creates a new user in the database - // @param $uid string user_id of the user to be created - // @param $hash string hash of the user to be created - // @return bool result of user creation + /* + * @breif creates a new user in the database + * @param $uid string user_id of the user to be created + * @param $hash string hash of the user to be created + * @return bool result of user creation + */ public static function createUser( $uid, $hash ){ // Check if userid exists diff --git a/lib/migrate/provider.php b/lib/migrate/provider.php deleted file mode 100644 index 7ac3cf97ca..0000000000 --- a/lib/migrate/provider.php +++ /dev/null @@ -1,28 +0,0 @@ -id = $appid; - OC_Migrate::registerProvider( $this ); - } - - /** - * @breif exports data for apps - * @param string $uid - * @return array appdata to be exported - */ - abstract function export($uid); - - /** - * @breif imports data for the app - * @param $appinfo object with the data that the app exported - * @param $info array of info including exportinfo.json - * @return void - */ - abstract function import( $appinfo, $info ); -} diff --git a/lib/migration/content.php b/lib/migration/content.php new file mode 100644 index 0000000000..fe8a21a45b --- /dev/null +++ b/lib/migration/content.php @@ -0,0 +1,239 @@ +. + * + */ + + +/** + * provides methods to add and access data from the migration + */ +class OC_Migration_Content{ + + private $zip=false; + // Holds the MDB2 object + private $db=false; + // Holds an array of tmpfiles to delete after zip creation + private $tmpfiles=false; + + /** + * @breif sets up the + * @param $zip ZipArchive object + * @param optional $db a MDB2 database object (required for exporttype user) + * @return bool + */ + public function __construct( $zip, $db=false ){ + + $this->zip = $zip; + $this->db = $db; + + } + + // @breif prepares the db + // @param $query the sql query to prepare + public function prepare( $query ){ + + // Optimize the query + $query = $this->processQuery( $query ); + + // Optimize the query + $query = $this->MDB2->prepare( $query ); + + // Die if we have an error (error means: bad query, not 0 results!) + if( PEAR::isError( $query ) ) { + $entry = 'DB Error: "'.$result->getMessage().'"
'; + $entry .= 'Offending command was: '.$query.'
'; + OC_Log::write( 'migration', $entry, OC_Log::FATAL ); + return false; + } else { + return $query; + } + + } + + /** + * @breif processes the db query + * @param $query the query to process + * @return string of processed query + */ + private function processQuery( $query ){ + $query = str_replace( '`', '\'', $query ); + $query = str_replace( 'NOW()', 'datetime(\'now\')', $query ); + $query = str_replace( 'now()', 'datetime(\'now\')', $query ); + // remove table prefixes + $query = str_replace( '*PREFIX*', '', $query ); + return $query; + } + + /** + * @brief copys rows to migration.db from the main database + * @param $options array of options. + * @return bool + */ + public function copyRows( $options ){ + if( !array_key_exists( 'table', $options ) ){ + return false; + } + + $return = array(); + + // Need to include 'where' in the query? + if( array_key_exists( 'matchval', $options ) && array_key_exists( 'matchcol', $options ) ){ + + // If only one matchval, create an array + if(!is_array($options['matchval'])){ + $options['matchval'] = array( $options['matchval'] ); + } + + foreach( $options['matchval'] as $matchval ){ + // Run the query for this match value (where x = y value) + $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?" ); + $results = $query->execute( array( $matchval ) ); + $newreturns = $this->insertData( $results, $options ); + $return = array_merge( $return, $newreturns ); + } + + } else { + // Just get everything + $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] ); + $results = $query->execute(); + $return = $this->insertData( $results, $options ); + + } + + return $return; + + } + + /** + * @breif saves a sql data set into migration.db + * @param $data a sql data set returned from self::prepare()->query() + * @param $options array of copyRows options + * @return void + */ + private function insertData( $data, $options ){ + $return = array(); + while( $row = $data->fetchRow() ){ + // Now save all this to the migration.db + foreach($row as $field=>$value){ + $fields[] = $field; + $values[] = $value; + } + + // Generate some sql + $sql = "INSERT INTO `" . $options['table'] . '` ( `'; + $fieldssql = implode( '`, `', $fields ); + $sql .= $fieldssql . "` ) VALUES( "; + $valuessql = substr( str_repeat( '?, ', count( $fields ) ),0,-2 ); + $sql .= $valuessql . " )"; + // Make the query + $query = $this->prepare( $sql ); + if( !$query ){ + OC_Log::write( 'migration', 'Invalid sql produced: '.$sql, OC_Log::FATAL ); + return false; + exit(); + } else { + $query->execute( $values ); + // Do we need to return some values? + if( array_key_exists( 'idcol', $options ) ){ + // Yes we do + $return[] = $row[$options['idcol']]; + } else { + // Take a guess and return the first field :) + $return[] = reset($row); + } + } + } + return $return; + } + + /** + * @breif adds a directory to the zip object + * @param $dir string path of the directory to add + * @param $recursive bool + * @param $internaldir string path of folder to add dir to in zip + * @return bool + */ + public function addDir( $dir, $recursive=true, $internaldir='' ) { + $dirname = basename($dir); + $this->zip->addEmptyDir($internaldir . $dirname); + $internaldir.=$dirname.='/'; + + if ($dirhandle = opendir($dir)) { + while (false !== ( $file = readdir($dirhandle))) { + + if (( $file != '.' ) && ( $file != '..' )) { + + if (is_dir($dir . '/' . $file) && $recursive) { + $this->addDir($dir . '/' . $file, $recursive, $internaldir); + } elseif (is_file($dir . '/' . $file)) { + $this->zip->addFile($dir . '/' . $file, $internaldir . $file); + } + } + } + closedir($dirhandle); + } else { + OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); + return false; + } + return true; + } + + /** + * @breif adds a file to the zip from a given string + * @param $data string of data to add + * @param $path the relative path inside of the zip to save the file to + * @return bool + */ + public function addFromString( $data, $path ){ + // Create a temp file + $file = tempnam( get_temp_dir(). '/', 'oc_export_tmp_' ); + $this->tmpfiles[] = $file; + if( !file_put_contents( $file, $data ) ){ + OC_Log::write( 'migation', 'Failed to save data to a temporary file', OC_Log::ERROR ); + return false; + } + // Add file to the zip + $this->zip->addFile( $file, $path ); + return true; + } + + /** + * @breif closes the zip, removes temp files + * @return bool + */ + public function finish(){ + if( !$this->zip->close() ){ + OC_Log::write( 'migration', 'Failed to write the zip file with error: '.$this->zip->getStatusString(), OC_Log::ERROR ); + return false; + } + $this->cleanup(); + return true; + } + + /** + * @breif cleans up after the zip + */ + private function cleanup(){ + // Delete tmp files + foreach($this->tmpfiles as $i){ + unlink( $i ); + } + } +} \ No newline at end of file diff --git a/lib/migration/provider.php b/lib/migration/provider.php new file mode 100644 index 0000000000..b9e2c47620 --- /dev/null +++ b/lib/migration/provider.php @@ -0,0 +1,49 @@ +id = $appid; + OC_Migrate::registerProvider( $this ); + } + + /** + * @breif exports data for apps + * @return array appdata to be exported + */ + abstract function export( ); + + /** + * @breif imports data for the app + * @return void + */ + abstract function import( ); + + /** + * @breif sets the OC_Migration_Content object to $this->content + * @param $content a OC_Migration_Content object + */ + public function setData( $uid, $content, $info=false, $appinfo=false ){ + $this->content = $content; + $this->uid = $uid; + $this->info = $info; + $this->appinfo = $appinfo; + } + + /** + * @breif returns the appid of the provider + * @return string + */ + public function getID(){ + return $this->id; + } +} From 3d8a09b14798724ba447d690f1f5502307da9f43 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Mon, 19 Mar 2012 21:42:59 +0100 Subject: [PATCH 055/302] Split the init function in lib/base.php a bit --- lib/base.php | 85 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/lib/base.php b/lib/base.php index 9995544f14..54cc9f2c73 100644 --- a/lib/base.php +++ b/lib/base.php @@ -114,42 +114,7 @@ class OC{ return($mode); } - public static function init(){ - // register autoloader - spl_autoload_register(array('OC','autoload')); - - // set some stuff - //ob_start(); - error_reporting(E_ALL | E_STRICT); - if (defined('DEBUG') && DEBUG){ - ini_set('display_errors', 1); - } - - date_default_timezone_set('Europe/Berlin'); - ini_set('arg_separator.output','&'); - - //set http auth headers for apache+php-cgi work around - if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) - { - list($name, $password) = explode(':', base64_decode($matches[1])); - $_SERVER['PHP_AUTH_USER'] = strip_tags($name); - $_SERVER['PHP_AUTH_PW'] = strip_tags($password); - } - - //set http auth headers for apache+php-cgi work around if variable gets renamed by apache - if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['REDIRECT_HTTP_AUTHORIZATION'], $matches)) - { - list($name, $password) = explode(':', base64_decode($matches[1])); - $_SERVER['PHP_AUTH_USER'] = strip_tags($name); - $_SERVER['PHP_AUTH_PW'] = strip_tags($password); - } - - // register the stream wrappers - require_once('streamwrappers.php'); - stream_wrapper_register("fakedir", "OC_FakeDirStream"); - stream_wrapper_register('static', 'OC_StaticStreamWrapper'); - stream_wrapper_register('close', 'OC_CloseStreamWrapper'); - + public static function initPaths(){ // calculate the documentroot OC::$DOCUMENTROOT=realpath($_SERVER['DOCUMENT_ROOT']); OC::$SERVERROOT=str_replace("\\",'/',substr(__FILE__,0,-13)); @@ -211,14 +176,18 @@ class OC{ get_include_path().PATH_SEPARATOR. OC::$SERVERROOT ); + } + public static function checkInstalled() { // Redirect to installer if not installed if (!OC_Config::getValue('installed', false) && OC::$SUBURI != '/index.php') { $url = 'http://'.$_SERVER['SERVER_NAME'].OC::$WEBROOT.'/index.php'; header("Location: $url"); exit(); } + } + public static function checkSSL() { // redirect to https site if configured if( OC_Config::getValue( "forcessl", false )){ ini_set("session.cookie_secure", "on"); @@ -228,7 +197,9 @@ class OC{ exit(); } } + } + public static function checkUpgrade() { if(OC_Config::getValue('installed', false)){ $installedVersion=OC_Config::getValue('version','0.0.0'); $currentVersion=implode('.',OC_Util::getVersion()); @@ -250,6 +221,48 @@ class OC{ OC_App::updateApps(); } + } + + public static function init(){ + // register autoloader + spl_autoload_register(array('OC','autoload')); + + // set some stuff + //ob_start(); + error_reporting(E_ALL | E_STRICT); + if (defined('DEBUG') && DEBUG){ + ini_set('display_errors', 1); + } + + date_default_timezone_set('Europe/Berlin'); + ini_set('arg_separator.output','&'); + + //set http auth headers for apache+php-cgi work around + if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) + { + list($name, $password) = explode(':', base64_decode($matches[1])); + $_SERVER['PHP_AUTH_USER'] = strip_tags($name); + $_SERVER['PHP_AUTH_PW'] = strip_tags($password); + } + + //set http auth headers for apache+php-cgi work around if variable gets renamed by apache + if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['REDIRECT_HTTP_AUTHORIZATION'], $matches)) + { + list($name, $password) = explode(':', base64_decode($matches[1])); + $_SERVER['PHP_AUTH_USER'] = strip_tags($name); + $_SERVER['PHP_AUTH_PW'] = strip_tags($password); + } + + // register the stream wrappers + require_once('streamwrappers.php'); + stream_wrapper_register("fakedir", "OC_FakeDirStream"); + stream_wrapper_register('static', 'OC_StaticStreamWrapper'); + stream_wrapper_register('close', 'OC_CloseStreamWrapper'); + + self::initPaths(); + self::checkInstalled(); + self::checkSSL(); + self::checkUpgrade(); ini_set('session.cookie_httponly','1;'); session_start(); From 632b3cbbbb7e2d70599d20a517b1b0a05aece294 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Mon, 19 Mar 2012 21:55:27 +0100 Subject: [PATCH 056/302] Cleanup the template code for redundant code --- lib/template.php | 255 ++++++++++++++++++++++++----------------------- 1 file changed, 128 insertions(+), 127 deletions(-) diff --git a/lib/template.php b/lib/template.php index eea2925975..5bcf52b932 100644 --- a/lib/template.php +++ b/lib/template.php @@ -140,7 +140,7 @@ class OC_Template{ /** * @brief Constructor * @param $app app providing the template - * @param $file name of the tempalte file (without suffix) + * @param $file name of the template file (without suffix) * @param $renderas = ""; produce a full page * @returns OC_Template object * @@ -151,10 +151,20 @@ class OC_Template{ * "admin". */ public function __construct( $app, $name, $renderas = "" ){ - // Read the selected theme from the config file - $theme=OC_Config::getValue( "theme" ); + // Set the private data + $this->renderas = $renderas; + $this->application = $app; + $this->vars = array(); + $this->l10n = new OC_L10N($app); - // Read the detected formfactor and use the right file name. + $this->findTemplate($name); + } + + /** + * @brief Returns the formfactor extention for current formfactor + */ + protected function getFormFactorExtension() + { $formfactor=$_SESSION['formfactor']; if($formfactor=='default') { $fext=''; @@ -167,70 +177,79 @@ class OC_Template{ }else{ $fext=''; } + return $fext; + } + /** + * @brief find the template with the given name + * @param $name of the template file (without suffix) + * + * Will select the template file for the selected theme and formfactor. + * Checking all the possible locations. + */ + protected function findTemplate($name) + { + // Read the selected theme from the config file + $theme=OC_Config::getValue( "theme" ); + + // Read the detected formfactor and use the right file name. + $fext = $this->getFormFactorExtension(); + + $app = $this->application; // Check if it is a app template or not. if( $app != "" ){ // Check if the app is in the app folder or in the root if( file_exists( OC::$APPSROOT."/apps/$app/templates/" )){ // Check if the template is overwritten by the selected theme - if( file_exists( OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"."$name.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"."$name.php"; - $path = OC::$SERVERROOT."/themes/$theme/apps/$app/templates/"; - }elseif( OC::$APPSROOT."/apps/$app/templates/"."$name$fext.php" ){ - $template = OC::$APPSROOT."/apps/$app/templates/"."$name$fext.php"; - $path = OC::$APPSROOT."/apps/$app/templates/"; - }else{ - $template = OC::$APPSROOT."/apps/$app/templates/"."$name.php"; - $path = OC::$APPSROOT."/apps/$app/templates/"; + if ($this->checkPathForTemplate(OC::$SERVERROOT."/themes/$theme/apps/$app/templates/", $name, $fext)) { + }elseif ($this->checkPathForTemplate(OC::$APPSROOT."/apps/$app/templates/", $name, $fext)) { } }else{ // Check if the template is overwritten by the selected theme - if( file_exists( OC::$SERVERROOT."/themes/$theme/$app/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/$app/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/themes/$theme/$app/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/themes/$theme/$app/templates/"."$name.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/$app/templates/"."$name.php"; - $path = OC::$SERVERROOT."/themes/$theme/$app/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/$app/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/$app/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/$app/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/$app/templates/"."$name.php" )){ - $template = OC::$SERVERROOT."/$app/templates/"."$name.php"; - $path = OC::$SERVERROOT."/$app/templates/"; + if ($this->checkPathForTemplate(OC::$SERVERROOT."/themes/$theme/$app/templates/", $name, $fext)) { + }elseif ($this->checkPathForTemplate(OC::$SERVERROOT."/$app/templates/", $name, $fext)) { }else{ - echo('template not found: template:'.$name.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + echo('template not found: template:'.$name.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); die(); } - } + } }else{ // Check if the template is overwritten by the selected theme - if( file_exists( OC::$SERVERROOT."/themes/$theme/core/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/core/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/themes/$theme/core/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/themes/$theme/core/templates/"."$name.php" )){ - $template = OC::$SERVERROOT."/themes/$theme/core/templates/"."$name.php"; - $path = OC::$SERVERROOT."/themes/$theme/core/templates/"; - }elseif( file_exists( OC::$SERVERROOT."/core/templates/"."$name$fext.php" )){ - $template = OC::$SERVERROOT."/core/templates/"."$name$fext.php"; - $path = OC::$SERVERROOT."/core/templates/"; + if ($this->checkPathForTemplate(OC::$SERVERROOT."/themes/$theme/core/templates/", $name, $fext)) { + } elseif ($this->checkPathForTemplate(OC::$SERVERROOT."/core/templates/", $name, $fext)) { }else{ - $template = OC::$SERVERROOT."/core/templates/"."$name.php"; - $path = OC::$SERVERROOT."/core/templates/"; + echo('template not found: template:'.$name.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + die(); } } + } - - // Set the private data - $this->renderas = $renderas; - $this->application = $app; - $this->template = $template; - $this->path = $path; - $this->vars = array(); - $this->l10n = new OC_L10N($app); + /** + * @brief check Path For Template with and without $fext + * @param $path to check + * @param $name of the template file (without suffix) + * @param $fext formfactor extension + * @return bool true when found + * + * Will set $this->template and $this->path if there is a template at + * the specifief $path + */ + protected function checkPathForTemplate($path, $name, $fext) + { + if ($name =='') return false; + $template = null; + if( is_file( $path.$name.$fext.'.php' )){ + $template = $path.$name.$fext.'.php'; + }elseif( is_file( $path.$name.'.php' )){ + $template = $path.$name.'.php'; + } + if ($template) { + $this->template = $template; + $this->path = $path; + return true; + } + return false; } /** @@ -267,7 +286,7 @@ class OC_Template{ $this->vars[$key] = array( $value ); } } - + /** * @brief Add a custom element to the header * @param string tag tag name of the element @@ -295,11 +314,25 @@ class OC_Template{ } } + /* + * @brief append the $file-url if exist at $root + * @param $type of collection to use when appending + * @param $root path to check + * @param $web base for path + * @param $file the filename + */ + public function appendIfExist($type, $root, $web, $file) { + if (is_file($root.'/'.$file)) { + $this->append( $type, $web.'/'.$file); + return true; + } + return false; + } /** * @brief Proceeds the template * @returns content * - * This function proceeds the template. If $this->renderas is set, it will + * This function proceeds the template. If $this->renderas is set, it * will produce a full page. */ public function fetchPage(){ @@ -329,68 +362,44 @@ class OC_Template{ }else{ $page = new OC_Template( "core", "layout.guest" ); } - + // Read the selected theme from the config file $theme=OC_Config::getValue( "theme" ); // Read the detected formfactor and use the right file name. - $formfactor=$_SESSION['formfactor']; - if($formfactor=='default') { - $fext=''; - }elseif($formfactor=='mobile') { - $fext='.mobile'; - }elseif($formfactor=='tablet') { - $fext='.tablet'; - }elseif($formfactor=='standalone') { - $fext='.standalone'; - }else{ - $fext=''; - } + $fext = $this->getFormFactorExtension(); // Add the core js files or the js files provided by the selected theme foreach(OC_Util::$scripts as $script){ // Is it in 3rd party? - if(is_file(OC::$THIRDPARTYROOT."/$script.js" )){ - $page->append( "jsfiles", OC::$THIRDPARTYWEBROOT."/$script.js" ); + if($page->appendIfExist('jsfiles', OC::$THIRDPARTYROOT, OC::$THIRDPARTYWEBROOT, $script.'.js')) { // Is it in apps and overwritten by the theme? - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/apps/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/apps/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/apps/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/apps/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/apps/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/apps/$script.js" )) { // Is it part of an app? - }elseif(is_file(OC::$APPSROOT."/apps/$script$fext.js" )){ - $page->append( "jsfiles", OC::$APPSWEBROOT."/apps/$script$fext.js" ); - }elseif(is_file(OC::$APPSROOT."/apps/$script.js" )){ - $page->append( "jsfiles", OC::$APPSWEBROOT."/apps/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$APPSROOT, OC::$APPSWEBROOT, "apps/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$APPSROOT, OC::$APPSWEBROOT, "apps/$script.js" )) { // Is it in the owncloud root but overwritten by the theme? - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/$script.js" ); - + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/$script.js" )) { + // Is it in the owncloud root ? - }elseif(is_file(OC::$SERVERROOT."/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "$script.js" )) { // Is in core but overwritten by a theme? - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/core/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/core/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/core/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/themes/$theme/core/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/core/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/core/$script.js" )) { // Is it in core? - }elseif(is_file(OC::$SERVERROOT."/core/$script$fext.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/core/$script$fext.js" ); - }elseif(is_file(OC::$SERVERROOT."/core/$script.js" )){ - $page->append( "jsfiles", OC::$WEBROOT."/core/$script.js" ); + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "core/$script$fext.js" )) { + }elseif($page->appendIfExist('jsfiles', OC::$SERVERROOT, OC::$WEBROOT, "core/$script.js" )) { }else{ - echo('js file not found: script:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + echo('js file not found: script:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); die(); } @@ -398,54 +407,46 @@ class OC_Template{ // Add the css files foreach(OC_Util::$styles as $style){ // is it in 3rdparty? - if(is_file(OC::$THIRDPARTYROOT."/$style.css" )){ - $page->append( "cssfiles", OC::$THIRDPARTYWEBROOT."/$style.css" ); + if($page->appendIfExist('cssfiles', OC::$THIRDPARTYROOT, OC::$THIRDPARTYWEBROOT, $style.'.css')) { + // or in apps? - }elseif(is_file(OC::$APPSROOT."/apps/$style$fext.css" )){ - $page->append( "cssfiles", OC::$APPSWEBROOT."/apps/$style$fext.css" ); - }elseif(is_file(OC::$APPSROOT."/apps/$style.css" )){ - $page->append( "cssfiles", OC::$APPSWEBROOT."/apps/$style.css" ); + }elseif($page->appendIfExist('cssfiles', OC::$APPSROOT, OC::$APPSWEBROOT, "apps/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$APPSROOT, OC::$APPSWEBROOT, "apps/$style.css" )) { + // or in the owncloud root? - }elseif(is_file(OC::$SERVERROOT."/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/$style.css" ); - // or in core ? - }elseif(is_file(OC::$SERVERROOT."/core/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/core/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/core/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/core/$style.css" ); + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "$style.css" )) { + + // or in core ? + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "core/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "core/$style.css" )) { }else{ - echo('css file not found: style:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); + echo('css file not found: style:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT); die(); } } - // Add the theme css files. you can override the default values here + // Add the theme css files. you can override the default values here if(!empty($theme)) { - foreach(OC_Util::$styles as $style){ - if(is_file(OC::$SERVERROOT."/themes/$theme/apps/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/apps/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/apps/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/apps/$style.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/$style.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/core/$style$fext.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/core/$style$fext.css" ); - }elseif(is_file(OC::$SERVERROOT."/themes/$theme/core/$style.css" )){ - $page->append( "cssfiles", OC::$WEBROOT."/themes/$theme/core/$style.css" ); - } - } - } - + foreach(OC_Util::$styles as $style){ + if($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/apps/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/apps/$style.css" )) { + + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/$style.css" )) { + + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/core/$style$fext.css" )) { + }elseif($page->appendIfExist('cssfiles', OC::$SERVERROOT, OC::$WEBROOT, "themes/$theme/core/$style.css" )) { + } + } + } + // Add custom headers $page->assign('headers',$this->headers); foreach(OC_Util::$headers as $header){ $page->append('headers',$header); } - + // Add css files and js files $page->assign( "content", $data ); return $page->fetchPage(); From 89c39b47bcbe3b5085e9a528a6dc0afcff161f37 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 20 Mar 2012 18:46:56 +0100 Subject: [PATCH 057/302] add microsoft office mimetypes to the fixlist - bugfix for oc-357 --- lib/mimetypes.fixlist.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/mimetypes.fixlist.php b/lib/mimetypes.fixlist.php index 1c6acbc443..51f12dbcc2 100644 --- a/lib/mimetypes.fixlist.php +++ b/lib/mimetypes.fixlist.php @@ -10,5 +10,11 @@ return array( 'pl'=>'text/x-script.perl', 'py'=>'text/x-script.phyton', 'vcf' => 'text/vcard', - 'vcard' => 'text/vcard' + 'vcard' => 'text/vcard', + 'doc'=>'application/msword', + 'docx'=>'application/msword', + 'xls'=>'application/msexcel', + 'xlsx'=>'application/msexcel', + 'ppt'=>'application/mspowerpoint', + 'pptx'=>'application/mspowerpoint' ); From 19bbb61b20e1e9a7c7d4f9ef645ab65fcc453416 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 20 Mar 2012 19:48:08 +0100 Subject: [PATCH 058/302] fix spelling fail in texteditor's style.css --- apps/files_texteditor/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_texteditor/css/style.css b/apps/files_texteditor/css/style.css index e260ab0dd4..6a4392a08e 100644 --- a/apps/files_texteditor/css/style.css +++ b/apps/files_texteditor/css/style.css @@ -5,7 +5,7 @@ left: 12.5em; } #editorwrapper{ - position: absoloute; + position: absolute; height: 0; width: 0; top: 41px; From 514c9ad8e7df1d7882adc33c42eb32a209537273 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 20 Mar 2012 20:19:21 +0000 Subject: [PATCH 059/302] Added unified import method. --- apps/admin_export/settings.php | 10 +- apps/bookmarks/lib/migrate.php | 10 +- apps/user_migrate/admin.php | 18 +--- lib/db.php | 7 +- lib/migrate.php | 188 +++++++++++++++++++++++++++++++-- lib/migration/content.php | 6 +- lib/migration/provider.php | 10 +- 7 files changed, 206 insertions(+), 43 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index af8dd0dbf5..cd85bedcd7 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -25,8 +25,6 @@ OC_Util::checkAdminUser(); OC_Util::checkAppEnabled('admin_export'); -define('DS', '/'); - // Export? if (isset($_POST['admin_export'])) { // Create the export zip @@ -44,9 +42,11 @@ if (isset($_POST['admin_export'])) { } // Import? } else if( isset($_POST['admin_import']) ){ - - // TODO - // OC_Migrate::import( $pathtozipfile ); + $from = $_FILES['owncloud_import']['tmp_name']; + + if( !OC_Migrate::import( $from ) ){ + die('failed'); + } } else { // fill template diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/lib/migrate.php index 36a08c0cf4..02c96e5963 100644 --- a/apps/bookmarks/lib/migrate.php +++ b/apps/bookmarks/lib/migrate.php @@ -36,19 +36,19 @@ class OC_Migration_Provider_Bookmarks extends OC_Migration_Provider{ switch( $this->appinfo->version ){ default: // All versions of the app have had the same db structure, so all can use the same import function - $query = OC_Migrate::prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); - $results = $query->execute( array( $this->info['olduid'] ) ); + $query = $this->content->prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); + $results = $query->execute( array( $this->olduid ) ); $idmap = array(); - while( $row = $data->fetchRow() ){ + while( $row = $results->fetchRow() ){ // Import each bookmark, saving its id into the map $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks(url, title, user_id, public, added, lastmodified) VALUES (?, ?, ?, ?, ?, ?)" ); - $query->execute( array( $row['url'], $row['title'], $this->info['newuid'], $row['public'], $row['added'], $row['lastmodified'] ) ); + $query->execute( array( $row['url'], $row['title'], $this->uid, $row['public'], $row['added'], $row['lastmodified'] ) ); // Map the id $idmap[$row['id']] = OC_DB::insertid(); } // Now tags foreach($idmap as $oldid => $newid){ - $query = OC_Migrate::prepare( "SELECT * FROM bookmarks_tags WHERE user_id LIKE ?" ); + $query = $this->content->prepare( "SELECT * FROM bookmarks_tags WHERE user_id LIKE ?" ); $results = $query->execute( array( $oldid ) ); while( $row = $data->fetchRow() ){ // Import the tags for this bookmark, using the new bookmark id diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index 6f3565788e..d54bd6965b 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -36,24 +36,12 @@ if (isset($_POST['user_import'])) { $from = $_FILES['owncloud_import']['tmp_name']; $to = get_temp_dir().'/'.$importname.'.zip'; if( !move_uploaded_file( $from, $to ) ){ - OC_Log::write('migration',"Failed to copy the uploaded file",OC_Log::INFO); + OC_Log::write( 'user_migrate', "Failed to copy the uploaded file", OC_Log::ERROR ); exit(); } - // Extract zip - $zip = new ZipArchive(); - if ($zip->open(get_temp_dir().'/'.$importname.'.zip') != TRUE) { - OC_Log::write('migration',"Failed to open zip file",OC_Log::INFO); - exit(); - } - $zip->extractTo(get_temp_dir().'/'.$importname.'/'); - $zip->close(); - - $importdir = get_temp_dir() . '/' . $importname; - - // Delete uploaded file - unlink( $importdir . '.zip' ); - + OC_Migrate::import( $to, 'user', 'newuser' ); + die(); // Find folder $files = scandir( $importdir ); unset($files[0]); diff --git a/lib/db.php b/lib/db.php index 07e5859096..bfcff67869 100644 --- a/lib/db.php +++ b/lib/db.php @@ -487,6 +487,7 @@ class OC_DB { /** * @breif replaces the owncloud tables with a new set + * @param $file string path to the MDB2 xml db export file */ public static function replaceDB( $file ){ @@ -503,7 +504,11 @@ class OC_DB { } // Create new tables - self::createDBFromStructure( $file ); + if( self::createDBFromStructure( $file ) ){ + return true; + } else { + return false; + } } diff --git a/lib/migrate.php b/lib/migrate.php index 338d091af8..0058de7391 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -122,7 +122,6 @@ class OC_Migrate{ } } // Create the zip object - self::$zip = new ZipArchive; if( !self::createZip() ){ return false; } @@ -188,6 +187,177 @@ class OC_Migrate{ return self::$zippath; } + /** + * @breif imports a user, or owncloud instance + * @param $path string path to zip + * @param optional $uid userid of new user + */ + public static function import( $path, $uid=null ){ + OC_Util::checkAdminUser(); + $datadir = OC_Config::getValue( 'datadirectory' ); + // Extract the zip + if( !$extractpath = self::extractZip( $path ) ){ + return false; + } + // Get export_info.json + $scan = scandir( $extractpath ); + // Check for export_info.json + if( !in_array( 'export_info.json', $scan ) ){ + OC_Log::write( 'migration', 'Invalid import file, export_info.json note found', OC_Log::ERROR ); + return false; + } + $json = json_decode( file_get_contents( $extractpath . 'export_info.json' ) ); + self::$exporttype = $json->exporttype; + + // Have we got a user if type is user + if( self::$exporttype == 'user' ){ + if( !$uid ){ + self::$uid = $json->exporteduser; + } else { + self::$uid = $uid; + } + } + + // Handle export types + switch( self::$exporttype ){ + case 'user': + // Check user availability + if( OC_User::userExists( self::$uid ) ){ + OC_Log::write( 'migration', 'User already exists', OC_Log::ERROR ); + return false; + } + // Create the user + if( !self::createUser( self::$uid, $json->hash ) ){ + return false; + } + // Make the new users data dir + $path = $datadir . '/' . self::$uid . '/files/'; + if( !mkdir( $path, 0755, true ) ){ + OC_Log::write( 'migration', 'Failed to create users data dir: '.$path, OC_Log::ERROR ); + return false; + } + // Copy data + if( !self::copy_r( $extractpath . $json->exporteduser . '/files', $datadir . '/' . self::$uid . '/files' ) ){ + return false; + } + // Import user app data + if( !self::importAppData( $extractpath . $json->exporteduser . '/migration.db', $json, self::$uid ) ){ + return false; + } + // All done! + if( !self::unlink_r( $extractpath ) ){ + OC_Log::write( 'migration', 'Failed to delete the extracted zip', OC_Log::ERROR ); + } + return true; + break; + case 'instance': + // Check for new data dir and dbexport before doing anything + // TODO + /* + // Delete current data folder. + OC_Log::write( 'migration', "Deleting current data dir", OC_Log::INFO ); + if( self::unlink_r( $datadir, false ) ){ + OC_Log::write( 'migration', 'Failed to delete the current data dir', OC_Log::ERROR ); + return false; + } + + // Copy over data + if( !self::copy_r( $extractname . 'data', $datadir ) ){ + OC_Log::write( 'migration', 'Failed to copy over data directory', OC_Log::ERROR ); + return false; + } + */ + // Import the db + if( !OC_DB::replaceDB( $extractpath . 'dbexport.xml' ) ){ + return false; + } + // Done + return true; + break; + } + + } + + /** + * @breif recursively deletes a directory + * @param $dir string path of dir to delete + * $param optional $deleteRootToo bool delete the root directory + * @return bool + */ + private static function unlink_r( $dir, $deleteRootToo=true ){ + if( !$dh = @opendir( $dir ) ){ + return false; + } + while (false !== ($obj = readdir($dh))){ + if($obj == '.' || $obj == '..') { + continue; + } + if (!@unlink($dir . '/' . $obj)){ + self::unlink_r($dir.'/'.$obj, true); + } + } + closedir($dh); + if ( $deleteRootToo ) { + @rmdir($dir); + } + return true; + } + + /** + * @breif copies recursively + * @param $path string path to source folder + * @param $dest string path to destination + * @return bool + */ + private static function copy_r( $path, $dest ){ + if( is_dir($path) ){ + @mkdir( $dest ); + $objects = scandir( $path ); + if( sizeof( $objects ) > 0 ){ + foreach( $objects as $file ){ + if( $file == "." || $file == ".." ) + continue; + // go on + if( is_dir( $path . '/' . $file ) ){ + self::copy_r( $path .'/' . $file, $dest . '/' . $file ); + } else { + copy( $path . '/' . $file, $dest . '/' . $file ); + } + } + } + return true; + } + elseif( is_file( $path ) ){ + return copy( $path, $dest ); + } else { + return false; + } + } + + /** + * @breif tries to extract the import zip + * @param $path string path to the zip + * @return string path to extract location (with a trailing slash) or false on failure + */ + static private function extractZip( $path ){ + self::$zip = new ZipArchive; + // Validate path + if( !file_exists( $path ) ){ + OC_Log::write( 'migration', 'Zip not found', OC_Log::ERROR ); + return false; + } + if ( self::$zip->open( $path ) != TRUE ) { + OC_Log::write( 'migration', "Failed to open zip file", OC_Log::ERROR ); + return false; + } + $to = get_temp_dir() . '/oc_import_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '/'; + if( !self::$zip->extractTo( $to ) ){ + return false; + } + self::$zip->close(); + return $to; + } + /** * @brief connects to a MDB2 database scheme * @returns bool @@ -272,7 +442,6 @@ class OC_Migrate{ $result = $query->execute( array( self::$uid ) ); $row = $result->fetchRow(); $hash = $row ? $row['password'] : false; - die(var_dump($hash)); if( !$hash ){ OC_Log::write( 'migration', 'Failed to get the users password hash', OC_log::ERROR); return false; @@ -287,7 +456,7 @@ class OC_Migrate{ $info = array_merge( $info, (array)$array ); // Create json $json = json_encode( $info ); - return true; + return $json; } /** @@ -399,8 +568,9 @@ class OC_Migrate{ * @return bool */ static private function createZip(){ + self::$zip = new ZipArchive; // Check if properties are set - if( !self::$zip || !self::$zippath ){ + if( !self::$zippath ){ OC_Log::write('migration', 'createZip() called but $zip and/or $zippath have not been set', OC_Log::ERROR); return false; } @@ -435,9 +605,6 @@ class OC_Migrate{ * @return bool if the import succedded */ public static function importAppData( $db, $info, $uid=null ){ - - self::$uid = !is_null( $uid ) ? $uid : $info->exporteduser; - // Check if the db exists if( file_exists( $db ) ){ // Connect to the db @@ -466,12 +633,11 @@ class OC_Migrate{ // Did it succeed? if( $info->apps->$id->success ){ // Give the provider the content object - // TODO PASS THE PATH TO MIGRATION.DB - if( !self::connectDB() ){ + if( !self::connectDB( $db ) ){ return false; } - $content = new OC_Migration_Content( self::$zip, self::$db ); - $provider->setObject( $content ); + $content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + $provider->setData( self::$uid, $content, $info ); // Then do the import $provider->import( $info->apps->$id, $importinfo ); } diff --git a/lib/migration/content.php b/lib/migration/content.php index fe8a21a45b..d25b5af293 100644 --- a/lib/migration/content.php +++ b/lib/migration/content.php @@ -53,7 +53,7 @@ class OC_Migration_Content{ $query = $this->processQuery( $query ); // Optimize the query - $query = $this->MDB2->prepare( $query ); + $query = $this->db->prepare( $query ); // Die if we have an error (error means: bad query, not 0 results!) if( PEAR::isError( $query ) ) { @@ -174,7 +174,9 @@ class OC_Migration_Content{ $dirname = basename($dir); $this->zip->addEmptyDir($internaldir . $dirname); $internaldir.=$dirname.='/'; - + if( !file_exists( $dir ) ){ + return false; + } if ($dirhandle = opendir($dir)) { while (false !== ( $file = readdir($dirhandle))) { diff --git a/lib/migration/provider.php b/lib/migration/provider.php index b9e2c47620..d592ed6726 100644 --- a/lib/migration/provider.php +++ b/lib/migration/provider.php @@ -7,7 +7,7 @@ abstract class OC_Migration_Provider{ protected $id=false; protected $content=false; protected $uid=false; - protected $info=false; + protected $olduid=false; protected $appinfo=false; public function __construct( $appid ){ @@ -32,11 +32,13 @@ abstract class OC_Migration_Provider{ * @breif sets the OC_Migration_Content object to $this->content * @param $content a OC_Migration_Content object */ - public function setData( $uid, $content, $info=false, $appinfo=false ){ + public function setData( $uid, $content, $info=false ){ $this->content = $content; $this->uid = $uid; - $this->info = $info; - $this->appinfo = $appinfo; + $this->olduid = $info->exporteduser; + $id = $this->id; + $this->appinfo = $info->apps->$id; + } /** From 0fa5e196ef8d0b220e4af17b008fb4908b080445 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 20 Mar 2012 20:32:01 +0000 Subject: [PATCH 060/302] Try to use old uid when importing --- apps/user_migrate/admin.php | 65 ++----------------------------- apps/user_migrate/appinfo/app.php | 1 - lib/migrate.php | 11 ++++-- 3 files changed, 11 insertions(+), 66 deletions(-) diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index d54bd6965b..c1afb0aed4 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -40,69 +40,10 @@ if (isset($_POST['user_import'])) { exit(); } - OC_Migrate::import( $to, 'user', 'newuser' ); - die(); - // Find folder - $files = scandir( $importdir ); - unset($files[0]); - unset($files[1]); - - // Get the user - if( count($files) != 1 ){ - OC_Log::write('migration', 'Invalid import file', OC_Log::ERROR); - die('invalid import, no user included'); + if( !OC_Migrate::import( $to, 'user' ) ){ + die( 'failed to to import' ); } - - $olduser = reset($files); - - // Check for dbexport.xml and export info and data dir - $files = scandir( $importdir . '/' . $olduser ); - - $required = array( 'migration.db', 'export_info.json', 'files'); - foreach($required as $require){ - if( !in_array( $require, $files) ){ - OC_Log::write('migration', 'Invlaid import file', OC_Log::ERROR); - die('invalid import'); - } - } - - $migrateinfo = $importdir . '/' . $olduser . '/export_info.json'; - $migrateinfo = json_decode( file_get_contents( $migrateinfo ) ); - - // Check if uid is available - if( OC_User::UserExists( $olduser ) ){ - OC_Log::write('migration','Username exists', OC_Log::ERROR); - die('user exists'); - } - - // Create the user - if( !OC_Migrate::createUser( $olduser, $migrateinfo->hash ) ){ - OC_Log::write('migration', 'Failed to create the new user', OC_Log::ERROR); - die('coundlt create new user'); - } - - $datadir = OC_Config::getValue( 'datadirectory' ); - // Make the new users data dir - $path = $datadir . '/' . $olduser . '/files/'; - if( !mkdir( $path, 0755, true ) ){ - OC_Log::write('migration','Failed to create users data dir: '.$path, OC_Log::ERROR); - die('failed to create users data dir'); - } - - // Copy data - if( !copy_r( $importdir . '/' . $olduser . '/files', $datadir . '/' . $olduser . '/files' ) ){ - OC_Log::write('migration','Failed to copy user files to destination', OC_Log::ERROR); - die('failed to copy user files'); - } - - // Import user data - if( !OC_Migrate::importAppData( $importdir . '/' . $olduser . '/migration.db', $migrateinfo ) ){ - OC_Log::write('migration','Failed to import user data', OC_Log::ERROR); - die('failed to import user data'); - } - - // All done! - die('done'); + } else { // fill template diff --git a/apps/user_migrate/appinfo/app.php b/apps/user_migrate/appinfo/app.php index 18b97b93df..4b9cbd1308 100644 --- a/apps/user_migrate/appinfo/app.php +++ b/apps/user_migrate/appinfo/app.php @@ -22,7 +22,6 @@ */ OC_APP::registerPersonal('user_migrate','settings'); -OC_APP::registerAdmin('user_migrate','admin'); // add settings page to navigation $entry = array( diff --git a/lib/migrate.php b/lib/migrate.php index 0058de7391..8b0a2aa3f7 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -190,9 +190,10 @@ class OC_Migrate{ /** * @breif imports a user, or owncloud instance * @param $path string path to zip + * @param optional $type type of import (user or instance) * @param optional $uid userid of new user */ - public static function import( $path, $uid=null ){ + public static function import( $path, $type='user', $uid=null ){ OC_Util::checkAdminUser(); $datadir = OC_Config::getValue( 'datadirectory' ); // Extract the zip @@ -207,8 +208,12 @@ class OC_Migrate{ return false; } $json = json_decode( file_get_contents( $extractpath . 'export_info.json' ) ); - self::$exporttype = $json->exporttype; - + if( !$json->exporttype != $type ){ + OC_Log::write( 'migration', 'Invalid import file', OC_Log::ERROR ); + return false; + } + self::$exporttype = $type; + // Have we got a user if type is user if( self::$exporttype == 'user' ){ if( !$uid ){ From 07d7138df08834acc1ac6ff019859ceca09d4690 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 20 Mar 2012 20:34:20 +0000 Subject: [PATCH 061/302] Register admin pane --- apps/user_migrate/appinfo/app.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/user_migrate/appinfo/app.php b/apps/user_migrate/appinfo/app.php index 4b9cbd1308..18ea8f52b2 100644 --- a/apps/user_migrate/appinfo/app.php +++ b/apps/user_migrate/appinfo/app.php @@ -21,7 +21,8 @@ * */ -OC_APP::registerPersonal('user_migrate','settings'); +OC_APP::registerPersonal( 'user_migrate', 'settings' ); +OC_APP::registerAdmin( 'user_migrate', 'admin' ); // add settings page to navigation $entry = array( From 7c815054c7bc43d1147294faddbb822c099a1f53 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 21 Mar 2012 13:05:15 +0100 Subject: [PATCH 062/302] Config file is more appropriate here adjust default value --- files/admin.php | 8 ++++---- files/templates/index.php | 4 ++-- lib/files.php | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/files/admin.php b/files/admin.php index b9c26c465f..1fe1ff55a4 100644 --- a/files/admin.php +++ b/files/admin.php @@ -35,16 +35,16 @@ if($_POST) { } if(isset($_POST['maxZipInputSize'])) { $maxZipInputSize=$_POST['maxZipInputSize']; - OC_Preferences::setValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize($maxZipInputSize)); + OC_Config::setValue('maxZipInputSize', OC_Helper::computerFileSize($maxZipInputSize)); } - OC_Preferences::setValue('', 'files', 'allowZipDownload', isset($_POST['allowZipDownload'])); + OC_Config::setValue('allowZipDownload', isset($_POST['allowZipDownload'])); }else{ $upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); $post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); $maxUploadFilesize = min($upload_max_filesize, $post_max_size); - $allowZipDownload = intval(OC_Preferences::getValue('', 'files', 'allowZipDownload', 1)); - $maxZipInputSize = OC_Helper::humanfilesize(OC_Preferences::getValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize('800 MB'))); + $maxZipInputSize = OC_Helper::humanfilesize(OC_Config::getValue('maxZipInputSize', OC_Helper::computerFileSize('800 MB'))); } +$allowZipDownload = intval(OC_Config::getValue('allowZipDownload', true)); OC_App::setActiveNavigationEntry( "files_administration" ); diff --git a/files/templates/index.php b/files/templates/index.php index 497a0f36c0..3302cfa929 100644 --- a/files/templates/index.php +++ b/files/templates/index.php @@ -40,7 +40,7 @@ t( 'Name' ); ?> - + Download" /> @@ -70,4 +70,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/lib/files.php b/lib/files.php index 662f0b5972..57ebb9005a 100644 --- a/lib/files.php +++ b/lib/files.php @@ -224,7 +224,7 @@ class OC_Files { * @param files $files */ static function validateZipDownload($dir, $files) { - if(!OC_Preferences::getValue('', 'files', 'allowZipDownload', 1)) { + if(!OC_Config::getValue('allowZipDownload', true)) { $l = new OC_L10N('files'); header("HTTP/1.0 409 Conflict"); $tmpl = new OC_Template( '', 'error', 'user' ); @@ -239,7 +239,7 @@ class OC_Files { exit; } - $zipLimit = OC_Preferences::getValue('', 'files', 'maxZipInputSize', OC_Helper::computerFileSize('800 MB')); + $zipLimit = OC_Config::getValue('maxZipInputSize', OC_Helper::computerFileSize('800 MB')); if($zipLimit > 0) { $totalsize = 0; if(is_array($files)){ From d91bc9b317b440a0720fe9b7ca3f43538d840af8 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 21 Mar 2012 13:10:02 +0100 Subject: [PATCH 063/302] use placeholder in template file --- files/index.php | 1 + files/templates/index.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/files/index.php b/files/index.php index 79261e495b..82d0960892 100644 --- a/files/index.php +++ b/files/index.php @@ -98,6 +98,7 @@ $tmpl->assign( 'readonly', !OC_Filesystem::is_writable($dir)); $tmpl->assign( "files", $files ); $tmpl->assign( 'uploadMaxFilesize', $maxUploadFilesize); $tmpl->assign( 'uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize)); +$tmpl->assign( 'allowZipDownload', intval(OC_Config::getValue('allowZipDownload', true))); $tmpl->printPage(); ?> diff --git a/files/templates/index.php b/files/templates/index.php index 3302cfa929..da1e58ce13 100644 --- a/files/templates/index.php +++ b/files/templates/index.php @@ -40,7 +40,7 @@ t( 'Name' ); ?> - + Download" /> @@ -70,4 +70,4 @@ - \ No newline at end of file + \ No newline at end of file From 892343c7c11e7ba967f69174820a501b3954badb Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Wed, 21 Mar 2012 16:30:59 +0000 Subject: [PATCH 064/302] Fix instance import --- apps/admin_export/settings.php | 2 +- lib/migrate.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index cd85bedcd7..269147a3bc 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -44,7 +44,7 @@ if (isset($_POST['admin_export'])) { } else if( isset($_POST['admin_import']) ){ $from = $_FILES['owncloud_import']['tmp_name']; - if( !OC_Migrate::import( $from ) ){ + if( !OC_Migrate::import( $from, 'instance' ) ){ die('failed'); } diff --git a/lib/migrate.php b/lib/migrate.php index 8b0a2aa3f7..718dc57b9f 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -208,7 +208,7 @@ class OC_Migrate{ return false; } $json = json_decode( file_get_contents( $extractpath . 'export_info.json' ) ); - if( !$json->exporttype != $type ){ + if( $json->exporttype != $type ){ OC_Log::write( 'migration', 'Invalid import file', OC_Log::ERROR ); return false; } From 5b03de9a66cf418ea2f269c9f061f44ca2146eb7 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 21 Mar 2012 11:57:15 +0100 Subject: [PATCH 065/302] fix comments in crypt lib --- apps/files_encryption/lib/crypt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 0a593b98c4..246d4f672d 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -26,7 +26,7 @@ // - Crypt/decrypt button in the userinterface // - Setting if crypto should be on by default // - Add a setting "Don´t encrypt files larger than xx because of performance reasons" -// - Transparent decrypt/encrpt in filesystem.php. Autodetect if a file is encrypted (.encrypted extensio) +// - Transparent decrypt/encrypt in filesystem.php. Autodetect if a file is encrypted (.encrypted extension) // - Don't use a password directly as encryption key. but a key which is stored on the server and encrypted with the user password. -> password change faster // - IMPORTANT! Check if the block lenght of the encrypted data stays the same From 0a93d4eccf405f7145694a21c4412ba213b50a4a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 22 Mar 2012 19:54:24 +0100 Subject: [PATCH 066/302] loosen tests for mtime and ctime a bit --- tests/lib/filestorage.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/lib/filestorage.php b/tests/lib/filestorage.php index 9ffa0eca9c..4858234a2d 100644 --- a/tests/lib/filestorage.php +++ b/tests/lib/filestorage.php @@ -135,10 +135,12 @@ abstract class Test_FileStorage extends UnitTestCase { $ctimeEnd=time(); $cTime=$this->instance->filectime('/lorem.txt'); $mTime=$this->instance->filemtime('/lorem.txt'); - $this->assertTrue($ctimeStart<=$cTime); - $this->assertTrue($cTime<=$ctimeEnd); - $this->assertTrue($ctimeStart<=$mTime); - $this->assertTrue($mTime<=$ctimeEnd); + if($cTime!=-1){//not everything can support ctime + $this->assertTrue(($ctimeStart-1)<=$cTime); + $this->assertTrue($cTime<=($ctimeEnd+1)); + } + $this->assertTrue(($ctimeStart-1)<=$mTime); + $this->assertTrue($mTime<=($ctimeEnd+1)); $this->assertEqual(filesize($textFile),$this->instance->filesize('/lorem.txt')); $stat=$this->instance->stat('/lorem.txt'); @@ -153,8 +155,8 @@ abstract class Test_FileStorage extends UnitTestCase { $originalCTime=$cTime; $cTime=$this->instance->filectime('/lorem.txt'); $mTime=$this->instance->filemtime('/lorem.txt'); - $this->assertTrue($mtimeStart<=$mTime); - $this->assertTrue($mTime<=$mtimeEnd); + $this->assertTrue(($mtimeStart-1)<=$mTime); + $this->assertTrue($mTime<=($mtimeEnd+1)); $this->assertEqual($cTime,$originalCTime); if($this->instance->touch('/lorem.txt',100)!==false){ @@ -170,8 +172,8 @@ abstract class Test_FileStorage extends UnitTestCase { $mtimeEnd=time(); $originalCTime=$cTime; $mTime=$this->instance->filemtime('/lorem.txt'); - $this->assertTrue($mtimeStart<=$mTime); - $this->assertTrue($mTime<=$mtimeEnd); + $this->assertTrue(($mtimeStart-1)<=$mTime); + $this->assertTrue($mTime<=($mtimeEnd+1)); } public function testSearch(){ From 82b54938e32a980d3d268a63fdf56598f12e2ff4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 22 Mar 2012 19:56:19 +0100 Subject: [PATCH 067/302] ftp storage backend --- apps/files_remote/appinfo/app.php | 9 ++ apps/files_remote/appinfo/info.xml | 10 ++ apps/files_remote/lib/ftp.php | 157 +++++++++++++++++++++++++++++ apps/files_remote/tests/config.php | 9 ++ apps/files_remote/tests/ftp.php | 28 +++++ 5 files changed, 213 insertions(+) create mode 100644 apps/files_remote/appinfo/app.php create mode 100644 apps/files_remote/appinfo/info.xml create mode 100644 apps/files_remote/lib/ftp.php create mode 100644 apps/files_remote/tests/config.php create mode 100644 apps/files_remote/tests/ftp.php diff --git a/apps/files_remote/appinfo/app.php b/apps/files_remote/appinfo/app.php new file mode 100644 index 0000000000..f94e813ea5 --- /dev/null +++ b/apps/files_remote/appinfo/app.php @@ -0,0 +1,9 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_remote/lib/ftp.php'; diff --git a/apps/files_remote/appinfo/info.xml b/apps/files_remote/appinfo/info.xml new file mode 100644 index 0000000000..0720b6095b --- /dev/null +++ b/apps/files_remote/appinfo/info.xml @@ -0,0 +1,10 @@ + + + files_remote + Remote storage support + Mount remote storage sources + 0.1 + AGPL + Robin Appelman + 3 + diff --git a/apps/files_remote/lib/ftp.php b/apps/files_remote/lib/ftp.php new file mode 100644 index 0000000000..802446b4fd --- /dev/null +++ b/apps/files_remote/lib/ftp.php @@ -0,0 +1,157 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_FileStorage_FTP extends OC_Filestorage_Common{ + private $password; + private $user; + private $host; + private $secure; + private $root; + + private static $tempFiles=array(); + + public function __construct($params){ + $this->host=$params['host']; + $this->user=$params['user']; + $this->password=$params['password']; + $this->secure=isset($params['secure'])?(bool)$params['secure']:false; + $this->root=isset($params['root'])?$params['root']:'/'; + if(substr($this->root,0,1)!='/'){ + $this->root='/'.$this->root; + } + + //create the root folder if necesary + mkdir($this->constructUrl('')); + } + + /** + * construct the ftp url + * @param string path + * @return string + */ + public function constructUrl($path){ + $url='ftp'; + if($this->secure){ + $url.='s'; + } + $url.='://'.$this->user.':'.$this->password.'@'.$this->host.$this->root.$path; + return $url; + } + + public function mkdir($path){ + return mkdir($this->constructUrl($path)); + } + + public function rmdir($path){ + if($this->file_exists($path)){ + $succes=rmdir($this->constructUrl($path)); + clearstatcache(); + return $succes; + }else{ + return false; + } + } + + public function opendir($path){ + return opendir($this->constructUrl($path)); + } + + public function filetype($path){ + return filetype($this->constructUrl($path)); + } + + public function is_readable($path){ + return true;//not properly supported + } + + public function is_writable($path){ + return true;//not properly supported + } + + public function file_exists($path){ + return file_exists($this->constructUrl($path)); + } + + public function unlink($path){ + $succes=unlink($this->constructUrl($path)); + clearstatcache(); + return $succes; + } + + public function fopen($path,$mode){ + switch($mode){ + case 'r': + case 'rb': + case 'w': + case 'wb': + case 'a': + case 'ab': + //these are supported by the wrapper + $context = stream_context_create(array('ftp' => array('overwrite' => true))); + return fopen($this->constructUrl($path),$mode,false,$context); + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + if(strrpos($path,'.')!==false){ + $ext=substr($path,strrpos($path,'.')); + }else{ + $ext=''; + } + $tmpFile=OC_Helper::tmpFile($ext); + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + if($this->file_exists($path)){ + $this->getFile($path,$tmpFile); + } + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + public function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->uploadFile($tmpFile,self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + public function free_space($path){ + return 0; + } + + public function touch($path,$mtime=null){ + if(is_null($mtime)){ + $fh=$this->fopen($path,'a'); + fwrite($fh,''); + fclose($fh); + }else{ + return false;//not supported + } + } + + public function getFile($path,$target){ + return copy($this->constructUrl($path),$target); + } + + public function uploadFile($path,$target){ + return copy($path,$this->constructUrl($target)); + } + + public function rename($path1,$path2){ + return rename($this->constructUrl($path1),$this->constructUrl($path2)); + } + + public function stat($path){ + return stat($this->constructUrl($path)); + } +} diff --git a/apps/files_remote/tests/config.php b/apps/files_remote/tests/config.php new file mode 100644 index 0000000000..5410578291 --- /dev/null +++ b/apps/files_remote/tests/config.php @@ -0,0 +1,9 @@ +array( + 'host'=>'localhost', + 'user'=>'test', + 'password'=>'test', + 'root'=>'/test', + ) +); diff --git a/apps/files_remote/tests/ftp.php b/apps/files_remote/tests/ftp.php new file mode 100644 index 0000000000..2d5405ccda --- /dev/null +++ b/apps/files_remote/tests/ftp.php @@ -0,0 +1,28 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_Filestorage_FTP extends Test_FileStorage { + /** + * @var string tmpDir + */ + private $config; + private $id; + + public function setUp(){ + $id=uniqid(); + $this->config=include('apps/files_remote/tests/config.php'); + $this->config['ftp']['root'].='/'.$id;//make sure we have an new empty folder to work in + $this->instance=new OC_Filestorage_FTP($this->config['ftp']); + } + + public function tearDown(){ + OC_Helper::rmdirr($this->instance->constructUrl('')); + } +} + +?> \ No newline at end of file From 7ed8f3974417964df18b135b154fdfdbd4f1b6ca Mon Sep 17 00:00:00 2001 From: Nils Jansen Date: Fri, 23 Mar 2012 13:34:07 +0100 Subject: [PATCH 068/302] as preperation for the sgf viewer app i added a file icon for .sgf files (saved go games), and an according mimetype. --- core/img/filetypes/application-sgf.png | Bin 0 -> 725 bytes lib/mimetypes.fixlist.php | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 core/img/filetypes/application-sgf.png diff --git a/core/img/filetypes/application-sgf.png b/core/img/filetypes/application-sgf.png new file mode 100644 index 0000000000000000000000000000000000000000..f171f5579e735fe5cc729144e059263a0bc20741 GIT binary patch literal 725 zcmV;`0xJE9P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyY| z5)UXgb;nEq00LD>L_t(2&y`Y3NK|1M{r<;&%|*p1(?-!0dSaM?nHfsC5sVfsOl)03 zZCV9E(8fiyx1e2%+GJ1=Cgn~ggT$~@vMH)@Y@8Wq#&O1b@BjE(jH$Kj>^?Y$^Ks50 zhzS1!zVjF!x;#C<8u^)9imwsS6}zkhwWV!Ml|5G;Z*fS`;p5og$k_YwnN%X}NvQ)N zf*|mHPsyOJDs(97r9Q_~884OHpv;m|Nh(1jAd>CmP-~4E0pR;mb|tpRmlNyxf^S)bF~(6N z>YYJrt(8(rD@4rYgIGMrtM<5zDBt%10EAHbUx5%pDW$b8FSTp-mGbfu)3xbJ+EYp) z=f2Vb0r=Ctuapo%N(lgU;j(bUDT0WX&Nq)w|00xe#;SqXh)O9bm6Rgpd9lEgQiW{e zTK|c=4<|VQoNWnzT1+e_3WUJ6)t{ZY) zHxvrlwqqCuW0VlW7;QXQF?6T9$lM%X-^DJ|pu?jJU!xfzl-3#%DJ3PYdHhIaU(eC2 zw_pA>z9L>c=$)L7&P9?xVp-edwc)a+!+YDBt2_E1ZE=1B;p|h!+58Bn00000NkvXX Hu0mjfh3`Xt literal 0 HcmV?d00001 diff --git a/lib/mimetypes.fixlist.php b/lib/mimetypes.fixlist.php index 51f12dbcc2..a40fbd9e22 100644 --- a/lib/mimetypes.fixlist.php +++ b/lib/mimetypes.fixlist.php @@ -16,5 +16,6 @@ return array( 'xls'=>'application/msexcel', 'xlsx'=>'application/msexcel', 'ppt'=>'application/mspowerpoint', - 'pptx'=>'application/mspowerpoint' + 'pptx'=>'application/mspowerpoint', + 'sgf' => 'application/sgf' ); From a191b75c3192156c67f45778e05cf88fff42bf50 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Fri, 23 Mar 2012 15:52:41 +0100 Subject: [PATCH 069/302] make it possible to connect to other ocs appstores and other ocs knowledgebase servers. also make it possible to switch the app store and the knowledgebase off completely. --- config/config.sample.php | 4 ++++ lib/app.php | 9 ++++--- lib/ocsclient.php | 52 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 8 deletions(-) mode change 100644 => 100755 lib/app.php mode change 100644 => 100755 lib/ocsclient.php diff --git a/config/config.sample.php b/config/config.sample.php index 5206737455..199c9248c5 100755 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -15,6 +15,10 @@ $CONFIG = array( "theme" => "", "3rdpartyroot" => "", "3rdpartyurl" => "", +"knowledgebaseenabled" => true, +"knowledgebaseurl" => "", +"appstoreenabled" => true, +"appstoreurl" => "", // "datadirectory" => "" ); ?> diff --git a/lib/app.php b/lib/app.php old mode 100644 new mode 100755 index 64cbe8894e..3daf539aa2 --- a/lib/app.php +++ b/lib/app.php @@ -210,10 +210,13 @@ class OC_App{ public static function getSettingsNavigation(){ $l=new OC_L10N('core'); + $settings = array(); // by default, settings only contain the help menu - $settings = array( - array( "id" => "help", "order" => 1000, "href" => OC_Helper::linkTo( "settings", "help.php" ), "name" => $l->t("Help"), "icon" => OC_Helper::imagePath( "settings", "help.svg" )) - ); + if(OC_Config::getValue('knowledgebaseenabled', true)==true){ + $settings = array( + array( "id" => "help", "order" => 1000, "href" => OC_Helper::linkTo( "settings", "help.php" ), "name" => $l->t("Help"), "icon" => OC_Helper::imagePath( "settings", "help.svg" )) + ); + } // if the user is logged-in if (OC_User::isLoggedIn()) { diff --git a/lib/ocsclient.php b/lib/ocsclient.php old mode 100644 new mode 100755 index 9d5932fb72..d830a4f3e7 --- a/lib/ocsclient.php +++ b/lib/ocsclient.php @@ -28,6 +28,38 @@ class OC_OCSClient{ + /** + * @brief Get the url of the OCS AppStore server. + * @returns string of the AppStore server + * + * This function returns the url of the OCS AppStore server. It´s possible to set it in the config file or it will fallback to the default + */ + private static function getAppStoreURL(){ + $configurl=OC_Config::getValue('appstoreurl', ''); + if($configurl<>'') { + $url=$configurl; + }else{ + $url='http://api.apps.owncloud.com/v1'; + } + return($url); + } + + /** + * @brief Get the url of the OCS KB server. + * @returns string of the KB server + * This function returns the url of the OCS knowledge base server. It´s possible to set it in the config file or it will fallback to the default + */ + private static function getKBURL(){ + $configurl=OC_Config::getValue('knowledgebaseurl', ''); + if($configurl<>'') { + $url=$configurl; + }else{ + $url='http://api.apps.owncloud.com/v1'; + } + return($url); + } + + /** * @brief Get all the categories from the OCS server * @returns array with category ids @@ -35,7 +67,7 @@ class OC_OCSClient{ * This function returns a list of all the application categories on the OCS server */ public static function getCategories(){ - $url='http://api.apps.owncloud.com/v1/content/categories'; + $url=OC_OCSClient::getAppStoreURL().'/content/categories'; $xml=@file_get_contents($url); if($xml==FALSE){ @@ -64,12 +96,16 @@ class OC_OCSClient{ * This function returns a list of all the applications on the OCS server */ public static function getApplications($categories){ + if(OC_Config::getValue('appstoreenabled', true)==false){ + return(array()); + } + if(is_array($categories)) { $categoriesstring=implode('x',$categories); }else{ $categoriesstring=$categories; } - $url='http://api.apps.owncloud.com/v1/content/data?categories='.urlencode($categoriesstring).'&sortmode=new&page=0&pagesize=10'; + $url=OC_OCSClient::getAppStoreURL().'/content/data?categories='.urlencode($categoriesstring).'&sortmode=new&page=0&pagesize=10'; $apps=array(); $xml=@file_get_contents($url); if($xml==FALSE){ @@ -104,7 +140,7 @@ class OC_OCSClient{ * This function returns an applications from the OCS server */ public static function getApplication($id){ - $url='http://api.apps.owncloud.com/v1/content/data/'.urlencode($id); + $url=OC_OCSClient::getAppStoreURL().'/content/data/'.urlencode($id); $xml=@file_get_contents($url); if($xml==FALSE){ @@ -137,7 +173,7 @@ class OC_OCSClient{ * This function returns an download url for an applications from the OCS server */ public static function getApplicationDownload($id,$item){ - $url='http://api.apps.owncloud.com/v1/content/download/'.urlencode($id).'/'.urlencode($item); + $url=OC_OCSClient::getAppStoreURL().'/content/download/'.urlencode($id).'/'.urlencode($item); $xml=@file_get_contents($url); if($xml==FALSE){ @@ -164,9 +200,15 @@ class OC_OCSClient{ * This function returns a list of all the knowledgebase entries from the OCS server */ public static function getKnownledgebaseEntries($page,$pagesize){ + if(OC_Config::getValue('knowledgebaseenabled', true)==false){ + $kbe=array(); + $kbe['totalitems']=0; + return $kbe; + } + $p= (int) $page; $s= (int) $pagesize; - $url='http://api.apps.owncloud.com/v1/knowledgebase/data?type=150&page='.$p.'&pagesize='.$s; + $url=OC_OCSClient::getKBURL().'/knowledgebase/data?type=150&page='.$p.'&pagesize='.$s; $kbe=array(); $xml=@file_get_contents($url); From 7cad6ccce0f2fdcfc61e1071cb6373e9d28bcd63 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Fri, 23 Mar 2012 16:48:16 +0100 Subject: [PATCH 070/302] =?UTF-8?q?don=C2=B4t=20show=20ugly=20error=20mess?= =?UTF-8?q?age?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 lib/helper.php diff --git a/lib/helper.php b/lib/helper.php old mode 100644 new mode 100755 index 75942f092c..66f31d929b --- a/lib/helper.php +++ b/lib/helper.php @@ -220,7 +220,7 @@ class OC_Helper { $fullpath = $path.'/'.$file; if(is_link($fullpath)) return FALSE; - elseif(!is_dir($fullpath) && !chmod($fullpath, $filemode)) + elseif(!is_dir($fullpath) && !@chmod($fullpath, $filemode)) return FALSE; elseif(!self::chmodr($fullpath, $filemode)) return FALSE; From 45b320d674332dc224cf95c82e2e02e25ac66661 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 23 Mar 2012 16:53:44 +0100 Subject: [PATCH 071/302] use unnamed as title for a bookmark if no title was entered and curl fails --- apps/bookmarks/bookmarksHelper.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/bookmarks/bookmarksHelper.php b/apps/bookmarks/bookmarksHelper.php index 8def7401e2..7ada69014f 100644 --- a/apps/bookmarks/bookmarksHelper.php +++ b/apps/bookmarks/bookmarksHelper.php @@ -71,7 +71,7 @@ function getURLMetadata($url) { return $metadata; } -function addBookmark($url, $title='', $tags='') { +function addBookmark($url, $title, $tags='') { $CONFIG_DBTYPE = OC_Config::getValue( "dbtype", "sqlite" ); if( $CONFIG_DBTYPE == 'sqlite' or $CONFIG_DBTYPE == 'sqlite3' ){ $_ut = "strftime('%s','now')"; @@ -93,6 +93,11 @@ function addBookmark($url, $title='', $tags='') { $title = $metadata['title']; } + if(empty($title)) { + $l = new OC_L10N('bookmarks'); + $title = $l->t('unnamed'); + } + $params=array( htmlspecialchars_decode($url), htmlspecialchars_decode($title), From b63b377c4d173b3a62b93360909857a1d6a62837 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 23 Mar 2012 16:58:48 +0100 Subject: [PATCH 072/302] Bookmarks: make link text click clickable too - bugfix for oc-131 --- apps/bookmarks/js/bookmarks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bookmarks/js/bookmarks.js b/apps/bookmarks/js/bookmarks.js index fa5adde254..3557810d4b 100644 --- a/apps/bookmarks/js/bookmarks.js +++ b/apps/bookmarks/js/bookmarks.js @@ -145,7 +145,7 @@ function updateBookmarksList(bookmark) { '

'+ '' + encodeEntities(bookmark.title) + '' + '

' + - '

' + encodeEntities(bookmark.url) + '

' + + '

' + encodeEntities(bookmark.url) + '

' + '' ); if(taglist != '') { From ac8362e34a89b0e513e1304cf0f111c52d27f9e5 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 23 Mar 2012 17:09:51 +0100 Subject: [PATCH 073/302] check if the title of an event is null - bugfix for oc-139 --- apps/calendar/ajax/events.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/calendar/ajax/events.php b/apps/calendar/ajax/events.php index 922df90b76..cf768f51c6 100755 --- a/apps/calendar/ajax/events.php +++ b/apps/calendar/ajax/events.php @@ -12,7 +12,7 @@ require_once('when/When.php'); function create_return_event($event, $vevent){ $return_event = array(); $return_event['id'] = (int)$event['id']; - $return_event['title'] = htmlspecialchars($event['summary']); + $return_event['title'] = htmlspecialchars(($event['summary']!=NULL)?$event['summary']:$l->t('unnamed')); $return_event['description'] = isset($vevent->DESCRIPTION)?htmlspecialchars($vevent->DESCRIPTION->value):''; $last_modified = $vevent->__get('LAST-MODIFIED'); if ($last_modified){ From 0e11cc9f6ff6176ae723acd8facef9237252be5f Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 23 Mar 2012 17:27:52 +0100 Subject: [PATCH 074/302] fix previous made bug and fix oc bug - oc-139 --- apps/calendar/ajax/events.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/calendar/ajax/events.php b/apps/calendar/ajax/events.php index cf768f51c6..c62f93c540 100755 --- a/apps/calendar/ajax/events.php +++ b/apps/calendar/ajax/events.php @@ -8,11 +8,13 @@ require_once ('../../../lib/base.php'); require_once('when/When.php'); - +$l = new OC_L10N('calendar'); +$unnamed = $l->t('unnamed'); function create_return_event($event, $vevent){ $return_event = array(); + global $unnamed; $return_event['id'] = (int)$event['id']; - $return_event['title'] = htmlspecialchars(($event['summary']!=NULL)?$event['summary']:$l->t('unnamed')); + $return_event['title'] = htmlspecialchars(($event['summary']!=NULL || $event['summary'] != '')?$event['summary']: $unnamed); $return_event['description'] = isset($vevent->DESCRIPTION)?htmlspecialchars($vevent->DESCRIPTION->value):''; $last_modified = $vevent->__get('LAST-MODIFIED'); if ($last_modified){ From 109d80661713b896287c0a58ddf1f55604ad1298 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 23 Mar 2012 18:52:41 +0100 Subject: [PATCH 075/302] make sure we can load OC_Config when we need it --- lib/base.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/base.php b/lib/base.php index 54cc9f2c73..b07ac5af41 100644 --- a/lib/base.php +++ b/lib/base.php @@ -139,6 +139,12 @@ class OC{ OC::$WEBROOT='/'.OC::$WEBROOT; } + // ensure we can find OC_Config + set_include_path( + OC::$SERVERROOT.'/lib'.PATH_SEPARATOR. + get_include_path() + ); + // search the 3rdparty folder if(OC_Config::getValue('3rdpartyroot', '')<>'' and OC_Config::getValue('3rdpartyurl', '')<>''){ OC::$THIRDPARTYROOT=OC_Config::getValue('3rdpartyroot', ''); @@ -227,6 +233,7 @@ class OC{ // register autoloader spl_autoload_register(array('OC','autoload')); + // set some stuff //ob_start(); error_reporting(E_ALL | E_STRICT); @@ -252,6 +259,8 @@ class OC{ $_SERVER['PHP_AUTH_USER'] = strip_tags($name); $_SERVER['PHP_AUTH_PW'] = strip_tags($password); } + + self::initPaths(); // register the stream wrappers require_once('streamwrappers.php'); @@ -259,7 +268,6 @@ class OC{ stream_wrapper_register('static', 'OC_StaticStreamWrapper'); stream_wrapper_register('close', 'OC_CloseStreamWrapper'); - self::initPaths(); self::checkInstalled(); self::checkSSL(); self::checkUpgrade(); From e0cbefc7275fa622f15dc1e57be67fd283f28056 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 23 Mar 2012 18:54:38 +0100 Subject: [PATCH 076/302] webdav storage backend --- apps/files_remote/appinfo/app.php | 1 + apps/files_remote/lib/webdav.php | 293 +++++++++++++++++++++++++++++ apps/files_remote/tests/config.php | 8 +- apps/files_remote/tests/ftp.php | 5 - apps/files_remote/tests/webdav.php | 23 +++ 5 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 apps/files_remote/lib/webdav.php create mode 100644 apps/files_remote/tests/webdav.php diff --git a/apps/files_remote/appinfo/app.php b/apps/files_remote/appinfo/app.php index f94e813ea5..25473d87fe 100644 --- a/apps/files_remote/appinfo/app.php +++ b/apps/files_remote/appinfo/app.php @@ -7,3 +7,4 @@ */ OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_remote/lib/ftp.php'; +OC::$CLASSPATH['OC_Filestorage_DAV']='apps/files_remote/lib/webdav.php'; diff --git a/apps/files_remote/lib/webdav.php b/apps/files_remote/lib/webdav.php new file mode 100644 index 0000000000..7a2da5c8ec --- /dev/null +++ b/apps/files_remote/lib/webdav.php @@ -0,0 +1,293 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_FileStorage_DAV extends OC_Filestorage_Common{ + private $password; + private $user; + private $host; + private $secure; + private $root; + /** + * @var Sabre_DAV_Client + */ + private $client; + + private static $tempFiles=array(); + + public function __construct($params){ + $this->host=$params['host']; + $this->user=$params['user']; + $this->password=$params['password']; + $this->secure=isset($params['secure'])?(bool)$params['secure']:false; + $this->root=isset($params['root'])?$params['root']:'/'; + if(substr($this->root,0,1)!='/'){ + $this->root='/'.$this->root; + } + if(substr($this->root,-1,1)!='/'){ + $this->root.='/'; + } + + $settings = array( + 'baseUri' => $this->createBaseUri(), + 'userName' => $this->user, + 'password' => $this->password, + ); + $this->client = new Sabre_DAV_Client($settings); + + //create the root folder if necesary + $this->mkdir(''); + } + + private function createBaseUri(){ + $baseUri='http'; + if($this->secure){ + $baseUri.'s'; + } + $baseUri.='://'.$this->host.$this->root; + return $baseUri; + } + + public function mkdir($path){ + $path=$this->cleanPath($path); + return $this->simpleResponse('MKCOL',$path,null,201); + } + + public function rmdir($path){ + $path=$this->cleanPath($path); + return $this->simpleResponse('DELETE',$path,null,204); + } + + public function opendir($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array(),1); + $stripLength=strlen($this->root)+strlen($path); + $id=md5('webdav'.$this->root.$path); + OC_FakeDirStream::$dirs[$id]=array(); + foreach($response as $file=>$data){ + //strip root and path + $file=trim(substr($file,$stripLength)); + $file=trim($file,'/'); + if($file){ + OC_FakeDirStream::$dirs[$id][]=$file; + } + } + return opendir('fakedir://'.$id); + }catch(Exception $e){ + return false; + } + } + + public function filetype($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}resourcetype')); + $responseType=$response["{DAV:}resourcetype"]->resourceType; + return (count($responseType)>0 and $responseType[0]=="{DAV:}collection")?'dir':'file'; + }catch(Exception $e){ + return false; + } + } + + public function is_readable($path){ + return true;//not properly supported + } + + public function is_writable($path){ + return true;//not properly supported + } + + public function file_exists($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}resourcetype')); + return true;//no 404 exception + }catch(Exception $e){ + return false; + } + } + + public function unlink($path){ + return $this->simpleResponse('DELETE',$path,null,204); + } + + public function fopen($path,$mode){ + $path=$this->cleanPath($path); + switch($mode){ + case 'r': + case 'rb': + //straight up curl instead of sabredav here, sabredav put's the entire get result in memory + $curl = curl_init(); + $fp = fopen('php://temp', 'r+'); + curl_setopt($curl,CURLOPT_USERPWD,$this->user.':'.$this->password); + curl_setopt($curl, CURLOPT_URL, $this->createBaseUri().$path); + curl_setopt($curl, CURLOPT_FILE, $fp); + + curl_exec ($curl); + curl_close ($curl); + rewind($fp); + return $fp; + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + if(strrpos($path,'.')!==false){ + $ext=substr($path,strrpos($path,'.')); + }else{ + $ext=''; + } + $tmpFile=OC_Helper::tmpFile($ext); + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + if($this->file_exists($path)){ + $this->getFile($path,$tmpFile); + } + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + public function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->uploadFile($tmpFile,self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + public function free_space($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}quota-available-bytes')); + if(isset($response['{DAV:}quota-available-bytes'])){ + return (int)$response['{DAV:}quota-available-bytes']; + }else{ + return 0; + } + }catch(Exception $e){ + return 0; + } + } + + public function touch($path,$mtime=null){ + if(is_null($mtime)){ + $mtime=time(); + } + $path=$this->cleanPath($path); + $this->client->proppatch($path, array('{DAV:}lastmodified' => $mtime,)); + } + + public function getFile($path,$target){ + $source=$this->fopen($path,'r'); + file_put_contents($target,$source); + } + + public function uploadFile($path,$target){ + $source=fopen($path,'r'); + + $curl = curl_init(); + curl_setopt($curl,CURLOPT_USERPWD,$this->user.':'.$this->password); + curl_setopt($curl, CURLOPT_URL, $this->createBaseUri().$target); + curl_setopt($curl, CURLOPT_BINARYTRANSFER, true); + curl_setopt($curl, CURLOPT_INFILE, $source); // file pointer + curl_setopt($curl, CURLOPT_INFILESIZE, filesize($path)); + curl_setopt($curl, CURLOPT_PUT, true); + curl_exec ($curl); + curl_close ($curl); + } + + public function rename($path1,$path2){ + $path1=$this->cleanPath($path1); + $path2=$this->root.$this->cleanPath($path2); + try{ + $response=$this->client->request('MOVE',$path1,null,array('Destination'=>$path2)); + return true; + }catch(Exception $e){ + echo $e; + echo 'fail'; + var_dump($response); + return false; + } + } + + public function copy($path1,$path2){ + $path1=$this->cleanPath($path1); + $path2=$this->root.$this->cleanPath($path2); + try{ + $response=$this->client->request('COPY',$path1,null,array('Destination'=>$path2)); + return true; + }catch(Exception $e){ + echo $e; + echo 'fail'; + var_dump($response); + return false; + } + } + + public function stat($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}getlastmodified','{DAV:}getcontentlength')); + if(isset($response['{DAV:}getlastmodified']) and isset($response['{DAV:}getcontentlength'])){ + return array( + 'mtime'=>strtotime($response['{DAV:}getlastmodified']), + 'size'=>(int)$response['{DAV:}getcontentlength'], + 'ctime'=>-1, + ); + }else{ + return array(); + } + }catch(Exception $e){ + return array(); + } + } + + public function getMimeType($path){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->propfind($path, array('{DAV:}getcontenttype','{DAV:}resourcetype')); + $responseType=$response["{DAV:}resourcetype"]->resourceType; + $type=(count($responseType)>0 and $responseType[0]=="{DAV:}collection")?'dir':'file'; + if($type=='dir'){ + return 'httpd/unix-directory'; + }elseif(isset($response['{DAV:}getcontenttype'])){ + return $response['{DAV:}getcontenttype']; + }else{ + return false; + } + }catch(Exception $e){ + return false; + } + } + + private function cleanPath($path){ + if(substr($path,0,1)=='/'){ + return substr($path,1); + }else{ + return $path; + } + } + + private function simpleResponse($method,$path,$body,$expected){ + $path=$this->cleanPath($path); + try{ + $response=$this->client->request($method,$path,$body); + return $response['statusCode']==$expected; + }catch(Exception $e){ + return false; + } + } +} + diff --git a/apps/files_remote/tests/config.php b/apps/files_remote/tests/config.php index 5410578291..e2eea72f24 100644 --- a/apps/files_remote/tests/config.php +++ b/apps/files_remote/tests/config.php @@ -5,5 +5,11 @@ return array( 'user'=>'test', 'password'=>'test', 'root'=>'/test', - ) + ), + 'webdav'=>array( + 'host'=>'localhost', + 'user'=>'test', + 'password'=>'test', + 'root'=>'/owncloud/files/webdav.php', + ), ); diff --git a/apps/files_remote/tests/ftp.php b/apps/files_remote/tests/ftp.php index 2d5405ccda..03633b7c0d 100644 --- a/apps/files_remote/tests/ftp.php +++ b/apps/files_remote/tests/ftp.php @@ -7,9 +7,6 @@ */ class Test_Filestorage_FTP extends Test_FileStorage { - /** - * @var string tmpDir - */ private $config; private $id; @@ -24,5 +21,3 @@ class Test_Filestorage_FTP extends Test_FileStorage { OC_Helper::rmdirr($this->instance->constructUrl('')); } } - -?> \ No newline at end of file diff --git a/apps/files_remote/tests/webdav.php b/apps/files_remote/tests/webdav.php new file mode 100644 index 0000000000..219fff8852 --- /dev/null +++ b/apps/files_remote/tests/webdav.php @@ -0,0 +1,23 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_Filestorage_DAV extends Test_FileStorage { + private $config; + private $id; + + public function setUp(){ + $id=uniqid(); + $this->config=include('apps/files_remote/tests/config.php'); + $this->config['webdav']['root'].='/'.$id;//make sure we have an new empty folder to work in + $this->instance=new OC_Filestorage_DAV($this->config['webdav']); + } + + public function tearDown(){ + $this->instance->rmdir('/'); + } +} From cbdcb68c2b2bfed5d7ad3a1b0bf8b7f20210b258 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sat, 24 Mar 2012 18:35:16 +0100 Subject: [PATCH 077/302] gallery sharing, experimental version --- apps/gallery/ajax/galleryOp.php | 44 ++++++++++- apps/gallery/ajax/sharing.php | 118 ++++++++++++++++++++++++++++++ apps/gallery/appinfo/app.php | 1 + apps/gallery/appinfo/database.xml | 24 ++++++ apps/gallery/css/sharing.css | 8 ++ apps/gallery/css/styles.css | 2 + apps/gallery/img/breadcrumb.png | Bin 0 -> 220 bytes apps/gallery/index.php | 5 +- apps/gallery/js/album_cover.js | 46 +++++++++++- apps/gallery/js/albums.js | 5 +- apps/gallery/js/sharing.js | 57 +++++++++++++++ apps/gallery/lib/album.php | 2 +- apps/gallery/lib/photo.php | 5 +- apps/gallery/lib/sharing.php | 89 ++++++++++++++++++++++ apps/gallery/sharing.php | 47 ++++++++++++ apps/gallery/templates/index.php | 3 +- core/js/oc-dialogs.js | 20 +++-- 17 files changed, 457 insertions(+), 19 deletions(-) create mode 100644 apps/gallery/ajax/sharing.php create mode 100644 apps/gallery/css/sharing.css create mode 100644 apps/gallery/img/breadcrumb.png create mode 100644 apps/gallery/js/sharing.js create mode 100644 apps/gallery/lib/sharing.php create mode 100644 apps/gallery/sharing.php diff --git a/apps/gallery/ajax/galleryOp.php b/apps/gallery/ajax/galleryOp.php index 459c30f6ac..b0433898cd 100644 --- a/apps/gallery/ajax/galleryOp.php +++ b/apps/gallery/ajax/galleryOp.php @@ -111,9 +111,48 @@ function handleGetGallery($path) { $p[] = utf8_encode($r['file_path']); } - OC_JSON::success(array('albums'=>$a, 'photos'=>$p)); + $r = OC_Gallery_Sharing::getEntryByAlbumId($album_details['album_id']); + $shared = false; + $recursive = false; + $token = ''; + if ($row = $r->fetchRow()) { + $shared = true; + $recursive = ($row['recursive'] == 1)? true : false; + $token = $row['token']; + } + + OC_JSON::success(array('albums'=>$a, 'photos'=>$p, 'shared' => $shared, 'recursive' => $recursive, 'token' => $token)); } +function handleShare($path, $share, $recursive) { + $recursive = $recursive == 'true' ? 1 : 0; + $owner = OC_User::getUser(); + $r = OC_Gallery_Album::find($owner, null, $path); + if ($row = $r->fetchRow()) { + $albumId = $row['album_id']; + } else { + OC_JSON::error(array('cause' => 'Couldn\'t find requested gallery')); + exit; + } + + if ($share == false) { + OC_Gallery_Sharing::remove($albumId); + OC_JSON::success(array('sharing' => false)); + } else { // share, yeah \o/ + $r = OC_Gallery_Sharing::getEntryByAlbumId($albumId); + if (($row = $r->fetchRow())) { // update entry + OC_Gallery_Sharing::updateSharingByToken($row['token'], $recursive); + OC_JSON::success(array('sharing' => true, 'token' => $row['token'], 'recursive' => $recursive == 1 ? true : false )); + } else { // and new sharing entry + $date = new DateTime(); + $token = md5($owner . $date->getTimestamp()); + OC_Gallery_Sharing::addShared($token, intval($albumId), $recursive); + OC_JSON::success(array('sharing' => true, 'token' => $token, 'recursive' => $recursive == 1 ? true : false )); + } + } +} + + if ($_GET['operation']) { switch($_GET['operation']) { case 'rename': @@ -136,6 +175,9 @@ if ($_GET['operation']) { case 'get_gallery': handleGetGallery($_GET['path']); break; + case 'share': + handleShare($_GET['path'], $_GET['share'] == 'true' ? true : false, $_GET['recursive']); + break; default: OC_JSON::error(array('cause' => 'Unknown operation')); } diff --git a/apps/gallery/ajax/sharing.php b/apps/gallery/ajax/sharing.php new file mode 100644 index 0000000000..b737d8f6fe --- /dev/null +++ b/apps/gallery/ajax/sharing.php @@ -0,0 +1,118 @@ +. +* +*/ + +require_once('../../../lib/base.php'); + +if (!isset($_GET['token']) || !isset($_GET['operation'])) { + OC_JSON::error(array('cause' => 'Not enought arguments')); + exit; +} + +$operation = $_GET['operation']; +$token = $_GET['token']; + +if (!OC_Gallery_Sharing::isTokenValid($token)) { + OC_JSON::error(array('cause' => 'Given token is not valid')); + exit; +} + +function handleGetGallery($token, $path) { + $owner = OC_Gallery_Sharing::getTokenOwner($token); + $apath = OC_Gallery_Sharing::getPath($token); + + if ($path == false) + $root = $apath; + else + $root = rtrim($apath,'/').$path; + + $r = OC_Gallery_Album::find($owner, null, $root); + $albums = array(); + $photos = array(); + $albumId = -1; + if ($row = $r->fetchRow()) { + $albumId = $row['album_id']; + } + if ($albumId != -1) { + + if (OC_Gallery_Sharing::isRecursive($token)) { + $r = OC_Gallery_Album::find($owner, null, null, $root); + while ($row = $r->fetchRow()) + $albums[] = $row['album_name']; + } + + $r = OC_Gallery_Photo::find($albumId); + while ($row = $r->fetchRow()) + $photos[] = $row['file_path']; + } + + OC_JSON::success(array('albums' => $albums, 'photos' => $photos)); +} + +function handleGetThumbnail($token, $imgpath) { + $owner = OC_Gallery_Sharing::getTokenOwner($token); + $image = OC_Gallery_Photo::getThumbnail($imgpath, $owner); + if ($image) { + OC_Response::enableCaching(3600 * 24); // 24 hour + $image->show(); + } +} + +function handleGetAlbumThumbnail($token, $albumname) +{ + $owner = OC_Gallery_Sharing::getTokenOwner($token); + $file = OC_Config::getValue("datadirectory").'/'. $owner .'/gallery/'.$albumname.'.png'; + $image = new OC_Image($file); + if ($image->valid()) { + $image->centerCrop(); + $image->resize(200); + $image->fixOrientation(); + OC_Response::enableCaching(3600 * 24); // 24 hour + $image->show(); + } +} + +function handleGetPhoto($token, $photo) { + $owner = OC_Gallery_Sharing::getTokenOwner($token); + if (OC_User::isLoggedIn()) + $file = OC::$CONFIG_DATADIRECTORY.urldecode($photo); + else + $file = OC::$CONFIG_DATADIRECTORY.'/'.$owner.'/files'.urldecode($photo); + header('Content-Type: '.OC_Image::getMimeTypeForFile($file)); + OC_Response::sendFile($file); +} + +switch ($operation) { + case 'get_gallery': + handleGetGallery($token, isset($_GET['path'])? $_GET['path'] : false); + break; + case 'get_thumbnail': + handleGetThumbnail($token, urldecode($_GET['img'])); + break; + case 'get_album_thumbnail': + handleGetAlbumThumbnail($token, urldecode($_GET['albumname'])); + break; + case 'get_photo': + handleGetPhoto($token, urldecode($_GET['photo'])); + break; +} + diff --git a/apps/gallery/appinfo/app.php b/apps/gallery/appinfo/app.php index 1e5e27d408..3e7e38301c 100644 --- a/apps/gallery/appinfo/app.php +++ b/apps/gallery/appinfo/app.php @@ -24,6 +24,7 @@ OC::$CLASSPATH['OC_Gallery_Album'] = 'apps/gallery/lib/album.php'; OC::$CLASSPATH['OC_Gallery_Photo'] = 'apps/gallery/lib/photo.php'; OC::$CLASSPATH['OC_Gallery_Scanner'] = 'apps/gallery/lib/scanner.php'; +OC::$CLASSPATH['OC_Gallery_Sharing'] = 'apps/gallery/lib/sharing.php'; OC::$CLASSPATH['OC_Gallery_Hooks_Handlers'] = 'apps/gallery/lib/hooks_handlers.php'; $l = new OC_L10N('gallery'); diff --git a/apps/gallery/appinfo/database.xml b/apps/gallery/appinfo/database.xml index 62fdbee9cd..e3b13f7e93 100644 --- a/apps/gallery/appinfo/database.xml +++ b/apps/gallery/appinfo/database.xml @@ -67,4 +67,28 @@
+ + *dbprefix*gallery_sharing + + + token + text + true + 64 + + + gallery_id + integer + 0 + true + 4 + + + recursive + integer + true + 1 + + +
diff --git a/apps/gallery/css/sharing.css b/apps/gallery/css/sharing.css new file mode 100644 index 0000000000..eaac82ebd6 --- /dev/null +++ b/apps/gallery/css/sharing.css @@ -0,0 +1,8 @@ +body { background-color: #eee; margin: 0; padding: 0;} +#gallery_list { height: 100%; width: 80%; background-color: white; margin: 0 auto; box-shadow: 0 0 8px #888; } +div.gallery_box { width: 200px; position:relative; text-align: center; border: 0; display: inline-block; margin: 5pt; vertical-align: top; padding: 5px 5px 5px 5px; position: relative; cursor: pointer; -webkit-transition: color 0.5s ease-in-out; -o-transition: color 0.5s ease-in-out; -moz-transition: color 0.5s ease-in-out;color: #BBB;} +div.gallery_box:hover { color: black; } +div.gallery_box h1 {font-size: 17px; font-weight: normal;} +div#breadcrumb { border: 0; width: 70%; margin: 0 auto; padding: 25px 0; font-family: Verdana; text-align: center;} +span.breadcrumbelement { margin: 10px; margin-right: 0; cursor: pointer;} +span.inside { background-image: url('../img/breadcrumb.png'); padding-left: 20px; background-position: left; background-repeat: no-repeat;} diff --git a/apps/gallery/css/styles.css b/apps/gallery/css/styles.css index 013cd1b262..fbf54e43db 100644 --- a/apps/gallery/css/styles.css +++ b/apps/gallery/css/styles.css @@ -16,3 +16,5 @@ div.gallery_control_overlay a { color:white; } #g-settings {position: absolute; left 13.5em; top: 0;} input[type=button] { -webkit-transition: opacity 0.5s ease-in-out; -moz-transition: opacity 0.5s ease-in-out; -o-transition: opacity 0.5s ease-in-out; opacity: 1} input[type=button]:disabled { opacity: 0.5 } +.ui-dialog tr {background-color: #eee;} +.ui-dialog input {width: 90%;} diff --git a/apps/gallery/img/breadcrumb.png b/apps/gallery/img/breadcrumb.png new file mode 100644 index 0000000000000000000000000000000000000000..a252a751554e142eb9dda6daecf529b41ffb0bed GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4

'; + + for (var i in r.albums) { + var a = r.albums[i]; + var local = $(album_template.replace('IMGPATH', encodeURIComponent(a))); + local.attr('title', a); + $('h1', local).html(a); + element.append(local); + } + + $('div.gallery_box').each(function(i, element) { + $(element).click(function() { + paths.push($(this).attr('title')); + path = ''; + for (var e in paths) path += '/' + paths[e]; + $.getJSON('ajax/sharing.php', {operation: 'get_gallery', token: TOKEN, path: path}, function(r) { + var name = paths[paths.length-1]; + counter++; + var d = ''+name+''; + d = $(d).addClass('inside'); + $('#breadcrumb').append(d); + albumClickHandler(r); + }); + }); + }); + + var pat = ''; + for (var a in paths) pat += '/'+paths[a]; + var photo_template = ''; + for (var a in r.photos) { + var local = photo_template.replace('IMGPATH', encodeURIComponent(r.photos[a])).replace('*HREF*', 'ajax/sharing.php?token='+TOKEN+'&operation=get_photo&photo='+encodeURIComponent(r.photos[a])); + element.append(local); + } +} diff --git a/apps/gallery/lib/album.php b/apps/gallery/lib/album.php index 070afdd6cd..ef361a3791 100644 --- a/apps/gallery/lib/album.php +++ b/apps/gallery/lib/album.php @@ -79,7 +79,7 @@ class OC_Gallery_Album { $sql .= ' AND parent_path = ?'; $args[] = $parent; } - $order = OC_Preferences::getValue(OC_User::getUser(), 'gallery', 'order', 'ASC'); + $order = OC_Preferences::getValue($owner, 'gallery', 'order', 'ASC'); $sql .= ' ORDER BY album_name ' . $order; $stmt = OC_DB::prepare($sql); diff --git a/apps/gallery/lib/photo.php b/apps/gallery/lib/photo.php index 5e6df8069b..0c0f16e659 100644 --- a/apps/gallery/lib/photo.php +++ b/apps/gallery/lib/photo.php @@ -66,8 +66,9 @@ class OC_Gallery_Photo { $stmt->execute(array($newpath, $newAlbumId, $oldAlbumId, $oldpath)); } - public static function getThumbnail($image_name) { - $save_dir = OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery/'; + public static function getThumbnail($image_name, $owner = null) { + if (!$owner) $owner = OC_User::getUser(); + $save_dir = OC_Config::getValue("datadirectory").'/'. $owner .'/gallery/'; $save_dir .= dirname($image_name). '/'; $image_path = $image_name; $thumb_file = $save_dir . basename($image_name); diff --git a/apps/gallery/lib/sharing.php b/apps/gallery/lib/sharing.php new file mode 100644 index 0000000000..60f108bd6c --- /dev/null +++ b/apps/gallery/lib/sharing.php @@ -0,0 +1,89 @@ +. +* +*/ + +class OC_Gallery_Sharing { + private static function getEntries($token) { + $sql = 'SELECT * FROM *PREFIX*gallery_sharing WHERE token = ?'; + $stmt = OC_DB::prepare($sql); + return $stmt->execute(array($token)); + } + + public static function isTokenValid($token) { + $r = self::getEntries($token); + $row = $r->fetchRow(); + return $row != null; + } + + public static function isRecursive($token) { + $r = self::getEntries($token); + if ($row = $r->fetchRow()) return $row['recursive'] == 1; + return false; + } + + public static function getTokenOwner($token) { + $r = self::getEntries($token); + if ($row = $r->fetchRow()) { + $galleryId = $row['gallery_id']; + $sql = 'SELECT * FROM *PREFIX*gallery_albums WHERE album_id = ?'; + $stmt = OC_DB::prepare($sql); + $r = $stmt->execute(array($galleryId)); + if ($row = $r->fetchRow()) + return $row['uid_owner']; + } + return false; + } + + public static function getPath($token) { + $r = self::getEntries($token); + if ($row = $r->fetchRow()) { + $galleryId = $row['gallery_id']; + $sql = 'SELECT * FROM *PREFIX*gallery_albums WHERE album_id = ?'; + $stmt = OC_DB::prepare($sql); + $r = $stmt->execute(array($galleryId)); + if ($row = $r->fetchRow()) + return $row['album_path']; + } + } + + public static function updateSharingByToken($token, $recursive) { + $stmt = OC_DB::prepare('UPDATE *PREFIX*gallery_sharing SET recursive = ? WHERE token = ?'); + $stmt->execute(array($recursive, $token)); + } + + public static function getEntryByAlbumId($album_id) { + $stmt = OC_DB::prepare('SELECT * FROM *PREFIX*gallery_sharing WHERE gallery_id = ?'); + return $stmt->execute(array($album_id)); + } + + public static function addShared($token, $albumId, $recursive) { + $sql = 'INSERT INTO *PREFIX*gallery_sharing (token, gallery_id, recursive) VALUES (?, ?, ?)'; + $stmt = OC_DB::prepare($sql); + $stmt->execute(array($token, $albumId, $recursive)); + } + + public static function remove($albumId) { + $stmt = OC_DB::prepare('DELETE FROM *PREFIX*gallery_sharing WHERE gallery_id = ?'); + $stmt->execute(array($albumId)); + } +} + diff --git a/apps/gallery/sharing.php b/apps/gallery/sharing.php new file mode 100644 index 0000000000..d7430becf4 --- /dev/null +++ b/apps/gallery/sharing.php @@ -0,0 +1,47 @@ +. +* +*/ + +if (!isset($_GET['token']) || empty($_GET['token'])) { + exit; +} + +require_once('../../lib/base.php'); + +OC_Util::checkAppEnabled('gallery'); + +?> + + + + + + + + + + + + + diff --git a/apps/gallery/templates/index.php b/apps/gallery/templates/index.php index dc5852733b..c6373d3b0a 100644 --- a/apps/gallery/templates/index.php +++ b/apps/gallery/templates/index.php @@ -14,7 +14,8 @@ $l = new OC_L10N('gallery');
- + +
diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index 9ce2bae164..c11ac13332 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -2,7 +2,7 @@ * ownCloud * * @author Bartek Przybylski - * @copyright 2012 Bartek Przybylski bart.p.pl@gmail.com + * @copyright 2012 Bartek Przybylski bartek@alefzero.eu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -66,7 +66,7 @@ OCdialogs = { }, /** * prompt user for input with custom form - * fields should be passed in following format: [{text:'prompt text', name:'return name', type:'input type'},...] + * fields should be passed in following format: [{text:'prompt text', name:'return name', type:'input type', value: 'dafault value'},...] * @param fields to display * @param title dialog title * @param callback which will be triggered when user press OK (user answers will be passed to callback in following format: [{name:'return name', value: 'user value'},...]) @@ -76,8 +76,15 @@ OCdialogs = { for (var a in fields) { content += '
'+fields[a].text+''; var type=fields[a].type; - if (type == 'text' || type == 'checkbox' || type == 'password') - content += ''; + if (type == 'text' || type == 'checkbox' || type == 'password') { + content += ''; + } content += "
"; @@ -112,7 +119,8 @@ OCdialogs = { b[0] = {text: t('dialogs', 'Ok'), click: f}; break; } - $(c_id).dialog({width: 4*$(document).width()/9, height: $(d).height() + 150, modal: false, buttons: b}); + var possible_height = ($('tr', d).size()+1)*30; + $(c_id).dialog({width: 4*$(document).width()/9, height: possible_height + 120, modal: false, buttons: b}); OCdialogs.dialogs_counter++; }, // dialogs buttons types @@ -127,7 +135,7 @@ OCdialogs = { dialogs_counter: 0, determineValue: function(element) { switch ($(element).attr('type')) { - case 'checkbox': return $(element).attr('checked') != undefined; + case 'checkbox': return element.checked; } return $(element).val(); }, From afcf96549883e1ac9b87681ac7d5d0c3ea0cd513 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sat, 24 Mar 2012 18:40:27 +0100 Subject: [PATCH 078/302] fixup: sharing gallery for loggedin users fail --- apps/gallery/ajax/sharing.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/gallery/ajax/sharing.php b/apps/gallery/ajax/sharing.php index b737d8f6fe..fba85fa34e 100644 --- a/apps/gallery/ajax/sharing.php +++ b/apps/gallery/ajax/sharing.php @@ -93,10 +93,7 @@ function handleGetAlbumThumbnail($token, $albumname) function handleGetPhoto($token, $photo) { $owner = OC_Gallery_Sharing::getTokenOwner($token); - if (OC_User::isLoggedIn()) - $file = OC::$CONFIG_DATADIRECTORY.urldecode($photo); - else - $file = OC::$CONFIG_DATADIRECTORY.'/'.$owner.'/files'.urldecode($photo); + $file = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$owner.'/files'.urldecode($photo); header('Content-Type: '.OC_Image::getMimeTypeForFile($file)); OC_Response::sendFile($file); } From be60b451eb7a152f19e31eeaf1b2f009eadb1a6b Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sat, 24 Mar 2012 19:21:36 +0100 Subject: [PATCH 079/302] fix albums thumbnails preview for opera --- apps/gallery/js/albums.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/gallery/js/albums.js b/apps/gallery/js/albums.js index be121b2d70..1fb38a5546 100644 --- a/apps/gallery/js/albums.js +++ b/apps/gallery/js/albums.js @@ -67,14 +67,14 @@ Albums={ $(".gallery_album_cover", local).css('background-repeat', 'no-repeat'); $(".gallery_album_cover", local).css('background-position', '0'); $(".gallery_album_cover", local).css('background-image','url("'+OC.filePath('gallery','ajax','galleryOp.php')+'?operation=get_covers&albumname='+escape(a.name)+'")'); - $(".gallery_album_cover", local).mousemove(function(e) { + $(".gallery_album_cover", local).mousemove(function(event) { var albumMetadata = Albums.find(this.title); if (albumMetadata == undefined) { return; } - var x = Math.floor((e.layerX - this.offsetLeft)/(this.offsetWidth/albumMetadata.numOfCovers)); + var x = Math.floor(event.offsetX/(this.offsetWidth/albumMetadata.numOfCovers)); x *= this.offsetWidth; - if (x < 0) x=0; + if (x < 0 || isNaN(x)) x=0; $(this).css('background-position', -x+'px 0'); }); $(element).append(local); From 826cb2c650d9a9c1b8d826cba6863dbfacc8b8f4 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sat, 24 Mar 2012 19:26:53 +0100 Subject: [PATCH 080/302] fix sharing address construction in opera and firefox --- apps/gallery/js/album_cover.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/gallery/js/album_cover.js b/apps/gallery/js/album_cover.js index 41a648bec5..061bbcd0b4 100644 --- a/apps/gallery/js/album_cover.js +++ b/apps/gallery/js/album_cover.js @@ -30,10 +30,14 @@ function albumClick(title) { }); } +function constructSharingPath() { + return document.location.protocol + '//' + document.location.host + OC.linkTo('gallery', 'sharing.php') + '?token=' + Albums.token; +} + function shareGallery() { var existing_token = ''; if (Albums.token) - existing_token = document.location.origin + OC.linkTo('gallery', 'sharing.php') + '?token=' + Albums.token; + existing_token = constructSharingPath(); var form_fields = [{text: 'Share', name: 'share', type: 'checkbox', value: Albums.shared}, {text: 'Share recursive', name: 'recursive', type: 'checkbox', value: Albums.recursive}, {text: 'Shared gallery address', name: 'address', type: 'text', value: existing_token}]; @@ -53,7 +57,7 @@ function shareGallery() { } var actual_addr = ''; if (Albums.token) - actual_addr = document.location.origin + OC.linkTo('gallery', 'sharing.php') + '?token=' + Albums.token; + actual_addr = constructSharingPath(); $('input[name="address"]').val(actual_addr); } else { OC.dialogs.alert(t('gallery', 'Error: ') + r.cause, t('gallery', 'Internal error')); From 26fcb35a899fd8566a0e709dfd6b1f8a7d01094c Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 24 Mar 2012 14:49:44 -0400 Subject: [PATCH 081/302] Implement fopen() for Google Docs storage backend --- lib/filestorage/google.php | 122 +++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 26 deletions(-) diff --git a/lib/filestorage/google.php b/lib/filestorage/google.php index 4998554838..5de47650f6 100644 --- a/lib/filestorage/google.php +++ b/lib/filestorage/google.php @@ -40,7 +40,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $this->entries = array(); } - private function sendRequest($feedUri, $http_method, $postData = null) { + private function sendRequest($feedUri, $http_method, $isDownload = false, $postData = null) { $feedUri = trim($feedUri); // create an associative array from each key/value url query param pair. $params = array(); @@ -54,30 +54,71 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $tempStr .= '&' . urlencode($key) . '=' . urlencode($value); } $feedUri = preg_replace('/&/', '?', $tempStr, 1); - $req = OAuthRequest::from_consumer_and_token($this->consumer, $this->oauth_token, $http_method, $feedUri, $params); - $req->sign_request($this->sig_method, $this->consumer, $this->oauth_token); - $auth_header = $req->to_header(); - $result = send_signed_request($http_method, $feedUri, array($auth_header, 'Content-Type: application/atom+xml', 'GData-Version: 3.0'), $postData); - // TODO Return false if error is received - if (!$result) { - return false; + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->oauth_token, $http_method, $feedUri, $params); + $request->sign_request($this->sig_method, $this->consumer, $this->oauth_token); + $auth_header = $request->to_header(); + $headers = array($auth_header, 'Content-Type: application/atom+xml', 'GData-Version: 3.0'); + $curl = curl_init($feedUri); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_FAILONERROR, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + switch ($http_method) { + case 'GET': + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + break; + case 'POST': + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); + break; + case 'PUT': + $headers[] = 'If-Match: *'; + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $http_method); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); + break; + case 'DELETE': + $headers[] = 'If-Match: *'; + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $http_method); + break; + default: + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } - $result = explode('<', $result, 2); - $result = isset($result[1]) ? '<'.$result[1] : $result[0]; + if ($isDownload) { + $tmpFile = OC_Helper::tmpFile(); + $fp = fopen($tmpFile, 'w'); + curl_setopt($curl, CURLOPT_FILE, $fp); + curl_exec($curl); + curl_close($curl); + return $tmpFile; + } + $result = curl_exec($curl); + curl_close($curl); $dom = new DOMDocument(); $dom->loadXML($result); return $dom; } private function getResource($path) { - if (array_key_exists($path, $this->entries)) { - return $this->entries[$path]; + $file = basename($path); + if (array_key_exists($file, $this->entries)) { + return $this->entries[$file]; } else { - $title = basename($path); - $dom = $this->sendRequest('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$title, 'GET'); + // Strip the file extension; file could be a native Google Docs resource + if ($pos = strpos($file, '.')) { + $title = substr($file, 0, $pos); + $dom = $this->sendRequest('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$title, 'GET'); + // Check if request was successful and entry exists + if ($dom && $entry = $dom->getElementsByTagName('entry')->item(0)) { + $this->entries[$file] = $entry; + return $entry; + } + } + $dom = $this->sendRequest('https://docs.google.com/feeds/default/private/full?showfolders=true&title='.$file, 'GET'); // Check if request was successful and entry exists if ($dom && $entry = $dom->getElementsByTagName('entry')->item(0)) { - $this->entries[$path] = $entry; + $this->entries[$file] = $entry; return $entry; } return false; @@ -86,7 +127,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { private function getExtension($entry) { $mimetype = $this->getMimeType('', $entry); - switch($mimetype) { + switch ($mimetype) { case 'httpd/unix-directory': return ''; case 'application/vnd.oasis.opendocument.text': @@ -158,7 +199,10 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $name = $entry->getElementsByTagName('title')->item(0)->nodeValue; // Google Docs resources don't always include extensions in title if (!strpos($name, '.')) { - $name .= '.'.$this->getExtension($entry); + $extension = $this->getExtension($entry); + if ($extension != '') { + $name .= '.'.$extension; + } } $files[] = $name; // Cache entry for future use @@ -178,11 +222,15 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { } else if ($entry = $this->getResource($path)) { // NOTE: Native resources don't have a file size $stat['size'] = $entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'quotaBytesUsed')->item(0)->nodeValue; - $stat['atime'] = strtotime($entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'lastViewed')->item(0)->nodeValue); +// if (isset($atime = $entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'lastViewed')->item(0)->nodeValue)) +// $stat['atime'] = strtotime($entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'lastViewed')->item(0)->nodeValue); $stat['mtime'] = strtotime($entry->getElementsByTagName('updated')->item(0)->nodeValue); $stat['ctime'] = strtotime($entry->getElementsByTagName('published')->item(0)->nodeValue); } - return $stat; + if (isset($stat)) { + return $stat; + } + return false; } public function filetype($path) { @@ -278,14 +326,36 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { public function fopen($path, $mode) { if ($entry = $this->getResource($path)) { - $extension = $this->getExtension($path); - $downloadUri = $entry->getElementsByTagName('content')->item(0)->getAttribute('src'); - // TODO Non-native documents don't need these additional parameters - $downloadUri .= '&exportFormat='.$extension.'&format='.$extension; + switch ($mode) { + case 'r': + case 'rb': + $extension = $this->getExtension($entry); + $downloadUri = $entry->getElementsByTagName('content')->item(0)->getAttribute('src'); + // TODO Non-native documents don't need these additional parameters + $downloadUri .= '&exportFormat='.$extension.'&format='.$extension; + $tmpFile = $this->sendRequest($downloadUri, 'GET', true); + return fopen($tmpFile, 'r'); + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + // TODO Edit documents + } + } + return false; } public function getMimeType($path, $entry = null) { + // Entry can be passed, because extension is required for opendir and the entry can't be cached without the extension if ($entry == null) { if ($path == '' || $path == '/') { return 'httpd/unix-directory'; @@ -297,7 +367,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { $mimetype = $entry->getElementsByTagName('content')->item(0)->getAttribute('type'); // Native Google Docs resources often default to text/html, but it may be more useful to default to a corresponding ODF mimetype // Collections get reported as application/atom+xml, make sure it actually is a folder and fix the mimetype - if ($mimetype == 'text/html' || $mimetype == 'application/atom+xml') { + if ($mimetype == 'text/html' || $mimetype == 'application/atom+xml;type=feed') { $categories = $entry->getElementsByTagName('category'); foreach ($categories as $category) { if ($category->getAttribute('scheme') == 'http://schemas.google.com/g/2005#kind') { @@ -334,8 +404,8 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { return false; } - public function search($query) { - + public function touch($path, $mtime = null) { + } } \ No newline at end of file From a3125f164e3bb882ab25f42fee4da9aa691ed8bb Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sat, 24 Mar 2012 19:53:39 +0100 Subject: [PATCH 082/302] bookmarks tag placement & list width fix --- apps/bookmarks/js/bookmarks.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/bookmarks/js/bookmarks.js b/apps/bookmarks/js/bookmarks.js index 3557810d4b..a3a4779d26 100644 --- a/apps/bookmarks/js/bookmarks.js +++ b/apps/bookmarks/js/bookmarks.js @@ -9,9 +9,7 @@ $(document).ready(function() { fillWindow($('.bookmarks_list')); }); $(window).resize(); - $($('.bookmarks_list')).scroll(updateOnBottom); - - $('.bookmarks_list').empty(); + $('.bookmarks_list').scroll(updateOnBottom).empty().width($('#content').width()); getBookmarks(); }); From c23090b43c6729cde99b073fac6653185b96d4d0 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sat, 24 Mar 2012 19:54:54 +0100 Subject: [PATCH 083/302] spaces to tabs in bookmarks --- apps/bookmarks/js/bookmarks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bookmarks/js/bookmarks.js b/apps/bookmarks/js/bookmarks.js index a3a4779d26..9502af0a00 100644 --- a/apps/bookmarks/js/bookmarks.js +++ b/apps/bookmarks/js/bookmarks.js @@ -9,7 +9,7 @@ $(document).ready(function() { fillWindow($('.bookmarks_list')); }); $(window).resize(); - $('.bookmarks_list').scroll(updateOnBottom).empty().width($('#content').width()); + $('.bookmarks_list').scroll(updateOnBottom).empty().width($('#content').width()); getBookmarks(); }); From d139e3c3cd19103cdb798f2fc80a1a461ac08615 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 24 Mar 2012 15:27:42 -0400 Subject: [PATCH 084/302] Move Google Docs storage backend to files_remote app --- apps/files_remote/appinfo/app.php | 1 + {lib/filestorage => apps/files_remote/lib}/google.php | 2 -- apps/files_remote/tests/config.php | 7 +++++++ 3 files changed, 8 insertions(+), 2 deletions(-) rename {lib/filestorage => apps/files_remote/lib}/google.php (99%) diff --git a/apps/files_remote/appinfo/app.php b/apps/files_remote/appinfo/app.php index 25473d87fe..02c1c3ae31 100644 --- a/apps/files_remote/appinfo/app.php +++ b/apps/files_remote/appinfo/app.php @@ -8,3 +8,4 @@ OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_remote/lib/ftp.php'; OC::$CLASSPATH['OC_Filestorage_DAV']='apps/files_remote/lib/webdav.php'; +OC::$CLASSPATH['OC_Filestorage_Google']='apps/files_remote/lib/google.php'; diff --git a/lib/filestorage/google.php b/apps/files_remote/lib/google.php similarity index 99% rename from lib/filestorage/google.php rename to apps/files_remote/lib/google.php index 5de47650f6..0d6db1987f 100644 --- a/lib/filestorage/google.php +++ b/apps/files_remote/lib/google.php @@ -24,14 +24,12 @@ require_once 'common.inc.php'; class OC_Filestorage_Google extends OC_Filestorage_Common { - private $datadir; private $consumer; private $oauth_token; private $sig_method; private $entries; public function __construct($arguments) { - $this->datadir = $arguments['datadir']; $consumer_key = isset($arguments['consumer_key']) ? $arguments['consumer_key'] : 'anonymous'; $consumer_secret = isset($arguments['consumer_secret']) ? $arguments['consumer_secret'] : 'anonymous'; $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); diff --git a/apps/files_remote/tests/config.php b/apps/files_remote/tests/config.php index e2eea72f24..9b40d2b98c 100644 --- a/apps/files_remote/tests/config.php +++ b/apps/files_remote/tests/config.php @@ -12,4 +12,11 @@ return array( 'password'=>'test', 'root'=>'/owncloud/files/webdav.php', ), + 'google'=>array( + 'consumer_key'=>'anonymous', + 'consumer_secret'=>'anonymous', + 'token'=>'test', + 'token_secret'=>'test', + 'root'=>'/google', + ) ); From 34c08b3165ff7ee024006b4c425130fde057863c Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 24 Mar 2012 15:46:28 -0400 Subject: [PATCH 085/302] Add test for Google Docs storage backend, tester needs to token and token secret to config --- apps/files_remote/tests/google.php | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 apps/files_remote/tests/google.php diff --git a/apps/files_remote/tests/google.php b/apps/files_remote/tests/google.php new file mode 100644 index 0000000000..b49f9e4647 --- /dev/null +++ b/apps/files_remote/tests/google.php @@ -0,0 +1,38 @@ +. +*/ + +class Test_Filestorage_Google extends Test_FileStorage { + + private $config; + private $id; + + public function setUp(){ + $id=uniqid(); + $this->config=include('apps/files_remote/tests/config.php'); + $this->config['google']['root'].='/'.$id;//make sure we have an new empty folder to work in + $this->instance=new OC_Filestorage_Google($this->config['google']); + } + + public function tearDown(){ + $this->instance->rmdir('/'); + } +} \ No newline at end of file From ec40f69c9e7a6aba39bd74ff1549e70a93357e4f Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Sun, 25 Mar 2012 14:16:39 +0200 Subject: [PATCH 086/302] add error 403 site --- .htaccess | 1 + core/templates/403.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 core/templates/403.php diff --git a/.htaccess b/.htaccess index ebb28b0887..11520d743d 100644 --- a/.htaccess +++ b/.htaccess @@ -1,3 +1,4 @@ +ErrorDocument 403 /core/templates/403.php ErrorDocument 404 /core/templates/404.php php_value upload_max_filesize 512M diff --git a/core/templates/403.php b/core/templates/403.php new file mode 100644 index 0000000000..cdfef08ac7 --- /dev/null +++ b/core/templates/403.php @@ -0,0 +1,15 @@ +printPage(); + exit; +} +?> +
    +
  • + t( 'Access forbidden' ); ?>
    +

    +
  • +
From 102cf150b3a1de43877878debf9aef5f386d543b Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 26 Mar 2012 02:04:34 +0200 Subject: [PATCH 087/302] Fixed error in OC_Contacts_VCard::addFromDAVData as pointed out by Guillaume ZITTA --- apps/contacts/lib/vcard.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php index 3736f18c64..15a6176d40 100644 --- a/apps/contacts/lib/vcard.php +++ b/apps/contacts/lib/vcard.php @@ -228,7 +228,7 @@ class OC_Contacts_VCard{ * @param string $uri the uri of the card, default based on the UID * @return insertid on success or null if no card. */ - public static function add($aid, $card, $uri=null){ + public static function add($aid, OC_VObject $card, $uri=null){ if(is_null($card)){ OC_Log::write('contacts','OC_Contacts_VCard::add. No vCard supplied', OC_Log::ERROR); return null; @@ -267,7 +267,7 @@ class OC_Contacts_VCard{ */ public static function addFromDAVData($id,$uri,$data){ $card = OC_VObject::parse($data); - return self::add($id, $data, $uri); + return self::add($id, $card, $uri); } /** From 6ff89dcf727ab140c766c093b6f91b92a50ef7e9 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 26 Mar 2012 19:13:48 +0200 Subject: [PATCH 088/302] Removed obsolete code. --- apps/contacts/ajax/addproperty.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/apps/contacts/ajax/addproperty.php b/apps/contacts/ajax/addproperty.php index e1a3129283..d2c0291e8c 100644 --- a/apps/contacts/ajax/addproperty.php +++ b/apps/contacts/ajax/addproperty.php @@ -122,13 +122,4 @@ if(!OC_Contacts_VCard::edit($id,$vcard)) { exit(); } -$adr_types = OC_Contacts_App::getTypesOfProperty('ADR'); -$phone_types = OC_Contacts_App::getTypesOfProperty('TEL'); - -$tmpl = new OC_Template('contacts','part.property'); -$tmpl->assign('adr_types',$adr_types); -$tmpl->assign('phone_types',$phone_types); -$tmpl->assign('property',OC_Contacts_VCard::structureProperty($property,$line)); -$page = $tmpl->fetchPage(); - -OC_JSON::success(array('data' => array( 'checksum' => $checksum, 'page' => $page ))); +OC_JSON::success(array('data' => array( 'checksum' => $checksum ))); From 31e20d22834fd492f97ae38e6919f798ab6be932 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 26 Mar 2012 21:45:51 +0200 Subject: [PATCH 089/302] added some authors to the Authors file --- AUTHORS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AUTHORS b/AUTHORS index 02439884d2..f618df9311 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,6 +5,13 @@ ownCloud is written by: Jan-Christoph Borchardt Michael Gapczynski Arthur Schiwon + Bart Visscher + Georg Ehrke + Brice Maron + Tom Needham + Marvin Thomas Rabe + Florian Pritz + Bartek Przybylski … With help from many libraries and frameworks including: From ee31bc7995eaef0741dc1579d8b6b7e58f359ce4 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Mon, 26 Mar 2012 21:46:18 +0200 Subject: [PATCH 090/302] Contacts: Fix saveproperty for categories --- apps/contacts/ajax/saveproperty.php | 19 ++++++++++++++++++- apps/contacts/templates/part.contact.php | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php index 95a7ac2019..272a3f9b32 100644 --- a/apps/contacts/ajax/saveproperty.php +++ b/apps/contacts/ajax/saveproperty.php @@ -83,16 +83,34 @@ if($element != $name) { bailOut(OC_Contacts_App::$l10n->t('Something went FUBAR. ').$name.' != '.$element); } +/* preprocessing value */ switch($element) { case 'BDAY': $date = New DateTime($value); //$vcard->setDateTime('BDAY', $date, Sabre_VObject_Element_DateTime::DATE); $value = $date->format(DateTime::ATOM); + break; case 'FN': if(!$value) { // create a method thats returns an alternative for FN. //$value = getOtherValue(); } + break; + case 'CATEGORIES': + /* multi autocomplete triggers an save with empty value */ + if (!$value) { + $value = $vcard->getAsString('CATEGORIES'); + } + break; + case 'EMAIL': + $value = strtolower($value); + break; +} + +/* setting value */ +switch($element) { + case 'BDAY': + case 'FN': case 'N': case 'ORG': case 'NOTE': @@ -102,7 +120,6 @@ switch($element) { $vcard->setString($name, $value); break; case 'EMAIL': - $value = strtolower($value); case 'TEL': case 'ADR': // should I delete the property if empty or throw an error? debug('Setting element: (EMAIL/TEL/ADR)'.$element); diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index cb1e080a40..a1acb20b9c 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -64,7 +64,7 @@ $id = isset($_['id']) ? $_['id'] : ''; - + From 60a939dfcfceddbe1563e1712044a4a625bc70ac Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 26 Mar 2012 22:26:06 +0200 Subject: [PATCH 092/302] Small change and muting debug. --- apps/contacts/ajax/saveproperty.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php index 112d9031ce..924d873652 100644 --- a/apps/contacts/ajax/saveproperty.php +++ b/apps/contacts/ajax/saveproperty.php @@ -35,9 +35,9 @@ function bailOut($msg) { function debug($msg) { OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG); } -foreach ($_POST as $key=>$element) { - debug('_POST: '.$key.'=>'.print_r($element, true)); -} +// foreach ($_POST as $key=>$element) { +// debug('_POST: '.$key.'=>'.print_r($element, true)); +// } $id = isset($_POST['id'])?$_POST['id']:null; $name = isset($_POST['name'])?$_POST['name']:null; @@ -71,11 +71,6 @@ if(is_array($value)){ } else { $value = trim(strip_tags($value)); } -if(!$value) { - bailOut(OC_Contacts_App::$l10n->t('Cannot save empty value.')); -} - -debug('Element: '.$name.', value: '.print_r($value, true)); $vcard = OC_Contacts_App::getContactVCard( $id ); $line = OC_Contacts_App::getPropertyLineByChecksum($vcard, $checksum); @@ -112,6 +107,10 @@ switch($element) { break; } +if(!$value) { + bailOut(OC_Contacts_App::$l10n->t('Cannot save empty value.')); +} + /* setting value */ switch($element) { case 'BDAY': From 0c19e44a61229fe1376c8436b8a73e974b424539 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 26 Mar 2012 22:28:40 +0200 Subject: [PATCH 093/302] some more memory cleanup in OC_Image --- lib/image.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/image.php b/lib/image.php index 60a714880d..62f35b4fc9 100644 --- a/lib/image.php +++ b/lib/image.php @@ -526,6 +526,7 @@ class OC_Image { imagedestroy($process); return false; } + imagedestroy($this->resource); $this->resource = $process; return true; } @@ -558,7 +559,14 @@ class OC_Image { imagedestroy($process); return false; } + imagedestroy($this->resource); $this->resource = $process; return true; } + + public function __destruct(){ + if(is_resource($this->resource)){ + imagedestroy($this->resource); + } + } } From 0ba93323585cd277d845eb7d7600cd4cac4f84da Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 26 Mar 2012 22:33:37 +0200 Subject: [PATCH 094/302] some more memory cleanup in OC_Image --- lib/image.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/image.php b/lib/image.php index 62f35b4fc9..4717f81af7 100644 --- a/lib/image.php +++ b/lib/image.php @@ -317,10 +317,7 @@ class OC_Image { */ public function loadFromFileHandle($handle) { OC_Log::write('core',__METHOD__.'(): Trying', OC_Log::DEBUG); - $contents = ''; - while (!feof($handle)) { - $contents .= fread($handle, 8192); - } + $contents = stream_get_contents($handle); if($this->loadFromData($contents)) { return $this->resource; } @@ -486,6 +483,7 @@ class OC_Image { imagedestroy($process); return false; } + imagedestroy($this->resource); $this->resource = $process; return true; } From 3f881f1ca50b50bb1d564c6bdf55789cda10d4d4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 26 Mar 2012 22:40:38 +0200 Subject: [PATCH 095/302] memory cleanup in gallery --- apps/gallery/lib/photo.php | 6 +++--- apps/gallery/lib/scanner.php | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/gallery/lib/photo.php b/apps/gallery/lib/photo.php index 0c0f16e659..2263608dcc 100644 --- a/apps/gallery/lib/photo.php +++ b/apps/gallery/lib/photo.php @@ -66,9 +66,9 @@ class OC_Gallery_Photo { $stmt->execute(array($newpath, $newAlbumId, $oldAlbumId, $oldpath)); } - public static function getThumbnail($image_name, $owner = null) { - if (!$owner) $owner = OC_User::getUser(); - $save_dir = OC_Config::getValue("datadirectory").'/'. $owner .'/gallery/'; + public static function getThumbnail($image_name, $owner = null) { + if (!$owner) $owner = OC_User::getUser(); + $save_dir = OC_Config::getValue("datadirectory").'/'. $owner .'/gallery/'; $save_dir .= dirname($image_name). '/'; $image_path = $image_name; $thumb_file = $save_dir . basename($image_name); diff --git a/apps/gallery/lib/scanner.php b/apps/gallery/lib/scanner.php index c8825c267e..34b9bb8da3 100644 --- a/apps/gallery/lib/scanner.php +++ b/apps/gallery/lib/scanner.php @@ -81,6 +81,7 @@ class OC_Gallery_Scanner { } } imagepng($thumbnail, OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery/' . $albumName.'.png'); + imagedestroy($thumbnail); } public static function createIntermediateAlbums() { From 73c6db5c8e6d876adf7b6c1c91049c66be26ba87 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 26 Mar 2012 23:53:48 +0200 Subject: [PATCH 096/302] crop and resize in a single step when creating thumbnail also so more explicit memory cleanup --- apps/gallery/lib/photo.php | 11 ++++++----- apps/gallery/lib/scanner.php | 1 + lib/image.php | 26 ++++++++++++++++++++------ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/apps/gallery/lib/photo.php b/apps/gallery/lib/photo.php index 2263608dcc..3bb6f9129f 100644 --- a/apps/gallery/lib/photo.php +++ b/apps/gallery/lib/photo.php @@ -72,6 +72,9 @@ class OC_Gallery_Photo { $save_dir .= dirname($image_name). '/'; $image_path = $image_name; $thumb_file = $save_dir . basename($image_name); + if (!is_dir($save_dir)) { + mkdir($save_dir, 0777, true); + } if (file_exists($thumb_file)) { $image = new OC_Image($thumb_file); } else { @@ -81,17 +84,15 @@ class OC_Gallery_Photo { } $image = new OC_Image($image_path); if ($image->valid()) { - $image->centerCrop(); - $image->resize(200); + $image->centerCrop(200); $image->fixOrientation(); - if (!is_dir($save_dir)) { - mkdir($save_dir, 0777, true); - } $image->save($thumb_file); } } if ($image->valid()) { return $image; + }else{ + $image->destroy(); } return null; } diff --git a/apps/gallery/lib/scanner.php b/apps/gallery/lib/scanner.php index 34b9bb8da3..6d2d44d428 100644 --- a/apps/gallery/lib/scanner.php +++ b/apps/gallery/lib/scanner.php @@ -79,6 +79,7 @@ class OC_Gallery_Scanner { if ($image && $image->valid()) { imagecopyresampled($thumbnail, $image->resource(), $i*200, 0, 0, 0, 200, 200, 200, 200); } + $image->destroy(); } imagepng($thumbnail, OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery/' . $albumName.'.png'); imagedestroy($thumbnail); diff --git a/lib/image.php b/lib/image.php index 4717f81af7..b3c7d52ec2 100644 --- a/lib/image.php +++ b/lib/image.php @@ -216,7 +216,7 @@ class OC_Image { OC_Log::write('core','OC_Image->fixOrientation() No readable file path set.', OC_Log::DEBUG); return false; } - $exif = @exif_read_data($this->filepath, 'IFD0'); + $exif = @exif_read_data($this->filepath, 'IFD0'); if(!$exif) { return false; } @@ -267,6 +267,7 @@ class OC_Image { if($res) { if(imagealphablending($res, true)) { if(imagesavealpha($res, true)) { + imagedestroy($this->resource); $this->resource = $res; return true; } else { @@ -490,9 +491,10 @@ class OC_Image { /** * @brief Crops the image to the middle square. If the image is already square it just returns. + * @param int maximum size for the result (optional) * @returns bool for success or failure */ - public function centerCrop() { + public function centerCrop($size=0) { if(!$this->valid()) { OC_Log::write('core','OC_Image->centerCrop, No image loaded', OC_Log::ERROR); return false; @@ -512,13 +514,20 @@ class OC_Image { $y = ($height_orig/2) - ($height/2); $x = 0; } - $process = imagecreatetruecolor($width, $height); + if($size>0){ + $targetWidth=$size; + $targetHeight=$size; + }else{ + $targetWidth=$width; + $targetHeight=$height; + } + $process = imagecreatetruecolor($targetWidth, $targetHeight); if ($process == false) { OC_Log::write('core','OC_Image->centerCrop. Error creating true color image',OC_Log::ERROR); imagedestroy($process); return false; } - imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $width, $height, $width, $height); + imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height); if ($process == false) { OC_Log::write('core','OC_Image->centerCrop. Error resampling process image '.$width.'x'.$height,OC_Log::ERROR); imagedestroy($process); @@ -562,9 +571,14 @@ class OC_Image { return true; } - public function __destruct(){ - if(is_resource($this->resource)){ + public function destroy(){ + if($this->valid()){ imagedestroy($this->resource); } + $this->resource=null; + } + + public function __destruct(){ + $this->destroy(); } } From eb3ec05f882ec54cc19009966e62feb31f5577a4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 26 Mar 2012 23:56:07 +0200 Subject: [PATCH 097/302] fix gallery scan when an image cant be loaded --- apps/gallery/lib/scanner.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gallery/lib/scanner.php b/apps/gallery/lib/scanner.php index 6d2d44d428..0317f943e5 100644 --- a/apps/gallery/lib/scanner.php +++ b/apps/gallery/lib/scanner.php @@ -78,8 +78,8 @@ class OC_Gallery_Scanner { $image = OC_Gallery_Photo::getThumbnail($files[$i]); if ($image && $image->valid()) { imagecopyresampled($thumbnail, $image->resource(), $i*200, 0, 0, 0, 200, 200, 200, 200); + $image->destroy(); } - $image->destroy(); } imagepng($thumbnail, OC_Config::getValue("datadirectory").'/'. OC_User::getUser() .'/gallery/' . $albumName.'.png'); imagedestroy($thumbnail); From 128d446f39de93ce63bda3b8ef261446ad6efb0b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 27 Mar 2012 00:21:31 +0200 Subject: [PATCH 098/302] fix timestamps when viewing logs --- settings/js/log.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/js/log.js b/settings/js/log.js index 3814d9c10b..ae83f0a628 100644 --- a/settings/js/log.js +++ b/settings/js/log.js @@ -32,7 +32,7 @@ OC.Log={ row.append(messageTd); var timeTd=$(''); - timeTd.text(formatDate(entry.time)); + timeTd.text(formatDate(entry.time*1000)); row.append(timeTd); $('#log').append(row); } From 266699ddf90a074aa4c4f5b50a63f5f430842bde Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 27 Mar 2012 00:42:15 +0200 Subject: [PATCH 099/302] fix square images not getting proper thumbnails --- lib/image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/image.php b/lib/image.php index b3c7d52ec2..4c53dc32f5 100644 --- a/lib/image.php +++ b/lib/image.php @@ -501,7 +501,7 @@ class OC_Image { } $width_orig=imageSX($this->resource); $height_orig=imageSY($this->resource); - if($width_orig === $height_orig) { + if($width_orig === $height_orig and $size==0) { return true; } $ratio_orig = $width_orig/$height_orig; From bcef775d6bc93144316d4eea74f90c5be03576b0 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 27 Mar 2012 01:18:38 +0200 Subject: [PATCH 100/302] Overwrite Download-ZIP if it already exists. We do not want to show alien content. --- lib/files.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/files.php b/lib/files.php index 57ebb9005a..f1cf7573ba 100644 --- a/lib/files.php +++ b/lib/files.php @@ -64,7 +64,7 @@ class OC_Files { set_time_limit(0); $zip = new ZipArchive(); $filename = get_temp_dir()."/ownCloud.zip"; - if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) { exit("cannot open <$filename>\n"); } foreach($files as $file){ @@ -85,7 +85,7 @@ class OC_Files { set_time_limit(0); $zip = new ZipArchive(); $filename = get_temp_dir()."/ownCloud.zip"; - if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) { exit("cannot open <$filename>\n"); } $file=$dir.'/'.$files; From 24bc639222b3f4344bed388ca7fd834f8e70dff8 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 27 Mar 2012 01:19:34 +0200 Subject: [PATCH 101/302] Do not use always the same name for download-zip to avoid collisions. --- lib/files.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/files.php b/lib/files.php index f1cf7573ba..e7bfbbc19b 100644 --- a/lib/files.php +++ b/lib/files.php @@ -63,7 +63,7 @@ class OC_Files { $executionTime = intval(ini_get('max_execution_time')); set_time_limit(0); $zip = new ZipArchive(); - $filename = get_temp_dir()."/ownCloud.zip"; + $filename = get_temp_dir().'/ownCloud_'.mt_rand(10000,99999).'.zip'; if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) { exit("cannot open <$filename>\n"); } @@ -84,7 +84,7 @@ class OC_Files { $executionTime = intval(ini_get('max_execution_time')); set_time_limit(0); $zip = new ZipArchive(); - $filename = get_temp_dir()."/ownCloud.zip"; + $filename = get_temp_dir().'/ownCloud_'.mt_rand(10000,99999).'.zip'; if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) { exit("cannot open <$filename>\n"); } From c92fc9bf651e79aa44803eeeed1d16499a5f08e6 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 27 Mar 2012 02:24:52 +0200 Subject: [PATCH 102/302] return path of tmp file like filestorage/common does. Fixes broken folder-/multifile-download. --- lib/filesystemview.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/filesystemview.php b/lib/filesystemview.php index 89e0385fe9..39e47975b2 100644 --- a/lib/filesystemview.php +++ b/lib/filesystemview.php @@ -23,11 +23,11 @@ class OC_FilesystemView { private $fakeRoot=''; - + public function __construct($root){ $this->fakeRoot=$root; } - + public function getAbsolutePath($path){ if(!$path){ $path='/'; @@ -141,7 +141,7 @@ class OC_FilesystemView { while (!feof($handle)) { echo fread($handle, $chunkSize); @ob_flush(); - flush(); + flush(); } return $this->filesize($path); } @@ -282,7 +282,8 @@ class OC_FilesystemView { if($source){ $extention=substr($path,strrpos($path,'.')); $tmpFile=OC_Helper::tmpFile($extention); - return file_put_contents($tmpFile,$source); + file_put_contents($tmpFile,$source); + return $tmpFile; } } } From 0a07e5fdaabfc5652ec96b4179e2c9433ab42002 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 27 Mar 2012 01:09:28 +0200 Subject: [PATCH 103/302] Added some documentation. --- core/js/oc-vcategories.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/js/oc-vcategories.txt b/core/js/oc-vcategories.txt index 76d4245f5e..31216f80bd 100644 --- a/core/js/oc-vcategories.txt +++ b/core/js/oc-vcategories.txt @@ -17,7 +17,7 @@ Set the app specific values in your javascript file. This is what I've used for OCCategories.app = 'contacts'; OCCategories.changed = Contacts.UI.Card.categoriesChanged; -If OCCategories.changed point is set that function will be called each time the categories have been changed +If OCCategories.changed is set that function will be called each time the categories have been changed in the editor (add/delete/rescan) to allow the app to update the UI accordingly. The only argument to the function is an array of the updated categories e.g.: @@ -25,4 +25,9 @@ OCCategories.changed = function(categories) { for(var category in categories) { console.log(categories[category]); } -} \ No newline at end of file +} + +To show the categories editor call: + + OCCategories.edit() + From ff038f8766d49b2a4913ef5ae0a206758f2cdaaa Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 27 Mar 2012 10:12:30 +0200 Subject: [PATCH 104/302] Contacts: Update contact list thumbnail when deleting PHOTO. --- apps/contacts/js/contacts.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index 18214cb1cc..82aadb54b8 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -667,6 +667,10 @@ Contacts={ console.log('NOTE or PHOTO'); Contacts.UI.propertyContainerFor(obj).hide(); Contacts.UI.propertyContainerFor(obj).data('checksum', ''); + if(proptype == 'PHOTO') { + console.log('Delete PHOTO'); + Contacts.UI.Contacts.refreshThumbnail(Contacts.UI.Card.id); + } } else { $('dl dt[data-element="'+proptype+'"],dd[data-element="'+proptype+'"]').hide(); $('dl dd[data-element="'+proptype+'"]').data('checksum', ''); @@ -990,7 +994,7 @@ Contacts={ OC.dialogs.alert(response.data.message, t('contacts', 'Error')); } }); - $('#contacts [data-id="'+this.id+'"]').find('a').css('background','url(thumbnail.php?id='+this.id+'&refresh=1'+Math.random()+') no-repeat'); + Contacts.UI.Contacts.refreshThumbnail(this.id); }, addMail:function() { //alert('addMail'); @@ -1192,6 +1196,9 @@ Contacts={ $(this).find('a').css('background','url(thumbnail.php?id='+$(this).data('id')+') no-repeat'); } }); + }, + refreshThumbnail:function(id){ + $('#contacts [data-id="'+id+'"]').find('a').css('background','url(thumbnail.php?id='+id+'&refresh=1'+Math.random()+') no-repeat'); } } } From fa165498ccfe2becbdaf997b4e36336e794cce50 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 27 Mar 2012 12:26:59 +0200 Subject: [PATCH 105/302] Improvements in adding/deleting properties. --- apps/contacts/js/contacts.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index 82aadb54b8..e182702745 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -613,10 +613,12 @@ Contacts={ this.loadPhoto(true); $('#file_upload_form').show(); $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); + $('#file_upload_start').trigger('click'); break; case 'NOTE': $('#note').show(); $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); + $('#note').find('textarea').focus(); break; case 'EMAIL': if($('#emaillist>li').length == 1) { @@ -644,6 +646,7 @@ Contacts={ case 'BDAY': case 'CATEGORIES': $('dl dt[data-element="'+type+'"],dd[data-element="'+type+'"]').show(); + $('dd[data-element="'+type+'"]').find('input').focus(); $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); break; } @@ -670,10 +673,13 @@ Contacts={ if(proptype == 'PHOTO') { console.log('Delete PHOTO'); Contacts.UI.Contacts.refreshThumbnail(Contacts.UI.Card.id); + } else if(proptype == 'NOTE') { + $('#note').find('textarea').val(''); } } else { $('dl dt[data-element="'+proptype+'"],dd[data-element="'+proptype+'"]').hide(); $('dl dd[data-element="'+proptype+'"]').data('checksum', ''); + $('dl dd[data-element="'+proptype+'"]').find('input').val(''); } $('#contacts_propertymenu a[data-type="'+proptype+'"]').parent().show(); Contacts.UI.loading(obj, false); From cb2dd97509ffd039fbd321aea4a8d631e3effcc7 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 27 Mar 2012 12:29:19 +0200 Subject: [PATCH 106/302] Trigger autocomplete wo button. --- apps/contacts/js/jquery.multi-autocomplete.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/contacts/js/jquery.multi-autocomplete.js b/apps/contacts/js/jquery.multi-autocomplete.js index 1c923a2543..7607de3f91 100644 --- a/apps/contacts/js/jquery.multi-autocomplete.js +++ b/apps/contacts/js/jquery.multi-autocomplete.js @@ -5,14 +5,25 @@ (function( $ ) { $.widget('ui.multiple_autocomplete', { _create: function() { + var self = this; function split( val ) { return val.split( /,\s*/ ); } function extractLast( term ) { return split( term ).pop(); } + function showOptions() { + if(!self.element.autocomplete('widget').is(':visible') && self.element.val().trim() == '') { + self.element.autocomplete('search', ''); + } + } //console.log('_create: ' + this.options['id']); - var self = this; + this.element.bind('click', function( event ) { + showOptions(); + }); + this.element.bind('input', function( event ) { + showOptions(); + }); this.element.bind('blur', function( event ) { var tmp = self.element.val().trim(); if(tmp[tmp.length-1] == ',') { From b201e5152840406f0b5de9a403fd8f6ceedd3636 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 27 Mar 2012 20:43:44 +0000 Subject: [PATCH 107/302] Stop error on export --- lib/migration/provider.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/migration/provider.php b/lib/migration/provider.php index d592ed6726..98804ee91c 100644 --- a/lib/migration/provider.php +++ b/lib/migration/provider.php @@ -32,13 +32,14 @@ abstract class OC_Migration_Provider{ * @breif sets the OC_Migration_Content object to $this->content * @param $content a OC_Migration_Content object */ - public function setData( $uid, $content, $info=false ){ + public function setData( $uid, $content, $info=null ){ $this->content = $content; $this->uid = $uid; - $this->olduid = $info->exporteduser; + if( !is_null( $info ) ){ + $this->olduid = $info->exporteduser; + $this->appinfo = $info->apps->$id; + } $id = $this->id; - $this->appinfo = $info->apps->$id; - } /** From 553f4533c081e38e65ffb9981063d944fc58f431 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 27 Mar 2012 20:45:37 +0000 Subject: [PATCH 108/302] look for migrate.php in appinfo folder --- apps/bookmarks/{lib => appinfo}/migrate.php | 0 lib/migrate.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename apps/bookmarks/{lib => appinfo}/migrate.php (100%) diff --git a/apps/bookmarks/lib/migrate.php b/apps/bookmarks/appinfo/migrate.php similarity index 100% rename from apps/bookmarks/lib/migrate.php rename to apps/bookmarks/appinfo/migrate.php diff --git a/lib/migrate.php b/lib/migrate.php index 718dc57b9f..6b1497d1d0 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -64,7 +64,7 @@ class OC_Migrate{ $apps = OC_App::getAllApps(); foreach($apps as $app){ - $path = OC::$SERVERROOT . '/apps/' . $app . '/lib/migrate.php'; + $path = OC::$SERVERROOT . '/apps/' . $app . '/appinfo/migrate.php'; if( file_exists( $path ) ){ include( $path ); } From 31d268fe929abefbbf14e76a96c02f18235451a8 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 27 Mar 2012 20:55:53 +0000 Subject: [PATCH 109/302] check for sqlite --- lib/migrate.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/migrate.php b/lib/migrate.php index 6b1497d1d0..d7d1400e2f 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -482,6 +482,12 @@ class OC_Migrate{ $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + // DB type + if( !is_callable( 'sqlite_open' ) || !class_exists( 'SQLite3' ) ){ + OC_Log::write( 'migration', 'SQLite not found', OC_Log::ERROR ); + return false; + } + // Prepare options array $options = array( 'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE), From ef33219e4f83652676c7a668b6126c3c1c0da34d Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 27 Mar 2012 21:21:14 +0000 Subject: [PATCH 110/302] import method returns each apps' import status --- apps/user_migrate/admin.php | 16 ++++++++++++++- lib/migrate.php | 40 ++++++++++++++++++++++++------------- lib/migration/provider.php | 2 +- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index c1afb0aed4..1c4894c0f2 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -40,8 +40,22 @@ if (isset($_POST['user_import'])) { exit(); } - if( !OC_Migrate::import( $to, 'user' ) ){ + if( !$appsstatus = OC_Migrate::import( $to, 'user' ) ){ die( 'failed to to import' ); + } else { + // Check import status + foreach( $appsstatus as $app => $status ){ + if( $status != 'true' ){ + // It failed for some reason + if( $status == 'notsupported' ){ + $notsupported[] = $app; + } else if( !$status ){ + $failed[] = $app; + } + } + } + die(print_r($notsupported)); + die( 'Some apps failed to import, or were not supported.' ); } diff --git a/lib/migrate.php b/lib/migrate.php index d7d1400e2f..b16b84b2a4 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -246,14 +246,14 @@ class OC_Migrate{ return false; } // Import user app data - if( !self::importAppData( $extractpath . $json->exporteduser . '/migration.db', $json, self::$uid ) ){ + if( !$appsimported = self::importAppData( $extractpath . $json->exporteduser . '/migration.db', $json, self::$uid ) ){ return false; } // All done! if( !self::unlink_r( $extractpath ) ){ OC_Log::write( 'migration', 'Failed to delete the extracted zip', OC_Log::ERROR ); } - return true; + return $appsimported; break; case 'instance': // Check for new data dir and dbexport before doing anything @@ -611,9 +611,9 @@ class OC_Migrate{ /** * @breif imports a new user * @param $db string path to migration.db - * @param $info array of migration ino + * @param $info object of migration info * @param $uid optional uid to use - * @return bool if the import succedded + * @return array of apps with import statuses, or false on failure. */ public static function importAppData( $db, $info, $uid=null ){ // Check if the db exists @@ -641,21 +641,33 @@ class OC_Migrate{ // Is the app in the export? $id = $provider->getID(); if( isset( $info->apps->$id ) ){ - // Did it succeed? - if( $info->apps->$id->success ){ - // Give the provider the content object - if( !self::connectDB( $db ) ){ - return false; + // Is the app installed + if( !OC_App::isEnabled( $id ) ){ + OC_Log::write( 'migration', 'App: ' . $id . ' is not installed, can\'t import data.', OC_Log::INFO ); + $appsstatus[$id] = 'notsupported'; + } else { + // Did it succeed on export? + if( $info->apps->$id->success ){ + // Give the provider the content object + if( !self::connectDB( $db ) ){ + return false; + } + $content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + $provider->setData( self::$uid, $content, $info ); + // Then do the import + if( !$appsstatus[$id] = $provider->import( $info->apps->$id, $importinfo ) ){ + // Failed to import app + OC_Log::write( 'migration', 'Failed to import app data for user: ' . self::$uid . ' for app: ' . $id, OC_Log::ERROR ); + } + } else { + // Add to failed list + $appsstatus[$id] = false; } - $content = new OC_Migration_Content( self::$zip, self::$MDB2 ); - $provider->setData( self::$uid, $content, $info ); - // Then do the import - $provider->import( $info->apps->$id, $importinfo ); } } } - return true; + return $appsstatus; } diff --git a/lib/migration/provider.php b/lib/migration/provider.php index 98804ee91c..feae29f135 100644 --- a/lib/migration/provider.php +++ b/lib/migration/provider.php @@ -35,11 +35,11 @@ abstract class OC_Migration_Provider{ public function setData( $uid, $content, $info=null ){ $this->content = $content; $this->uid = $uid; + $id = $this->id; if( !is_null( $info ) ){ $this->olduid = $info->exporteduser; $this->appinfo = $info->apps->$id; } - $id = $this->id; } /** From bd4fd76bfb94a7b4af0f85838ac9e539e730fa5d Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 27 Mar 2012 21:35:29 +0000 Subject: [PATCH 111/302] Fix bookmarks migration provider --- apps/bookmarks/appinfo/migrate.php | 4 ++-- apps/user_migrate/admin.php | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/bookmarks/appinfo/migrate.php b/apps/bookmarks/appinfo/migrate.php index 02c96e5963..603a8b72d3 100644 --- a/apps/bookmarks/appinfo/migrate.php +++ b/apps/bookmarks/appinfo/migrate.php @@ -48,9 +48,9 @@ class OC_Migration_Provider_Bookmarks extends OC_Migration_Provider{ } // Now tags foreach($idmap as $oldid => $newid){ - $query = $this->content->prepare( "SELECT * FROM bookmarks_tags WHERE user_id LIKE ?" ); + $query = $this->content->prepare( "SELECT * FROM bookmarks_tags WHERE bookmark_id LIKE ?" ); $results = $query->execute( array( $oldid ) ); - while( $row = $data->fetchRow() ){ + while( $row = $results->fetchRow() ){ // Import the tags for this bookmark, using the new bookmark id $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks_tags(bookmark_id, tag) VALUES (?, ?)" ); $query->execute( array( $newid, $row['tag'] ) ); diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index 1c4894c0f2..61b83c2a14 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -54,11 +54,16 @@ if (isset($_POST['user_import'])) { } } } - die(print_r($notsupported)); - die( 'Some apps failed to import, or were not supported.' ); + // Any problems? + if( isset( $notsupported ) || isset( $failed ) ){ + if( count( $failed ) > 0 ){ + die( 'Some apps failed to import. View the log please.' ); + } else if( count( $notsupported ) > 0 ){ + die( 'Some apps were not found in this owncloud instance and therefore could not be installed' ); + } + } } - } else { // fill template $tmpl = new OC_Template('user_migrate', 'admin'); From bcda46eda3b3b9208609bc5a9497632aa0dabfa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 28 Mar 2012 16:07:50 +0200 Subject: [PATCH 112/302] webfinger installation creates symlink in document root --- apps/user_webfinger/appinfo/install.php | 32 ++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/user_webfinger/appinfo/install.php b/apps/user_webfinger/appinfo/install.php index f570a3a249..9ba953a4e6 100644 --- a/apps/user_webfinger/appinfo/install.php +++ b/apps/user_webfinger/appinfo/install.php @@ -3,4 +3,34 @@ $appInfoDir = __DIR__; $thisAppDir = dirname($appInfoDir); $appsDir = dirname($thisAppDir); $ownCloudDir = dirname($appsDir); -@symlink($thisAppDir, $ownCloudDir.'/.well-known'); +$docRoot = $_SERVER['DOCUMENT_ROOT']; +if(file_exists($docRoot . '/.well-known/host-meta')) { + OC_Log::write( + 'user_webfinger', + $docRoot . "/.well-known already exists; installation aborted", + OC_Log::ERROR + ); +} else { + if(@symlink($thisAppDir, $docRoot . '/.well-known')) { + OC_Log::write( + 'user_webfinger', + "Webfinger symlink created at " . $docRoot . "/.well-known", + OC_Log::INFO + ); + } else { + if(@symlink($thisAppDir, $ownCloudDir . '/.well-known')) { + OC_Log::write( + 'user_webfinger', + "Couldn't create webfinger symlink in document root, linked to " . $ownCloudDir . "/.well-known instead", + OC_Log::WARN + ); + } else { + OC_Log::write( + 'user_webfinger', + "Couldn't create webfinger symlink, either check write permissions or create the link manually!", + OC_Log::ERROR + ); + } + } +} +?> From 34a0128ddf01a44302f89a1dfa14bb9565a8ae47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 28 Mar 2012 16:12:34 +0200 Subject: [PATCH 113/302] whitespace indentation fix --- apps/user_webfinger/appinfo/install.php | 52 ++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/user_webfinger/appinfo/install.php b/apps/user_webfinger/appinfo/install.php index 9ba953a4e6..775141dce4 100644 --- a/apps/user_webfinger/appinfo/install.php +++ b/apps/user_webfinger/appinfo/install.php @@ -5,32 +5,32 @@ $appsDir = dirname($thisAppDir); $ownCloudDir = dirname($appsDir); $docRoot = $_SERVER['DOCUMENT_ROOT']; if(file_exists($docRoot . '/.well-known/host-meta')) { - OC_Log::write( - 'user_webfinger', - $docRoot . "/.well-known already exists; installation aborted", - OC_Log::ERROR - ); + OC_Log::write( + 'user_webfinger', + $docRoot . "/.well-known already exists; installation aborted", + OC_Log::ERROR + ); } else { - if(@symlink($thisAppDir, $docRoot . '/.well-known')) { - OC_Log::write( - 'user_webfinger', - "Webfinger symlink created at " . $docRoot . "/.well-known", - OC_Log::INFO - ); - } else { - if(@symlink($thisAppDir, $ownCloudDir . '/.well-known')) { - OC_Log::write( - 'user_webfinger', - "Couldn't create webfinger symlink in document root, linked to " . $ownCloudDir . "/.well-known instead", - OC_Log::WARN - ); - } else { - OC_Log::write( - 'user_webfinger', - "Couldn't create webfinger symlink, either check write permissions or create the link manually!", - OC_Log::ERROR - ); - } - } + if(@symlink($thisAppDir, $docRoot . '/.well-known')) { + OC_Log::write( + 'user_webfinger', + "Webfinger symlink created at " . $docRoot . "/.well-known", + OC_Log::INFO + ); + } else { + if(@symlink($thisAppDir, $ownCloudDir . '/.well-known')) { + OC_Log::write( + 'user_webfinger', + "Couldn't create webfinger symlink in document root, linked to " . $ownCloudDir . "/.well-known instead", + OC_Log::WARN + ); + } else { + OC_Log::write( + 'user_webfinger', + "Couldn't create webfinger symlink, either check write permissions or create the link manually!", + OC_Log::ERROR + ); + } + } } ?> From 0d16f177cba150156253b2a436f67456fd3d3984 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 28 Mar 2012 16:21:54 +0200 Subject: [PATCH 114/302] add scrollbar to the music list - bugfix for oc-301 --- apps/media/css/music.css | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/media/css/music.css b/apps/media/css/music.css index 0717362401..164a6c62ae 100644 --- a/apps/media/css/music.css +++ b/apps/media/css/music.css @@ -22,6 +22,7 @@ div.jp-volume-bar-value { background:#ccc; width:0; height:0.4em; } #leftcontent img.remove { display:none; float:right; cursor:pointer; opacity: 0; } #leftcontent li:hover img.remove { display:inline; opacity: .3; } #leftcontent li div.label { float: left; width: 200px; overflow: hidden; text-overflow: ellipsis; } +#rightcontent { overflow: auto; } #playlist li { list-style-type:none; } .template { display:none; } .collection_playing { background:#eee; font-weight: bold; } From e5c009599d428db392e06e374bb5e68b0eb773c8 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 28 Mar 2012 16:38:42 +0200 Subject: [PATCH 115/302] remove check for variable which was set one line above - bugfix for oc-324 --- lib/db.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/db.php b/lib/db.php index 9fab51edfc..a0fb6c385d 100644 --- a/lib/db.php +++ b/lib/db.php @@ -318,9 +318,6 @@ class OC_DB { // Make changes and save them to an in-memory file $file2 = 'static://db_scheme'; - if($file2 == ''){ - die('could not create tempfile in get_temp_dir() - aborting'); - } $content = str_replace( '*dbname*', $CONFIG_DBNAME, $content ); $content = str_replace( '*dbprefix*', $CONFIG_DBTABLEPREFIX, $content ); if( $CONFIG_DBTYPE == 'pgsql' ){ //mysql support it too but sqlite doesn't From 08b4f996d339fd4c7372f93850c8cea294f7ede5 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 28 Mar 2012 17:06:16 +0200 Subject: [PATCH 116/302] add backslash r to the newline var --- apps/calendar/ajax/import/import.php | 2 +- apps/calendar/export.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/calendar/ajax/import/import.php b/apps/calendar/ajax/import/import.php index c0797f6e42..d0bdab4f0d 100644 --- a/apps/calendar/ajax/import/import.php +++ b/apps/calendar/ajax/import/import.php @@ -10,7 +10,7 @@ ob_start(); require_once('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); -$nl = "\n"; +$nl = "\n\r"; $progressfile = OC::$APPSROOT . '/apps/calendar/import_tmp/' . md5(session_id()) . '.txt'; if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); diff --git a/apps/calendar/export.php b/apps/calendar/export.php index 9886ad8e8c..2736eec96c 100644 --- a/apps/calendar/export.php +++ b/apps/calendar/export.php @@ -11,7 +11,7 @@ OC_Util::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); $cal = isset($_GET['calid']) ? $_GET['calid'] : NULL; $event = isset($_GET['eventid']) ? $_GET['eventid'] : NULL; -$nl = "\n"; +$nl = "\n\r"; if(isset($cal)){ $calendar = OC_Calendar_App::getCalendar($cal); $calobjects = OC_Calendar_Object::all($cal); From f42897344fd938edcdc955c0dd0094391f5df18a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 28 Mar 2012 16:26:23 +0200 Subject: [PATCH 117/302] allow running a single test or group of tests --- tests/index.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/index.php b/tests/index.php index 2e86366740..a6f678b3bc 100644 --- a/tests/index.php +++ b/tests/index.php @@ -38,6 +38,7 @@ foreach($apps as $app){ } function loadTests($dir=''){ + $test=isset($_GET['test'])?$_GET['test']:false; if($dh=opendir($dir)){ while($name=readdir($dh)){ if(substr($name,0,1)!='.'){//no hidden files, '.' or '..' @@ -45,10 +46,13 @@ function loadTests($dir=''){ if(is_dir($file)){ loadTests($file); }elseif(substr($file,-4)=='.php' and $file!=__FILE__){ - $testCase=new TestSuite(getTestName($file)); - $testCase->addFile($file); - if($testCase->getSize()>0){ - $testCase->run(new HtmlReporter()); + $name=getTestName($file); + if($test===false or $test==$name or substr($name,0,strlen($test))==$test){ + $testCase=new TestSuite($name); + $testCase->addFile($file); + if($testCase->getSize()>0){ + $testCase->run(new HtmlReporter()); + } } } } From d8e9db207f94d8a46cd8a81caa1b49cd64843259 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 28 Mar 2012 22:30:55 +0200 Subject: [PATCH 118/302] add tempory folder utility to OC_Helper --- lib/helper.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/helper.php b/lib/helper.php index 66f31d929b..efff00c2fe 100755 --- a/lib/helper.php +++ b/lib/helper.php @@ -432,6 +432,19 @@ class OC_Helper { self::$tmpFiles[]=$file; return $file; } + + /** + * create a temporary folder with an unique filename + * @return string + * + * temporary files are automatically cleaned up after the script is finished + */ + public static function tmpFolder(){ + $path=get_temp_dir().'/'.md5(time().rand()); + mkdir($path); + self::$tmpFiles[]=$path; + return $path.'/'; + } /** * remove all files created by self::tmpFile @@ -439,7 +452,7 @@ class OC_Helper { public static function cleanTmp(){ foreach(self::$tmpFiles as $file){ if(file_exists($file)){ - unlink($file); + self::rmdirr($file); } } } From e57de98bbedfbf71841235fe01b10787d5e41962 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 28 Mar 2012 22:31:45 +0200 Subject: [PATCH 119/302] add extract all option to OC_Archive --- apps/files_archive/lib/archive.php | 7 +++++++ apps/files_archive/lib/zip.php | 9 +++++++++ apps/files_archive/tests/archive.php | 15 +++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/files_archive/lib/archive.php b/apps/files_archive/lib/archive.php index be89f894fb..3be3388a3b 100644 --- a/apps/files_archive/lib/archive.php +++ b/apps/files_archive/lib/archive.php @@ -77,6 +77,13 @@ abstract class OC_Archive{ * @return bool */ abstract function extractFile($path,$dest); + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + abstract function extract($dest); /** * check if a file or folder exists in the archive * @param string path diff --git a/apps/files_archive/lib/zip.php b/apps/files_archive/lib/zip.php index eab101b3a5..16f2273f44 100644 --- a/apps/files_archive/lib/zip.php +++ b/apps/files_archive/lib/zip.php @@ -128,6 +128,15 @@ class OC_Archive_ZIP extends OC_Archive{ $fp = $this->zip->getStream($path); file_put_contents($dest,$fp); } + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + function extract($dest){ + return $this->zip->extractTo($dest); + } /** * check if a file or folder exists in the archive * @param string path diff --git a/apps/files_archive/tests/archive.php b/apps/files_archive/tests/archive.php index 2e26b5e03b..2619d91b3b 100644 --- a/apps/files_archive/tests/archive.php +++ b/apps/files_archive/tests/archive.php @@ -27,10 +27,10 @@ abstract class Test_Archive extends UnitTestCase { $this->instance=$this->getExisting(); $allFiles=$this->instance->getFiles(); $expected=array('lorem.txt','logo-wide.png','dir/','dir/lorem.txt'); - $this->assertEqual(4,count($allFiles)); + $this->assertEqual(4,count($allFiles),'only found '.count($allFiles).' out of 4 expected files'); foreach($expected as $file){ $this->assertNotIdentical(false,array_search($file,$allFiles),'cant find '.$file.' in archive'); - $this->assertTrue($this->instance->fileExists($file)); + $this->assertTrue($this->instance->fileExists($file),'file '.$file.' does not exist in archive'); } $this->assertFalse($this->instance->fileExists('non/existing/file')); @@ -94,4 +94,15 @@ abstract class Test_Archive extends UnitTestCase { $this->assertTrue($this->instance->fileExists('lorem.txt')); $this->assertEqual(file_get_contents($dir.'/lorem.txt'),$this->instance->getFile('lorem.txt')); } + public function testExtract(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + $this->instance=$this->getExisting(); + $tmpDir=OC_Helper::tmpFolder(); + $this->instance->extract($tmpDir); + $this->assertEqual(true,file_exists($tmpDir.'lorem.txt')); + $this->assertEqual(true,file_exists($tmpDir.'dir/lorem.txt')); + $this->assertEqual(true,file_exists($tmpDir.'logo-wide.png')); + $this->assertEqual(file_get_contents($dir.'/lorem.txt'),file_get_contents($tmpDir.'lorem.txt')); + OC_Helper::rmdirr($tmpDir); + } } From c26e00346210492ed9dd60c7a910b5187310d47b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 28 Mar 2012 22:32:51 +0200 Subject: [PATCH 120/302] add tar backend for OC_Archive also handles bz2/gz compression --- 3rdparty/Archive/Tar.php | 1954 ++++++++++++++++++++++++++++ apps/files_archive/appinfo/app.php | 3 +- apps/files_archive/lib/tar.php | 271 ++++ apps/files_archive/tests/tar.php | 20 + 4 files changed, 2247 insertions(+), 1 deletion(-) create mode 100644 3rdparty/Archive/Tar.php create mode 100644 apps/files_archive/lib/tar.php create mode 100644 apps/files_archive/tests/tar.php diff --git a/3rdparty/Archive/Tar.php b/3rdparty/Archive/Tar.php new file mode 100644 index 0000000000..d69bab6ad7 --- /dev/null +++ b/3rdparty/Archive/Tar.php @@ -0,0 +1,1954 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet + * @copyright 1997-2010 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Tar.php 323476 2012-02-24 15:27:26Z mrook $ + * @link http://pear.php.net/package/Archive_Tar + */ + +require_once 'PEAR.php'; + +define('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +/** +* Creates a (compressed) Tar archive +* +* @package Archive_Tar +* @author Vincent Blavet +* @license http://www.opensource.org/licenses/bsd-license.php New BSD License +* @version $Revision: 323476 $ +*/ +class Archive_Tar extends PEAR +{ + /** + * @var string Name of the Tar + */ + var $_tarname=''; + + /** + * @var boolean if true, the Tar file will be gzipped + */ + var $_compress=false; + + /** + * @var string Type of compression : 'none', 'gz' or 'bz2' + */ + var $_compress_type='none'; + + /** + * @var string Explode separator + */ + var $_separator=' '; + + /** + * @var file descriptor + */ + var $_file=0; + + /** + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + var $_temp_tarname=''; + + /** + * @var string regular expression for ignoring files or directories + */ + var $_ignore_regexp=''; + + /** + * @var object PEAR_Error object + */ + var $error_object=null; + + // {{{ constructor + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz' or 'bz2'. This + * parameter indicates if gzip or bz2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * + * @access public + */ + function Archive_Tar($p_tarname, $p_compress = null) + { + $this->PEAR(); + $this->_compress = false; + $this->_compress_type = 'none'; + if (($p_compress === null) || ($p_compress == '')) { + if (@file_exists($p_tarname)) { + if ($fp = @fopen($p_tarname, "rb")) { + // look for gzip magic cookie + $data = fread($fp, 2); + fclose($fp); + if ($data == "\37\213") { + $this->_compress = true; + $this->_compress_type = 'gz'; + // No sure it's enought for a magic code .... + } elseif ($data == "BZ") { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + // probably a remote file or some file accessible + // through a stream interface + if (substr($p_tarname, -2) == 'gz') { + $this->_compress = true; + $this->_compress_type = 'gz'; + } elseif ((substr($p_tarname, -3) == 'bz2') || + (substr($p_tarname, -2) == 'bz')) { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + if (($p_compress === true) || ($p_compress == 'gz')) { + $this->_compress = true; + $this->_compress_type = 'gz'; + } else if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + $this->_error("Unsupported compression type '$p_compress'\n". + "Supported types are 'gz' and 'bz2'.\n"); + return false; + } + } + $this->_tarname = $p_tarname; + if ($this->_compress) { // assert zlib or bz2 extension support + if ($this->_compress_type == 'gz') + $extname = 'zlib'; + else if ($this->_compress_type == 'bz2') + $extname = 'bz2'; + + if (!extension_loaded($extname)) { + PEAR::loadExtension($extname); + } + if (!extension_loaded($extname)) { + $this->_error("The extension '$extname' couldn't be found.\n". + "Please make sure your version of PHP was built ". + "with '$extname' support.\n"); + return false; + } + } + } + // }}} + + // {{{ destructor + function _Archive_Tar() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') + @unlink($this->_temp_tarname); + $this->_PEAR(); + } + // }}} + + // {{{ create() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function create($p_filelist) + { + return $this->createModify($p_filelist, '', ''); + } + // }}} + + // {{{ add() + /** + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function add($p_filelist) + { + return $this->addModify($p_filelist, '', ''); + } + // }}} + + // {{{ extract() + function extract($p_path='', $p_preserve=false) + { + return $this->extractModify($p_path, '', $p_preserve); + } + // }}} + + // {{{ listContent() + function listContent() + { + $v_list_detail = array(); + + if ($this->_openRead()) { + if (!$this->_extractList('', $v_list_detail, "list", '', '')) { + unset($v_list_detail); + $v_list_detail = 0; + } + $this->_close(); + } + + return $v_list_detail; + } + // }}} + + // {{{ createModify() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * + * @return boolean true on success, false on error. + * @access public + * @see addModify() + */ + function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_openWrite()) + return false; + + if ($p_filelist != '') { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_cleanFile(); + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir); + } + + if ($v_result) { + $this->_writeFooter(); + $this->_close(); + } else + $this->_cleanFile(); + + return $v_result; + } + // }}} + + // {{{ addModify() + /** + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * + * @return true on success, false on error. + * @access public + */ + function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_isArchive()) + $v_result = $this->createModify($p_filelist, $p_add_dir, + $p_remove_dir); + else { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir); + } + + return $v_result; + } + // }}} + + // {{{ addString() + /** + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * + * @return true on success, false on error. + * @access public + */ + function addString($p_filename, $p_string) + { + $v_result = true; + + if (!$this->_isArchive()) { + if (!$this->_openWrite()) { + return false; + } + $this->_close(); + } + + if (!$this->_openAppend()) + return false; + + // Need to check the get back to the temporary file ? .... + $v_result = $this->_addString($p_filename, $p_string); + + $this->_writeFooter(); + + $this->_close(); + + return $v_result; + } + // }}} + + // {{{ extractModify() + /** + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return boolean true on success, false on error. + * @access public + * @see extractList() + */ + function extractModify($p_path, $p_remove_path, $p_preserve=false) + { + $v_result = true; + $v_list_detail = array(); + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, + "complete", 0, $p_remove_path, $p_preserve); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or NULL. + * @access public + */ + function extractInString($p_filename) + { + if ($this->_openRead()) { + $v_result = $this->_extractInString($p_filename); + $this->_close(); + } else { + $v_result = null; + } + + return $v_result; + } + // }}} + + // {{{ extractList() + /** + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return true on success, false on error. + * @access public + * @see extractModify() + */ + function extractList($p_filelist, $p_path='', $p_remove_path='', $p_preserve=false) + { + $v_result = true; + $v_list_detail = array(); + + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid string list'); + return false; + } + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, "partial", + $v_list, $p_remove_path, $p_preserve); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ setAttribute() + /** + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * + * @param mixed $argv variable list of attributes and values + * + * @return true on success, false on error. + * @access public + */ + function setAttribute() + { + $v_result = true; + + // ----- Get the number of variable list of arguments + if (($v_size = func_num_args()) == 0) { + return true; + } + + // ----- Get the arguments + $v_att_list = &func_get_args(); + + // ----- Read the attributes + $i=0; + while ($i<$v_size) { + + // ----- Look for next option + switch ($v_att_list[$i]) { + // ----- Look for options that request a string value + case ARCHIVE_TAR_ATT_SEPARATOR : + // ----- Check the number of parameters + if (($i+1) >= $v_size) { + $this->_error('Invalid number of parameters for ' + .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + return false; + } + + // ----- Get the value + $this->_separator = $v_att_list[$i+1]; + $i++; + break; + + default : + $this->_error('Unknow attribute code '.$v_att_list[$i].''); + return false; + } + + // ----- Next attribute + $i++; + } + + return $v_result; + } + // }}} + + // {{{ setIgnoreRegexp() + /** + * This method sets the regular expression for ignoring files and directories + * at import, for example: + * $arch->setIgnoreRegexp("#CVS|\.svn#"); + * + * @param string $regexp regular expression defining which files or directories to ignore + * + * @access public + */ + function setIgnoreRegexp($regexp) + { + $this->_ignore_regexp = $regexp; + } + // }}} + + // {{{ setIgnoreList() + /** + * This method sets the regular expression for ignoring all files and directories + * matching the filenames in the array list at import, for example: + * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); + * + * @param array $list a list of file or directory names to ignore + * + * @access public + */ + function setIgnoreList($list) + { + $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); + $regexp = '#/'.join('$|/', $list).'#'; + $this->setIgnoreRegexp($regexp); + } + // }}} + + // {{{ _error() + function _error($p_message) + { + $this->error_object = &$this->raiseError($p_message); + } + // }}} + + // {{{ _warning() + function _warning($p_message) + { + $this->error_object = &$this->raiseError($p_message); + } + // }}} + + // {{{ _isArchive() + function _isArchive($p_filename=null) + { + if ($p_filename == null) { + $p_filename = $this->_tarname; + } + clearstatcache(); + return @is_file($p_filename) && !@is_link($p_filename); + } + // }}} + + // {{{ _openWrite() + function _openWrite() + { + if ($this->_compress_type == 'gz' && function_exists('gzopen')) + $this->_file = @gzopen($this->_tarname, "wb9"); + else if ($this->_compress_type == 'bz2' && function_exists('bzopen')) + $this->_file = @bzopen($this->_tarname, "w"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "wb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openRead() + function _openRead() + { + if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { + + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar').'.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error('Unable to open in read mode \'' + .$this->_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error('Unable to open in write mode \'' + .$this->_temp_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) + @fwrite($v_file_to, $v_data); + @fclose($v_file_from); + @fclose($v_file_to); + } + + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + + } else + // ----- File to open if the normal Tar file + $v_filename = $this->_tarname; + + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($v_filename, "rb"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($v_filename, "r"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($v_filename, "rb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read mode \''.$v_filename.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openReadWrite() + function _openReadWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "r+b"); + else if ($this->_compress_type == 'bz2') { + $this->_error('Unable to open bz2 in read/write mode \'' + .$this->_tarname.'\' (limitation of bz2 extension)'); + return false; + } else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "r+b"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read/write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _close() + function _close() + { + //if (isset($this->_file)) { + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + @gzclose($this->_file); + else if ($this->_compress_type == 'bz2') + @bzclose($this->_file); + else if ($this->_compress_type == 'none') + @fclose($this->_file); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + $this->_file = 0; + } + + // ----- Look if a local copy need to be erase + // Note that it might be interesting to keep the url for a time : ToDo + if ($this->_temp_tarname != '') { + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } + + return true; + } + // }}} + + // {{{ _cleanFile() + function _cleanFile() + { + $this->_close(); + + // ----- Look for a local copy + if ($this->_temp_tarname != '') { + // ----- Remove the local copy but not the remote tarname + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } else { + // ----- Remove the local tarname file + @unlink($this->_tarname); + } + $this->_tarname = ''; + + return true; + } + // }}} + + // {{{ _writeBlock() + function _writeBlock($p_binary_data, $p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data, $p_len); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + } + return true; + } + // }}} + + // {{{ _readBlock() + function _readBlock() + { + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + $v_block = @gzread($this->_file, 512); + else if ($this->_compress_type == 'bz2') + $v_block = @bzread($this->_file, 512); + else if ($this->_compress_type == 'none') + $v_block = @fread($this->_file, 512); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } + return $v_block; + } + // }}} + + // {{{ _jumpBlock() + function _jumpBlock($p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) + $p_len = 1; + + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file)+($p_len*512)); + } + else if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i=0; $i<$p_len; $i++) + $this->_readBlock(); + } else if ($this->_compress_type == 'none') + @fseek($this->_file, $p_len*512, SEEK_CUR); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + return true; + } + // }}} + + // {{{ _writeFooter() + function _writeFooter() + { + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; + } + // }}} + + // {{{ _addList() + function _addList($p_list, $p_add_dir, $p_remove_dir) + { + $v_result=true; + $v_header = array(); + + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if (sizeof($p_list) == 0) + return true; + + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) + continue; + + if ($v_filename == '') + continue; + + // ----- ignore files and directories matching the ignore regular expression + if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) { + $this->_warning("File '$v_filename' ignored"); + continue; + } + + if (!file_exists($v_filename) && !is_link($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) + return false; + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") + $p_temp_list[0] = $v_filename.'/'.$p_hitem; + else + $p_temp_list[0] = $p_hitem; + + $v_result = $this->_addList($p_temp_list, + $p_add_dir, + $p_remove_dir); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } + + return $v_result; + } + // }}} + + // {{{ _addFile() + function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir,$v_stored_filename=null) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + if(is_null($v_stored_filename)){ + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); + $v_stored_filename = $p_filename; + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') + $p_remove_dir .= '/'; + + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); + } + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') + $v_stored_filename = $p_add_dir.$v_stored_filename; + else + $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; + } + + $v_stored_filename = $this->_pathReduction($v_stored_filename); + } + + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning("Unable to open file '".$p_filename + ."' in binary read mode"); + return true; + } + + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + fclose($v_file); + + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + } + + return true; + } + // }}} + + // {{{ _addString() + function _addString($p_filename, $p_string) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + + if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), + time(), 384, "", 0, 0)) + return false; + + $i=0; + while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _writeHeader() + function _writeHeader($p_filename, $p_stored_filename) + { + if ($p_stored_filename == '') + $p_stored_filename = $p_filename; + $v_reduce_filename = $this->_pathReduction($p_stored_filename); + + if (strlen($v_reduce_filename) > 99) { + if (!$this->_writeLongHeader($v_reduce_filename)) + return false; + } + + $v_info = lstat($p_filename); + $v_uid = sprintf("%07s", DecOct($v_info[4])); + $v_gid = sprintf("%07s", DecOct($v_info[5])); + $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); + + $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); + + $v_linkname = ''; + + if (@is_link($p_filename)) { + $v_typeflag = '2'; + $v_linkname = readlink($p_filename); + $v_size = sprintf("%011s", DecOct(0)); + } elseif (@is_dir($p_filename)) { + $v_typeflag = "5"; + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_typeflag = '0'; + clearstatcache(); + $v_size = sprintf("%011s", DecOct($v_info['size'])); + } + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($v_info[4]); + $groupinfo = posix_getgrgid($v_info[5]); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + $v_reduce_filename, $v_perms, $v_uid, + $v_gid, $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeHeaderBlock() + function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, + $p_type='', $p_uid=0, $p_gid=0) + { + $p_filename = $this->_pathReduction($p_filename); + + if (strlen($p_filename) > 99) { + if (!$this->_writeLongHeader($p_filename)) + return false; + } + + if ($p_type == "5") { + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_size = sprintf("%011s", DecOct($p_size)); + } + + $v_uid = sprintf("%07s", DecOct($p_uid)); + $v_gid = sprintf("%07s", DecOct($p_gid)); + $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); + + $v_mtime = sprintf("%11s", DecOct($p_mtime)); + + $v_linkname = ''; + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($p_uid); + $groupinfo = posix_getgrgid($p_gid); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12A12", + $p_filename, $v_perms, $v_uid, $v_gid, + $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $p_type, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeLongHeader() + function _writeLongHeader($p_filename) + { + $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); + + $v_typeflag = 'L'; + + $v_linkname = ''; + + $v_magic = ''; + + $v_version = ''; + + $v_uname = ''; + + $v_gname = ''; + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + '././@LongLink', 0, 0, 0, $v_size, 0); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + // ----- Write the filename as content of the block + $i=0; + while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _readHeader() + function _readHeader($v_binary_data, &$v_header) + { + if (strlen($v_binary_data)==0) { + $v_header['filename'] = ''; + return true; + } + + if (strlen($v_binary_data) != 512) { + $v_header['filename'] = ''; + $this->_error('Invalid block size : '.strlen($v_binary_data)); + return false; + } + + if (!is_array($v_header)) { + $v_header = array(); + } + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156; $i<512; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + + $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" . + "a8checksum/a1typeflag/a100link/a6magic/a2version/" . + "a32uname/a32gname/a8devmajor/a8devminor/a131prefix", + $v_binary_data); + + if (strlen($v_data["prefix"]) > 0) { + $v_data["filename"] = "$v_data[prefix]/$v_data[filename]"; + } + + // ----- Extract the checksum + $v_header['checksum'] = OctDec(trim($v_data['checksum'])); + if ($v_header['checksum'] != $v_checksum) { + $v_header['filename'] = ''; + + // ----- Look for last block (empty block) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + return true; + + $this->_error('Invalid checksum for file "'.$v_data['filename'] + .'" : '.$v_checksum.' calculated, ' + .$v_header['checksum'].' expected'); + return false; + } + + // ----- Extract the properties + $v_header['filename'] = $v_data['filename']; + if ($this->_maliciousFilename($v_header['filename'])) { + $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree'); + return false; + } + $v_header['mode'] = OctDec(trim($v_data['mode'])); + $v_header['uid'] = OctDec(trim($v_data['uid'])); + $v_header['gid'] = OctDec(trim($v_data['gid'])); + $v_header['size'] = OctDec(trim($v_data['size'])); + $v_header['mtime'] = OctDec(trim($v_data['mtime'])); + if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { + $v_header['size'] = 0; + } + $v_header['link'] = trim($v_data['link']); + /* ----- All these fields are removed form the header because + they do not carry interesting info + $v_header[magic] = trim($v_data[magic]); + $v_header[version] = trim($v_data[version]); + $v_header[uname] = trim($v_data[uname]); + $v_header[gname] = trim($v_data[gname]); + $v_header[devmajor] = trim($v_data[devmajor]); + $v_header[devminor] = trim($v_data[devminor]); + */ + + return true; + } + // }}} + + // {{{ _maliciousFilename() + /** + * Detect and report a malicious file name + * + * @param string $file + * + * @return bool + * @access private + */ + function _maliciousFilename($file) + { + if (strpos($file, '/../') !== false) { + return true; + } + if (strpos($file, '../') === 0) { + return true; + } + return false; + } + // }}} + + // {{{ _readLongHeader() + function _readLongHeader(&$v_header) + { + $v_filename = ''; + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= trim($v_content); + } + + // ----- Read the next header + $v_binary_data = $this->_readBlock(); + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + $v_filename = trim($v_filename); + $v_header['filename'] = $v_filename; + if ($this->_maliciousFilename($v_filename)) { + $this->_error('Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree'); + return false; + } + + return true; + } + // }}} + + // {{{ _extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or null on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or null. + * @access private + */ + function _extractInString($p_filename) + { + $v_result_str = ""; + + While (strlen($v_binary_data = $this->_readBlock()) != 0) + { + if (!$this->_readHeader($v_binary_data, $v_header)) + return null; + + if ($v_header['filename'] == '') + continue; + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return null; + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error('Unable to extract in string a directory ' + .'entry {'.$v_header['filename'].'}'); + return null; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr($v_content, 0, + ($v_header['size'] % 512)); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } + + return null; + } + // }}} + + // {{{ _extractList() + function _extractList($p_path, &$p_list_detail, $p_mode, + $p_file_list, $p_remove_path, $p_preserve=false) + { + $v_result=true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { + $p_path = "./".$p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) + $p_remove_path .= '/'; + $p_remove_path_size = strlen($p_remove_path); + + switch ($p_mode) { + case "complete" : + $v_extract_all = true; + $v_listing = false; + break; + case "partial" : + $v_extract_all = false; + $v_listing = false; + break; + case "list" : + $v_extract_all = false; + $v_listing = true; + break; + default : + $this->_error('Invalid extract mode ('.$p_mode.')'); + return false; + } + + clearstatcache(); + + while (strlen($v_binary_data = $this->_readBlock()) != 0) + { + $v_extract_file = FALSE; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return false; + } + + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i=0; $i strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i])) { + $v_extract_file = true; + break; + } + } + + // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = true; + break; + } + } + } else { + $v_extract_file = true; + } + + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) + { + if (($p_remove_path != '') + && (substr($v_header['filename'], 0, $p_remove_path_size) + == $p_remove_path)) + $v_header['filename'] = substr($v_header['filename'], + $p_remove_path_size); + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') + $p_path = substr($p_path, 0, strlen($p_path)-1); + + if (substr($v_header['filename'], 0, 1) == '/') + $v_header['filename'] = $p_path.$v_header['filename']; + else + $v_header['filename'] = $p_path.'/'.$v_header['filename']; + } + if (file_exists($v_header['filename'])) { + if ( (@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '')) { + $this->_error('File '.$v_header['filename'] + .' already exists as a directory'); + return false; + } + if ( ($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5")) { + $this->_error('Directory '.$v_header['filename'] + .' already exists as a file'); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error('File '.$v_header['filename'] + .' already exists and is write protected'); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } + + // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck(($v_header['typeflag'] == "5" + ?$v_header['filename'] + :dirname($v_header['filename'])))) != 1) { + $this->_error('Unable to create path for '.$v_header['filename']); + return false; + } + + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error('Unable to create directory {' + .$v_header['filename'].'}'); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error('Unable to extract symbolic link {' + .$v_header['filename'].'}'); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error('Error while opening {'.$v_header['filename'] + .'} in write binary mode'); + return false; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + if ($p_preserve) { + @chown($v_header['filename'], $v_header['uid']); + @chgrp($v_header['filename'], $v_header['gid']); + } + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (!is_file($v_header['filename'])) { + $this->_error('Extracted file '.$v_header['filename'] + .'does not exist. Archive may be corrupted.'); + return false; + } + + $filesize = filesize($v_header['filename']); + if ($filesize != $v_header['size']) { + $this->_error('Extracted file '.$v_header['filename'] + .' does not have the correct file size \'' + .$filesize + .'\' ('.$v_header['size'] + .' expected). Archive may be corrupted.'); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ + + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename']) + $v_file_dir = ''; + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) + $v_file_dir = '/'; + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } + } + + return true; + } + // }}} + + // {{{ _openAppend() + function _openAppend() + { + if (filesize($this->_tarname) == 0) + return $this->_openWrite(); + + if ($this->_compress) { + $this->_close(); + + if (!@rename($this->_tarname, $this->_tarname.".tmp")) { + $this->_error('Error while renaming \''.$this->_tarname + .'\' to temporary file \''.$this->_tarname + .'.tmp\''); + return false; + } + + if ($this->_compress_type == 'gz') + $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); + elseif ($this->_compress_type == 'bz2') + $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); + + if ($v_temp_tar == 0) { + $this->_error('Unable to open file \''.$this->_tarname + .'.tmp\' in binary read mode'); + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if (!$this->_openWrite()) { + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if ($this->_compress_type == 'gz') { + while (!@gzeof($v_temp_tar)) { + $v_buffer = @gzread($v_temp_tar, 512); + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + // do not copy end blocks, we will re-make them + // after appending + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @gzclose($v_temp_tar); + } + elseif ($this->_compress_type == 'bz2') { + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @bzclose($v_temp_tar); + } + + if (!@unlink($this->_tarname.".tmp")) { + $this->_error('Error while deleting temporary file \'' + .$this->_tarname.'.tmp\''); + } + + } else { + // ----- For not compressed tar, just add files before the last + // one or two 512 bytes block + if (!$this->_openReadWrite()) + return false; + + clearstatcache(); + $v_size = filesize($this->_tarname); + + // We might have zero, one or two end blocks. + // The standard is two, but we should try to handle + // other cases. + fseek($this->_file, $v_size - 1024); + if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 1024); + } + elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 512); + } + } + + return true; + } + // }}} + + // {{{ _append() + function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + { + if (!$this->_openAppend()) + return false; + + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) + $this->_writeFooter(); + + $this->_close(); + + return true; + } + // }}} + + // {{{ _dirCheck() + + /** + * Check if a directory exists and create it (including parent + * dirs) if not. + * + * @param string $p_dir directory to check + * + * @return bool true if the directory exists or was created + */ + function _dirCheck($p_dir) + { + clearstatcache(); + if ((@is_dir($p_dir)) || ($p_dir == '')) + return true; + + $p_parent_dir = dirname($p_dir); + + if (($p_parent_dir != $p_dir) && + ($p_parent_dir != '') && + (!$this->_dirCheck($p_parent_dir))) + return false; + + if (!@mkdir($p_dir, 0777)) { + $this->_error("Unable to create directory '$p_dir'"); + return false; + } + + return true; + } + + // }}} + + // {{{ _pathReduction() + + /** + * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", + * rand emove double slashes. + * + * @param string $p_dir path to reduce + * + * @return string reduced path + * + * @access private + * + */ + function _pathReduction($p_dir) + { + $v_result = ''; + + // ----- Look for not empty path + if ($p_dir != '') { + // ----- Explode path by directory names + $v_list = explode('/', $p_dir); + + // ----- Study directories from last to first + for ($i=sizeof($v_list)-1; $i>=0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } + else if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } + else if ( ($v_list[$i] == '') + && ($i!=(sizeof($v_list)-1)) + && ($i!=0)) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' + .$v_result:''); + } + } + } + + if (defined('OS_WINDOWS') && OS_WINDOWS) { + $v_result = strtr($v_result, '\\', '/'); + } + + return $v_result; + } + + // }}} + + // {{{ _translateWinPath() + function _translateWinPath($p_path, $p_remove_disk_letter=true) + { + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if ( ($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position+1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; + } + // }}} + +} +?> diff --git a/apps/files_archive/appinfo/app.php b/apps/files_archive/appinfo/app.php index 693c28d98a..67809ec980 100644 --- a/apps/files_archive/appinfo/app.php +++ b/apps/files_archive/appinfo/app.php @@ -7,7 +7,8 @@ */ OC::$CLASSPATH['OC_Archive'] = 'apps/files_archive/lib/archive.php'; -foreach(array('ZIP') as $type){ +OC::$CLASSPATH['Archive_Tar'] = '3rdparty/Archive/Tar.php'; +foreach(array('ZIP','TAR') as $type){ OC::$CLASSPATH['OC_Archive_'.$type] = 'apps/files_archive/lib/'.strtolower($type).'.php'; } diff --git a/apps/files_archive/lib/tar.php b/apps/files_archive/lib/tar.php new file mode 100644 index 0000000000..40b314a2ab --- /dev/null +++ b/apps/files_archive/lib/tar.php @@ -0,0 +1,271 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Archive_TAR extends OC_Archive{ + const PLAIN=0; + const GZIP=1; + const BZIP=2; + + /** + * @var Archive_Tar tar + */ + private $tar=null; + private $headers=array(); + private $path; + + function __construct($source){ + $types=array(null,'gz','bz'); + $this->path=$source; + $this->tar=new Archive_Tar($source,$types[self::getTarType($source)]); + } + + /** + * try to detect the type of tar compression + * @param string file + * @return str + */ + static public function getTarType($file){ + if(strpos($file,'.')){ + $extention=substr($file,strrpos($file,'.')); + switch($extention){ + case 'gz': + case 'tgz': + return self::GZIP; + case 'bz': + case 'bz2': + return self::BZIP; + default: + return self::PLAIN; + } + }else{ + return self::PLAIN; + } + } + + /** + * add an empty folder to the archive + * @param string path + * @return bool + */ + function addFolder($path){ + if(substr($path,-1)!=='/'){ + $path.='/'; + } + return $this->tar->add(array($path)); + } + /** + * add a file to the archive + * @param string path + * @param string source either a local file or string data + * @return bool + */ + function addFile($path,$source=''){ + if($this->fileExists($path)){ + $this->remove($path); + } + if(file_exists($source)){ + $header=array(); + $dummy=''; + $this->tar->_openAppend(); + $result=$this->tar->_addfile($source,$header,$dummy,$dummy,$path); + }else{ + $result=$this->tar->addString($path,$source); + } +// $this->reopen(); +// var_dump($this->getFiles()); +// exit(); + + return $result; + } + + /** + * rename a file or folder in the archive + * @param string source + * @param string dest + * @return bool + */ + function rename($source,$dest){ + //no proper way to delete, rename entire archive, rename file and remake archive + $tmp=OC_Helper::tmpFolder(); + $this->tar->extract($tmp); + rename($tmp.$source,$tmp.$dest); + $this->tar=null; + unlink($this->path); + $types=array(null,'gz','bz'); + $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]); + $this->tar->createModify(array($tmp),'',$tmp); + } + + private function getHeader($file){ + if(isset($this->headers[$file])){ + return $this->headers[$file]; + } + $headers=$this->tar->listContent(); + foreach($headers as $header){ + if($file==$header['filename']){ + return $header; + } + } + return null; + } + + /** + * get the uncompressed size of a file in the archive + * @param string path + * @return int + */ + function filesize($path){ + $stat=$this->getHeader($path); + return $stat['size']; + } + /** + * get the last modified time of a file in the archive + * @param string path + * @return int + */ + function mtime($path){ + $stat=$this->getHeader($path); + return $stat['mtime']; + } + + /** + * get the files in a folder + * @param path + * @return array + */ + function getFolder($path){ + $files=$this->getFiles(); + $folderContent=array(); + $pathLength=strlen($path); + foreach($files as $file){ + if(substr($file,0,$pathLength)==$path and $file!=$path){ + if(strrpos(substr($file,0,-1),'/')<=$pathLength){ + $folderContent[]=substr($file,$pathLength); + } + } + } + return $folderContent; + } + /** + *get all files in the archive + * @return array + */ + function getFiles(){ + $headers=$this->tar->listContent(); + $files=array(); + foreach($headers as $header){ + $files[]=$header['filename']; + } + return $files; + } + /** + * get the content of a file + * @param string path + * @return string + */ + function getFile($path){ + return $this->tar->extractInString($path); + } + /** + * extract a single file from the archive + * @param string path + * @param string dest + * @return bool + */ + function extractFile($path,$dest){ + $tmp=OC_Helper::tmpFolder(); + $success=$this->tar->extractList(array($path),$tmp); + if($success){ + rename($tmp.$path,$dest); + } + OC_Helper::rmdirr($tmp); + return $success; + } + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + function extract($dest){ + return $this->tar->extract($dest); + } + /** + * check if a file or folder exists in the archive + * @param string path + * @return bool + */ + function fileExists($path){ + return $this->getHeader($path)!==null; + } + + /** + * remove a file or folder from the archive + * @param string path + * @return bool + */ + function remove($path){ + //no proper way to delete, extract entire archive, delete file and remake archive + $tmp=OC_Helper::tmpFolder(); + $this->tar->extract($tmp); + OC_Helper::rmdirr($tmp.$path); + $this->tar=null; + unlink($this->path); + $this->reopen(); + $this->tar->createModify(array($tmp),'',$tmp); + } + /** + * get a file handler + * @param string path + * @param string mode + * @return resource + */ + function getStream($path,$mode){ + if(strrpos($path,'.')!==false){ + $ext=substr($path,strrpos($path,'.')); + }else{ + $ext=''; + } + $tmpFile=OC_Helper::tmpFile($ext); + if($this->fileExists($path)){ + $this->extractFile($path,$tmpFile); + }elseif($mode=='r' or $mode=='rb'){ + return false; + } + if($mode=='r' or $mode=='rb'){ + return fopen($tmpFile,$mode); + }else{ + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + private static $tempFiles=array(); + /** + * write back temporary files + */ + function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->addFile(self::$tempFiles[$tmpFile],$tmpFile); + unlink($tmpFile); + } + } + + /** + * reopen the archive to ensure everything is written + */ + private function reopen(){ + if($this->tar){ + $this->tar->_close(); + $this->tar=null; + } + $types=array(null,'gz','bz'); + $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]); + } +} diff --git a/apps/files_archive/tests/tar.php b/apps/files_archive/tests/tar.php new file mode 100644 index 0000000000..193a65b550 --- /dev/null +++ b/apps/files_archive/tests/tar.php @@ -0,0 +1,20 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('archive.php'); + +class Test_Archive_TAR extends Test_Archive{ + protected function getExisting(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + return new OC_Archive_TAR($dir.'/data.tar.gz'); + } + + protected function getNew(){ + return new OC_Archive_TAR(OC_Helper::tmpFile('.tar.gz')); + } +} From 1b6fe4f65efb1d1f696f35b70454ad30cae6310b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 28 Mar 2012 23:46:44 +0200 Subject: [PATCH 121/302] stricter tests for archive backends and make sure we make the tests --- 3rdparty/Archive/Tar.php | 4 ++-- apps/files_archive/lib/archive.php | 9 +++++++++ apps/files_archive/lib/storage.php | 4 ++++ apps/files_archive/lib/tar.php | 30 +++++++++++++++++----------- apps/files_archive/lib/zip.php | 25 +++++++++++++++-------- apps/files_archive/tests/archive.php | 25 +++++++++++++++++++++++ 6 files changed, 75 insertions(+), 22 deletions(-) diff --git a/3rdparty/Archive/Tar.php b/3rdparty/Archive/Tar.php index d69bab6ad7..d8eae851bd 100644 --- a/3rdparty/Archive/Tar.php +++ b/3rdparty/Archive/Tar.php @@ -649,14 +649,14 @@ class Archive_Tar extends PEAR // {{{ _error() function _error($p_message) { - $this->error_object = &$this->raiseError($p_message); + $this->error_object = $this->raiseError($p_message); } // }}} // {{{ _warning() function _warning($p_message) { - $this->error_object = &$this->raiseError($p_message); + $this->error_object = $this->raiseError($p_message); } // }}} diff --git a/apps/files_archive/lib/archive.php b/apps/files_archive/lib/archive.php index 3be3388a3b..113f92e960 100644 --- a/apps/files_archive/lib/archive.php +++ b/apps/files_archive/lib/archive.php @@ -17,6 +17,15 @@ abstract class OC_Archive{ switch($ext){ case '.zip': return new OC_Archive_ZIP($path); + case '.gz': + case '.bz': + case '.bz2': + if(strpos($path,'.tar.')){ + return new OC_Archive_TAR($path); + } + break; + case '.tgz': + return new OC_Archive_TAR($path); } } diff --git a/apps/files_archive/lib/storage.php b/apps/files_archive/lib/storage.php index 72a96ca5a5..598b85f0db 100644 --- a/apps/files_archive/lib/storage.php +++ b/apps/files_archive/lib/storage.php @@ -139,4 +139,8 @@ class OC_Filestorage_Archive extends OC_Filestorage_Common{ } self::$enableAutomount=true; } + + public function rename($path1,$path2){ + return $this->archive->rename($path1,$path2); + } } diff --git a/apps/files_archive/lib/tar.php b/apps/files_archive/lib/tar.php index 40b314a2ab..a5d5400478 100644 --- a/apps/files_archive/lib/tar.php +++ b/apps/files_archive/lib/tar.php @@ -15,7 +15,6 @@ class OC_Archive_TAR extends OC_Archive{ * @var Archive_Tar tar */ private $tar=null; - private $headers=array(); private $path; function __construct($source){ @@ -53,10 +52,17 @@ class OC_Archive_TAR extends OC_Archive{ * @return bool */ function addFolder($path){ - if(substr($path,-1)!=='/'){ + $tmpBase=get_temp_dir().'/'; + if(substr($path,-1,1)!='/'){ $path.='/'; } - return $this->tar->add(array($path)); + if($this->fileExists($path)){ + return false; + } + mkdir($tmpBase.$path); + $result=$this->tar->addModify(array($tmpBase.$path),'',$tmpBase); + rmdir($tmpBase.$path); + return $result; } /** * add a file to the archive @@ -76,10 +82,6 @@ class OC_Archive_TAR extends OC_Archive{ }else{ $result=$this->tar->addString($path,$source); } -// $this->reopen(); -// var_dump($this->getFiles()); -// exit(); - return $result; } @@ -98,16 +100,13 @@ class OC_Archive_TAR extends OC_Archive{ unlink($this->path); $types=array(null,'gz','bz'); $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]); - $this->tar->createModify(array($tmp),'',$tmp); + $this->tar->createModify(array($tmp),'',$tmp.'/'); } private function getHeader($file){ - if(isset($this->headers[$file])){ - return $this->headers[$file]; - } $headers=$this->tar->listContent(); foreach($headers as $header){ - if($file==$header['filename']){ + if($file==$header['filename'] or $file.'/'==$header['filename']){ return $header; } } @@ -179,6 +178,9 @@ class OC_Archive_TAR extends OC_Archive{ */ function extractFile($path,$dest){ $tmp=OC_Helper::tmpFolder(); + if(!$this->fileExists($path)){ + return false; + } $success=$this->tar->extractList(array($path),$tmp); if($success){ rename($tmp.$path,$dest); @@ -210,6 +212,9 @@ class OC_Archive_TAR extends OC_Archive{ * @return bool */ function remove($path){ + if(!$this->fileExists($path)){ + return false; + } //no proper way to delete, extract entire archive, delete file and remake archive $tmp=OC_Helper::tmpFolder(); $this->tar->extract($tmp); @@ -218,6 +223,7 @@ class OC_Archive_TAR extends OC_Archive{ unlink($this->path); $this->reopen(); $this->tar->createModify(array($tmp),'',$tmp); + return true; } /** * get a file handler diff --git a/apps/files_archive/lib/zip.php b/apps/files_archive/lib/zip.php index 16f2273f44..5a5bc76687 100644 --- a/apps/files_archive/lib/zip.php +++ b/apps/files_archive/lib/zip.php @@ -11,7 +11,6 @@ class OC_Archive_ZIP extends OC_Archive{ * @var ZipArchive zip */ private $zip=null; - private $contents=array(); private $success=false; private $path; @@ -56,7 +55,9 @@ class OC_Archive_ZIP extends OC_Archive{ * @return bool */ function rename($source,$dest){ - return $this->zip->renameName($source,$dest); + $source=$this->stripPath($source); + $dest=$this->stripPath($dest); + $this->zip->renameName($source,$dest); } /** * get the uncompressed size of a file in the archive @@ -99,15 +100,11 @@ class OC_Archive_ZIP extends OC_Archive{ * @return array */ function getFiles(){ - if(count($this->contents)){ - return $this->contents; - } $fileCount=$this->zip->numFiles; $files=array(); for($i=0;$i<$fileCount;$i++){ $files[]=$this->zip->getNameIndex($i); } - $this->contents=$files; return $files; } /** @@ -143,7 +140,7 @@ class OC_Archive_ZIP extends OC_Archive{ * @return bool */ function fileExists($path){ - return $this->zip->locateName($path)!==false; + return ($this->zip->locateName($path)!==false) or ($this->zip->locateName($path.'/')!==false); } /** * remove a file or folder from the archive @@ -151,7 +148,11 @@ class OC_Archive_ZIP extends OC_Archive{ * @return bool */ function remove($path){ - return $this->zip->deleteName($path); + if($this->fileExists($path.'/')){ + return $this->zip->deleteName($path.'/'); + }else{ + return $this->zip->deleteName($path); + } } /** * get a file handler @@ -188,4 +189,12 @@ class OC_Archive_ZIP extends OC_Archive{ unlink($tmpFile); } } + + private function stripPath($path){ + if(substr($path,0,1)=='/'){ + return substr($path,1); + }else{ + return $path; + } + } } diff --git a/apps/files_archive/tests/archive.php b/apps/files_archive/tests/archive.php index 2619d91b3b..9e99466a52 100644 --- a/apps/files_archive/tests/archive.php +++ b/apps/files_archive/tests/archive.php @@ -68,6 +68,7 @@ abstract class Test_Archive extends UnitTestCase { $this->instance->addFile('lorem.txt',$textFile); $this->assertEqual(1,count($this->instance->getFiles())); $this->assertTrue($this->instance->fileExists('lorem.txt')); + $this->assertFalse($this->instance->fileExists('lorem.txt/')); $this->assertEqual(file_get_contents($textFile),$this->instance->getFile('lorem.txt')); $this->instance->addFile('lorem.txt','foobar'); @@ -94,6 +95,17 @@ abstract class Test_Archive extends UnitTestCase { $this->assertTrue($this->instance->fileExists('lorem.txt')); $this->assertEqual(file_get_contents($dir.'/lorem.txt'),$this->instance->getFile('lorem.txt')); } + public function testFolder(){ + $this->instance=$this->getNew(); + $this->assertFalse($this->instance->fileExists('/test')); + $this->assertFalse($this->instance->fileExists('/test/')); + $this->instance->addFolder('/test'); + $this->assertTrue($this->instance->fileExists('/test')); + $this->assertTrue($this->instance->fileExists('/test/')); + $this->instance->remove('/test'); + $this->assertFalse($this->instance->fileExists('/test')); + $this->assertFalse($this->instance->fileExists('/test/')); + } public function testExtract(){ $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; $this->instance=$this->getExisting(); @@ -105,4 +117,17 @@ abstract class Test_Archive extends UnitTestCase { $this->assertEqual(file_get_contents($dir.'/lorem.txt'),file_get_contents($tmpDir.'lorem.txt')); OC_Helper::rmdirr($tmpDir); } + public function testMoveRemove(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + $textFile=$dir.'/lorem.txt'; + $this->instance=$this->getNew(); + $this->instance->addFile('lorem.txt',$textFile); + $this->assertFalse($this->instance->fileExists('target.txt')); + $this->instance->rename('lorem.txt','target.txt'); + $this->assertTrue($this->instance->fileExists('target.txt')); + $this->assertFalse($this->instance->fileExists('lorem.txt')); + $this->assertEqual(file_get_contents($textFile),$this->instance->getFile('target.txt')); + $this->instance->remove('target.txt'); + $this->assertFalse($this->instance->fileExists('target.txt')); + } } From 72882beb0d6ba7ebb70bdd6265c15656376e742c Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 28 Mar 2012 23:53:51 +0200 Subject: [PATCH 122/302] enble browsing tar files in the web interface --- apps/files_archive/js/archive.js | 4 ++++ apps/files_archive/lib/storage.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/files_archive/js/archive.js b/apps/files_archive/js/archive.js index ec316c7bf2..531eb61c01 100644 --- a/apps/files_archive/js/archive.js +++ b/apps/files_archive/js/archive.js @@ -11,5 +11,9 @@ $(document).ready(function() { window.location='index.php?dir='+encodeURIComponent($('#dir').val()).replace(/%2F/g, '/')+'/'+encodeURIComponent(filename); }); FileActions.setDefault('application/zip','Open'); + FileActions.register('application/x-gzip','Open','',function(filename){ + window.location='index.php?dir='+encodeURIComponent($('#dir').val()).replace(/%2F/g, '/')+'/'+encodeURIComponent(filename); + }); + FileActions.setDefault('application/x-gzip','Open'); } }); diff --git a/apps/files_archive/lib/storage.php b/apps/files_archive/lib/storage.php index 598b85f0db..700d963304 100644 --- a/apps/files_archive/lib/storage.php +++ b/apps/files_archive/lib/storage.php @@ -125,7 +125,7 @@ class OC_Filestorage_Archive extends OC_Filestorage_Common{ self::$rootView=new OC_FilesystemView(''); } self::$enableAutomount=false;//prevent recursion - $supported=array('zip'); + $supported=array('zip','tar.gz','tar.bz2','tgz'); foreach($supported as $type){ $ext='.'.$type.'/'; if(($pos=strpos(strtolower($path),$ext))!==false){ From d6a9af31389819c95084e33aab3c7f4a6d4cbef8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 29 Mar 2012 00:07:28 +0200 Subject: [PATCH 123/302] add support for installing apps from tgz --- lib/installer.php | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/installer.php b/lib/installer.php index 2a9676998f..db64d8e32d 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -62,7 +62,7 @@ class OC_Installer{ //download the file if necesary if($data['source']=='http'){ - $path=OC_Helper::tmpFile('.zip'); + $path=OC_Helper::tmpFile(); if(!isset($data['href'])){ OC_Log::write('core','No href specified when installing app from http',OC_Log::ERROR); return false; @@ -76,14 +76,24 @@ class OC_Installer{ $path=$data['path']; } + //detect the archive type + $mime=OC_Helper::getMimeType($path); + if($mime=='application/zip'){ + rename($path,$path.'.zip'); + $path.='.zip'; + }elseif($mime=='application/x-gzip'){ + rename($path,$path.'.tgz'); + $path.='.tgz'; + }else{ + OC_Log::write('core','Archives of type '.$mime.' are not supported',OC_Log::ERROR); + return false; + } + //extract the archive in a temporary folder - $extractDir=tempnam(get_temp_dir(),'oc_installer_uncompressed_'); - unlink($extractDir); + $extractDir=OC_Helper::tmpFolder(); mkdir($extractDir); - $zip = new ZipArchive; - if($zip->open($path)===true){ - $zip->extractTo($extractDir); - $zip->close(); + if($archive=OC_Archive::open($path)){ + $archive->extract($extractDir); } else { OC_Log::write('core','Failed to open archive when installing app',OC_Log::ERROR); OC_Helper::rmdirr($extractDir); From 60ba5508a4bfaf0581301a6240011060a7432997 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 29 Mar 2012 00:11:29 +0200 Subject: [PATCH 124/302] add support for installing apps from a subfolder in the zip/tgz file --- lib/installer.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/installer.php b/lib/installer.php index db64d8e32d..c5ecacae54 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -104,6 +104,17 @@ class OC_Installer{ } //load the info.xml file of the app + if(!is_file($extractDir.'/appinfo/info.xml')){ + //try to find it in a subdir + $dh=opendir($extractDir); + while($folder=readdir($dh)){ + if(substr($folder,0,1)!='.' and is_dir($extractDir.'/'.$folder)){ + if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')){ + $extractDir.='/'.$folder; + } + } + } + } if(!is_file($extractDir.'/appinfo/info.xml')){ OC_Log::write('core','App does not provide an info.xml file',OC_Log::ERROR); OC_Helper::rmdirr($extractDir); From 5fef9dfc22eba8d62e5db412632927a523ebf7d5 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Wed, 28 Mar 2012 21:18:17 -0400 Subject: [PATCH 125/302] Make users only able to share with users in groups they belong to --- apps/files_sharing/ajax/userautocomplete.php | 22 +++++++++++--------- apps/files_sharing/lib_share.php | 14 +++++++++++-- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/files_sharing/ajax/userautocomplete.php b/apps/files_sharing/ajax/userautocomplete.php index 9d971fb62a..38b673ee51 100644 --- a/apps/files_sharing/ajax/userautocomplete.php +++ b/apps/files_sharing/ajax/userautocomplete.php @@ -7,21 +7,23 @@ OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('files_sharing'); $users = array(); -$ocusers = OC_User::getUsers(); +$groups = array(); $self = OC_User::getUser(); -$groups = OC_Group::getUserGroups($self); +$userGroups = OC_Group::getUserGroups($self); $users[] = ""; -foreach ($ocusers as $user) { - if ($user != $self) { - $users[] = ""; +$groups[] = ""; +foreach ($userGroups as $group) { + $groupUsers = OC_Group::usersInGroup($group); + foreach ($groupUsers as $user) { + if ($user != $self) { + $users[] = ""; + } } + $groups[] = ""; } $users[] = ""; -$users[] = ""; -foreach ($groups as $group) { - $users[] = ""; -} -$users[] = ""; +$groups[] = ""; +$users = array_merge($users, $groups); OC_JSON::encodedPrint($users); ?> diff --git a/apps/files_sharing/lib_share.php b/apps/files_sharing/lib_share.php index 42739bdfba..673984f393 100644 --- a/apps/files_sharing/lib_share.php +++ b/apps/files_sharing/lib_share.php @@ -52,8 +52,18 @@ class OC_Share { // Remove the owner from the list of users in the group $uid_shared_with = array_diff($uid_shared_with, array($uid_owner)); } else if (OC_User::userExists($uid_shared_with)) { - $gid = null; - $uid_shared_with = array($uid_shared_with); + $userGroups = OC_Group::getUserGroups($uid_owner); + // Check if the user is in one of the owner's groups + foreach ($userGroups as $group) { + if ($inGroup = OC_Group::inGroup($uid_shared_with, $group)) { + $gid = null; + $uid_shared_with = array($uid_shared_with); + break; + } + } + if (!$inGroup) { + throw new Exception("You can't share with ".$uid_shared_with); + } } else { throw new Exception($uid_shared_with." is not a user"); } From 73eca66a892ade0894e35fb4b57d7b45a5e1c872 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Thu, 29 Mar 2012 10:17:08 +0000 Subject: [PATCH 126/302] Fix comments --- lib/migrate.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/migrate.php b/lib/migrate.php index b16b84b2a4..fe5ac45600 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -75,7 +75,8 @@ class OC_Migrate{ * @breif exports a user, or owncloud instance * @param ootional $type string type of export, defualts to user * @param otional $path string path to zip output folder - * @param optional $uid string user id of user to export if export type is user, defaults to current + * @param optional $uid string user id of user to export if export type is user, defaults to current + * @return false on error, path to zip on success */ public static function export( $type='user', $path=null, $uid=null ){ $datadir = OC_Config::getValue( 'datadirectory' ); From b559952806d1af4e20853d60fc339828b28b9a71 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Thu, 29 Mar 2012 14:30:54 +0200 Subject: [PATCH 127/302] Cleanup stylesheet. --- apps/contacts/css/contacts.css | 127 +++------------------------------ 1 file changed, 11 insertions(+), 116 deletions(-) diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 7c36a511d6..76b5972ba3 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -10,8 +10,6 @@ #contacts_propertymenu_button { position:absolute;top:15px;right:150px; background:url('../../../core/img/actions/add.svg') no-repeat center; } #contacts_propertymenu { background-color: #fff; position:absolute;top:40px;right:150px; overflow:hidden; text-overflow:ellipsis; /*border: thin solid #1d2d44;*/ -moz-box-shadow:0 0 10px #000; -webkit-box-shadow:0 0 10px #000; box-shadow:0 0 10px #000; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; } #contacts_propertymenu li { display: block; font-weight: bold; height: 20px; width: 100px; } -/*#contacts_propertymenu li:first-child { border-top: thin solid #1d2d44; -moz-border-radius-topleft:0.5em; -webkit-border-top-left-radius:0.5em; border-top-left-radius:0.5em; -moz-border-radius-topright:0.5em; -webkit-border-top-right-radius:0.5em; border-top-right-radius:0.5em; } -#contacts_propertymenu li:last-child { border-bottom: thin solid #1d2d44; -moz-border-radius-bottomleft:0.5em; -webkit-border-bottom-left-radius:0.5em; border-bottom-left-radius:0.5em; -moz-border-radius-bottomright:0.5em; -webkit-border-bottom-right-radius:0.5em; border-bottom-right-radius:0.5em; }*/ #contacts_propertymenu li a { padding: 3px; display: block } #contacts_propertymenu li:hover { background-color: #1d2d44; } #contacts_propertymenu li a:hover { color: #fff } @@ -25,54 +23,12 @@ #card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #f8f8f8; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } #card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { background-color: #ffc0c0 !important; } -/*input[type="text"]:valid,input[type="email"]:valid,input[type="tel"]:valid,input[type="date"]:valid { background-color: #b1d28f !important; }*/ -dl.form -{ - width: 100%; - float: left; - clear: right; - margin: 0; - padding: 0; -} - -.form dt -{ - display: table-cell; - clear: left; - float: left; - width: 7em; - /*overflow: hidden;*/ - margin: 0; - padding: 0.8em 0.5em 0 0; - font-weight: bold; - text-align:right; - text-overflow:ellipsis; - o-text-overflow: ellipsis; - vertical-align: text-bottom; - /* - white-space: pre-wrap; - white-space: -moz-pre-wrap !important; - white-space: -pre-wrap; - white-space: -o-pre-wrap;*/ -} - -.form dd -{ - display: table-cell; - clear: right; - float: left; - margin: 0; - padding: 0px; - white-space: nowrap; - vertical-align: text-bottom; - /*min-width: 20em;*/ - /*background-color: yellow;*/ -} +dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } +.form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; font-weight: bold; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } +.form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } .loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } -/*.add { cursor: pointer; width: 25px; height: 25px; margin: 0px; float: right; position:relative; content: "\+"; font-weight: bold; color: #666; font-size: large; bottom: 0px; right: 0px; clear: both; text-align: center; vertical-align: bottom; display: none; }*/ - .listactions { height: 1em; width:60px; float: left; clear: right; } .add,.edit,.delete,.mail, .globe { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; display: none; } .add { background:url('../../../core/img/actions/add.svg') no-repeat center; clear: both; } @@ -82,75 +38,21 @@ dl.form /*.globe { background:url('../img/globe.svg') no-repeat center; }*/ .globe { background:url('../../../core/img/actions/public.svg') no-repeat center; } -#messagebox_msg { font-weight: bold; font-size: 1.2em; } - -/* Name editor */ -#edit_name_dialog { - /*width: 25em;*/ - padding:0; -} -#edit_name_dialog > input { - width: 15em; -} -/* Address editor */ -#edit_address_dialog { - /*width: 30em;*/ -} -#edit_address_dialog > input { - width: 15em; -} +#edit_name_dialog { padding:0; } +#edit_name_dialog > input { width: 15em; } +#edit_address_dialog { /*width: 30em;*/ } +#edit_address_dialog > input { width: 15em; } #edit_photo_dialog_img { display: block; width: 150; height: 200; border: thin solid black; } #fn { float: left; } /** * Create classes form, floateven and floatodd which flows left and right respectively. */ -.contactsection { - float: left; - min-width: 30em; - max-width: 40em; - margin: 0.5em; - border: thin solid lightgray; - -webkit-border-radius: 0.5em; - -moz-border-radius: 0.5em; - border-radius: 0.5em; - background-color: #f8f8f8; -} +.contactsection { float: left; min-width: 30em; max-width: 40em; margin: 0.5em; border: thin solid lightgray; -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8; } -.contactpart legend { - /*background: #fff; - font-weight: bold; - left: 1em; - border: thin solid gray; - -webkit-border-radius: 0.5em; - -moz-border-radius: 0.5em; - border-radius: 0.5em; - padding: 3px;*/ -width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; -} -/*#contacts_details_photo { - cursor: pointer; - z-index:1; - margin: auto; -} -*/ -#cropbox { - margin: auto; -} +.contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } +#cropbox { margin: auto; } -/* Photo editor */ -/*#contacts_details_photo_wrapper { - z-index: 1000; -}*/ -#contacts_details_photo { - border-radius: 0.5em; - border: thin solid #bbb; - padding: 0.5em; - margin: 1em 1em 1em 7em; - cursor: pointer; - /*background: #f8f8f8;*/ - background: url(../../../core/img/loading.gif) no-repeat center center; - clear: right; -} +#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; padding: 0.5em; margin: 1em 1em 1em 7em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; clear: right; } #contacts_details_photo:hover { background: #fff; } #contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; } /* Address editor */ @@ -168,13 +70,6 @@ dl.addresscard dd > ul { margin: 0.3em; padding: 0.3em; } #adr_zipcode {} #adr_country {} -.delimiter { - height: 10px; - clear: both; -} - -/*input[type="text"] { float: left; max-width: 15em; } -input[type="radio"] { float: left; -khtml-appearance: none; width: 20px; height: 20px; vertical-align: middle; }*/ #file_upload_target, #crop_target { display:none; } #file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; position:absolute; left:0; top:0; cursor:pointer; width:0; height:0;} From 11f7eeb63a441a71ab90ea31001471562215a94d Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Thu, 29 Mar 2012 15:24:32 +0200 Subject: [PATCH 128/302] Improve actions on delete. --- apps/contacts/ajax/deletecard.php | 10 ++- apps/contacts/js/contacts.js | 87 +++++++++++++++--------- apps/contacts/templates/part.contact.php | 1 + 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/apps/contacts/ajax/deletecard.php b/apps/contacts/ajax/deletecard.php index e26dfd6ebf..5675aef5f1 100644 --- a/apps/contacts/ajax/deletecard.php +++ b/apps/contacts/ajax/deletecard.php @@ -19,6 +19,11 @@ * License along with this library. If not, see . * */ +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG); + exit(); +} // Init owncloud require_once('../../../lib/base.php'); @@ -27,7 +32,10 @@ require_once('../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('contacts'); -$id = $_GET['id']; +$id = isset($_GET['id'])?$_GET['id']:null; +if(!$id) { + bailOut(OC_Contacts_App::$l10n->t('id is not set.')); +} $card = OC_Contacts_App::getContactObject( $id ); OC_Contacts_VCard::delete($id); diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index e182702745..d314878cc0 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -213,19 +213,27 @@ Contacts={ honpre:'', honsuf:'', data:undefined, - update:function() { + update:function(id) { // Make sure proper DOM is loaded. - console.log('Card.update(), #n: ' + $('#n').length); + var newid; + console.log('Card.update(), id: ' + id); console.log('Card.update(), #contacts: ' + $('#contacts li').length); - if($('#n').length == 0 && $('#contacts li').length > 0) { + if(id == undefined) { + newid = $('#contacts li:first-child').data('id'); + } else { + newid = id; + } + if($('#contacts li').length > 0) { $.getJSON(OC.filePath('contacts', 'ajax', 'loadcard.php'),{},function(jsondata){ if(jsondata.status == 'success'){ $('#rightcontent').html(jsondata.data.page); Contacts.UI.loadHandlers(); if($('#contacts li').length > 0) { - var firstid = $('#contacts li:first-child').data('id'); - console.log('trying to load: ' + firstid); - $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':firstid},function(jsondata){ + //var newid = $('#contacts li:first-child').data('id'); + //$('#contacts li:first-child').addClass('active'); + $('#leftcontent li[data-id="'+newid+'"]').addClass('active'); + console.log('trying to load: ' + newid); + $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':newid},function(jsondata){ if(jsondata.status == 'success'){ Contacts.UI.Card.loadContact(jsondata.data); } else{ @@ -300,35 +308,49 @@ Contacts={ } }); }, - delete: function() { + delete:function() { $('#contacts_deletecard').tipsy('hide'); - $.getJSON('ajax/deletecard.php',{'id':this.id},function(jsondata){ - if(jsondata.status == 'success'){ - $('#leftcontent [data-id="'+jsondata.data.id+'"]').remove(); - $('#rightcontent').data('id',''); - //$('#rightcontent').empty(); - this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = ''; - this.data = undefined; - // Load first in list. - if($('#contacts li').length > 0) { - Contacts.UI.Card.update(); - } else { - // load intro page - $.getJSON('ajax/loadintro.php',{},function(jsondata){ - if(jsondata.status == 'success'){ - id = ''; - $('#rightcontent').data('id',''); - $('#rightcontent').html(jsondata.data.page); + OC.dialogs.confirm(t('contacts', 'Are you sure you want to delete this contact?'), t('contacts', 'Warning'), function(answer) { + if(answer == true) { + $.getJSON('ajax/deletecard.php',{'id':Contacts.UI.Card.id},function(jsondata){ + if(jsondata.status == 'success'){ + var newid = ''; + var curlistitem = $('#leftcontent [data-id="'+jsondata.data.id+'"]'); + var newlistitem = curlistitem.prev(); + console.log('Previous: ' + newlistitem); + if(newlistitem == undefined) { + newlistitem = curlistitem.next(); } - else{ - OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + curlistitem.remove(); + if(newlistitem != undefined) { + newid = newlistitem.data('id'); } - }); - } - } - else{ - OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); - //alert(jsondata.data.message); + $('#rightcontent').data('id',newid); + //$('#rightcontent').empty(); + this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = ''; + this.data = undefined; + // Load first in list. + if($('#contacts li').length > 0) { + Contacts.UI.Card.update(newid); + } else { + // load intro page + $.getJSON('ajax/loadintro.php',{},function(jsondata){ + if(jsondata.status == 'success'){ + id = ''; + $('#rightcontent').data('id',''); + $('#rightcontent').html(jsondata.data.page); + } + else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + } + } + else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + //alert(jsondata.data.message); + } + }); } }); return false; @@ -1232,6 +1254,7 @@ $(document).ready(function(){ */ $('#leftcontent li').live('click',function(){ var id = $(this).data('id'); + $(this).addClass('active'); var oldid = $('#rightcontent').data('id'); if(oldid != 0){ $('#leftcontent li[data-id="'+oldid+'"]').removeClass('active'); diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index a93069fa72..7e6dedb843 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -131,6 +131,7 @@ $(document).ready(function(){ if(''!='') { $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':''},function(jsondata){ if(jsondata.status == 'success'){ + $('#leftcontent li[data-id=""]').addClass('active'); Contacts.UI.Card.loadContact(jsondata.data); } else{ From 4b3282b325ea1a24c939481c8b2f2a2843e83f7c Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Thu, 29 Mar 2012 15:36:04 +0200 Subject: [PATCH 129/302] Contacts: Removed obsolete code. --- apps/contacts/templates/part.contact.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index 7e6dedb843..d243c2b5e1 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -56,13 +56,6 @@ $id = isset($_['id']) ? $_['id'] : ''; - From e2fd0bbf026631c05874f816e6d8d56eb6a23293 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 29 Mar 2012 18:16:41 +0200 Subject: [PATCH 130/302] IE 8: fix appeareance of checkboxes on login and files pages --- core/templates/login.php | 1 + files/templates/index.php | 1 + 2 files changed, 2 insertions(+) diff --git a/core/templates/login.php b/core/templates/login.php index 6c0a7a1222..82222c8212 100644 --- a/core/templates/login.php +++ b/core/templates/login.php @@ -1,3 +1,4 @@ +
'; } ?> diff --git a/files/templates/index.php b/files/templates/index.php index da1e58ce13..418a170fec 100644 --- a/files/templates/index.php +++ b/files/templates/index.php @@ -1,3 +1,4 @@ +
From af75ecf9da9d66f9ef772d4482a7e79a960fd464 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 29 Mar 2012 18:17:46 +0200 Subject: [PATCH 131/302] make checkboxes on files page work with IE8 () --- files/js/files.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/js/files.js b/files/js/files.js index 539d559889..df9f45a7af 100644 --- a/files/js/files.js +++ b/files/js/files.js @@ -98,7 +98,7 @@ $(document).ready(function() { procesSelection(); }); - $('td.filename input:checkbox').live('click',function(event) { + $('td.filename input:checkbox').live('change',function(event) { if (event.shiftKey) { var last = $(lastChecked).parent().parent().prevAll().length; var first = $(this).parent().parent().prevAll().length; From 45a3d84748b4e61a562d338df0cc0fd3badcc830 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 29 Mar 2012 18:30:27 +0200 Subject: [PATCH 132/302] checkobx opacity on files page for IE8 --- files/css/files.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/css/files.css b/files/css/files.css index 512e462cb6..9e950517b8 100644 --- a/files/css/files.css +++ b/files/css/files.css @@ -64,8 +64,8 @@ table td.filename .nametext { width:60%; } table td.filename form { float:left; font-size:.85em; } table thead.fixed tr{ position:fixed; top:6.5em; z-index:49; -moz-box-shadow:0 -3px 7px #ddd; -webkit-box-shadow:0 -3px 7px #ddd; box-shadow:0 -3px 7px #ddd; } table thead.fixed { height:2em; } -#fileList tr td.filename>input[type=checkbox]:first-child { opacity:0; float:left; margin:.7em 0 0 1em; /* bigger clickable area doesn’t work in FF width:2.8em; height:2.4em;*/ -webkit-transition:opacity 500ms; -moz-transition:opacity 500ms; -o-transition:opacity 500ms; transition:opacity 500ms; } -#fileList tr td.filename>input[type="checkbox"]:hover:first-child { opacity:.8; } +#fileList tr td.filename>input[type=checkbox]:first-child { opacity:0; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; float:left; margin:.7em 0 0 1em; /* bigger clickable area doesn’t work in FF width:2.8em; height:2.4em;*/ -webkit-transition:opacity 500ms; -moz-transition:opacity 500ms; -o-transition:opacity 500ms; transition:opacity 500ms; } +#fileList tr td.filename>input[type="checkbox"]:hover:first-child { opacity:.8; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; } #fileList tr td.filename>input[type="checkbox"]:checked:first-child { opacity:1; } #fileList tr td.filename { -webkit-transition:background-image 500ms; -moz-transition:background-image 500ms; -o-transition:background-image 500ms; transition:background-image 500ms; } #select_all { float:left; margin:.3em 0.6em 0 .5em; } From f74d11c0c3c4a3bcd733499edd63f8e31389a3a3 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Thu, 29 Mar 2012 20:45:21 +0200 Subject: [PATCH 133/302] fix gallery sharing when root is not / --- apps/gallery/ajax/galleryOp.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/gallery/ajax/galleryOp.php b/apps/gallery/ajax/galleryOp.php index b0433898cd..1b3ad48f56 100644 --- a/apps/gallery/ajax/galleryOp.php +++ b/apps/gallery/ajax/galleryOp.php @@ -127,6 +127,9 @@ function handleGetGallery($path) { function handleShare($path, $share, $recursive) { $recursive = $recursive == 'true' ? 1 : 0; $owner = OC_User::getUser(); + $root = OC_Preferences::getValue(OC_User::getUser(),'gallery', 'root', '/'); + $path = utf8_decode(rtrim($root.$path,'/')); + if($path == '') $path = '/'; $r = OC_Gallery_Album::find($owner, null, $path); if ($row = $r->fetchRow()) { $albumId = $row['album_id']; From 7bc9fa765c75846e5a293ea534505d3722d612f5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 30 Mar 2012 13:48:44 +0200 Subject: [PATCH 134/302] optimizations for updateApps --- lib/app.php | 29 +++++++++++++++++++++-------- lib/installer.php | 4 ++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/lib/app.php b/lib/app.php index 3daf539aa2..5ee9a0e565 100755 --- a/lib/app.php +++ b/lib/app.php @@ -265,19 +265,20 @@ class OC_App{ /** * @brief Read app metadata from the info.xml file * @param string $appid id of the app or the path of the info.xml file + * @param boolean path (optional) * @returns array */ - public static function getAppInfo($appid){ - if(is_file($appid)){ + public static function getAppInfo($appid,$path=false){ + if($path){ $file=$appid; }else{ $file=OC::$APPSROOT.'/apps/'.$appid.'/appinfo/info.xml'; - if(!is_file($file)){ - return array(); - } } $data=array(); $content=file_get_contents($file); + if(!$content){ + return; + } $xml = new SimpleXMLElement($content); $data['info']=array(); foreach($xml->children() as $child){ @@ -381,9 +382,8 @@ class OC_App{ */ public static function updateApps(){ // The rest comes here - $apps = OC_Appconfig::getApps(); - foreach( $apps as $app ){ - $installedVersion=OC_Appconfig::getValue($app,'installed_version'); + $versions = self::getAppVersions(); + foreach( $versions as $app=>$installedVersion ){ $appInfo=OC_App::getAppInfo($app); if (isset($appInfo['version'])) { $currentVersion=$appInfo['version']; @@ -395,6 +395,19 @@ class OC_App{ } } + /** + * get the installed version of all papps + */ + public static function getAppVersions(){ + $versions=array(); + $query = OC_DB::prepare( 'SELECT appid, configvalue FROM *PREFIX*appconfig WHERE configkey = "installed_version"' ); + $result = $query->execute(); + while($row = $result->fetchRow()){ + $versions[$row['appid']]=$row['configvalue']; + } + return $versions; + } + /** * update the database for the app and call the update script * @param string appid diff --git a/lib/installer.php b/lib/installer.php index c5ecacae54..38e17130e3 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -123,7 +123,7 @@ class OC_Installer{ } return false; } - $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml'); + $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml',true); $basedir=OC::$APPSROOT.'/apps/'.$info['id']; //check if an app with the same id is already installed @@ -296,7 +296,7 @@ class OC_Installer{ if(is_file(OC::$APPSROOT."/apps/$app/appinfo/install.php")){ include(OC::$APPSROOT."/apps/$app/appinfo/install.php"); } - $info=OC_App::getAppInfo(OC::$APPSROOT."/apps/$app/appinfo/info.xml"); + $info=OC_App::getAppInfo($app); OC_Appconfig::setValue($app,'installed_version',$info['version']); return $info; } From a07c6b1a2ea66f00b3e85d480703ca06a4b241be Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 30 Mar 2012 14:00:24 +0200 Subject: [PATCH 135/302] optimizations for loadApps --- lib/app.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/app.php b/lib/app.php index 5ee9a0e565..fa0a1d22d1 100755 --- a/lib/app.php +++ b/lib/app.php @@ -55,12 +55,10 @@ class OC_App{ } // The rest comes here - $apps = OC_Appconfig::getApps(); + $apps = self::getEnabledApps(); foreach( $apps as $app ){ - if( self::isEnabled( $app )){ - if(is_file(OC::$APPSROOT.'/apps/'.$app.'/appinfo/app.php')){ - require( $app.'/appinfo/app.php' ); - } + if(is_file(OC::$APPSROOT.'/apps/'.$app.'/appinfo/app.php')){ + require( $app.'/appinfo/app.php' ); } } @@ -70,6 +68,19 @@ class OC_App{ return true; } + /** + * get all enabled apps + */ + public static function getEnabledApps(){ + $apps=array(); + $query = OC_DB::prepare( 'SELECT appid FROM *PREFIX*appconfig WHERE configkey = "enabled" AND configvalue="yes"' ); + $query->execute(); + while($row=$query->fetchRow()){ + $apps[]=$row['appid']; + } + return $apps; + } + /** * @brief checks whether or not an app is enabled * @param $app app From 523fdda39915dd49190727ac74458a28f2d00f10 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 30 Mar 2012 14:39:07 +0200 Subject: [PATCH 136/302] add the option to only load apps of a specific type --- apps/files_archive/appinfo/info.xml | 3 ++ apps/files_encryption/appinfo/info.xml | 3 ++ apps/files_remote/appinfo/info.xml | 3 ++ apps/files_sharing/appinfo/info.xml | 3 ++ files/ajax/download.php | 3 ++ files/ajax/list.php | 3 ++ files/ajax/mimeicon.php | 3 ++ files/webdav.php | 3 ++ lib/app.php | 51 +++++++++++++++++++++++--- lib/base.php | 7 +++- 10 files changed, 76 insertions(+), 6 deletions(-) diff --git a/apps/files_archive/appinfo/info.xml b/apps/files_archive/appinfo/info.xml index df767d39f6..236b5a64b0 100644 --- a/apps/files_archive/appinfo/info.xml +++ b/apps/files_archive/appinfo/info.xml @@ -7,4 +7,7 @@ AGPL Robin Appelman 3 + + + diff --git a/apps/files_encryption/appinfo/info.xml b/apps/files_encryption/appinfo/info.xml index 053044aaed..691b265bf6 100644 --- a/apps/files_encryption/appinfo/info.xml +++ b/apps/files_encryption/appinfo/info.xml @@ -7,4 +7,7 @@ AGPL Robin Appelman 3 + + + diff --git a/apps/files_remote/appinfo/info.xml b/apps/files_remote/appinfo/info.xml index 0720b6095b..8cf66ddbc3 100644 --- a/apps/files_remote/appinfo/info.xml +++ b/apps/files_remote/appinfo/info.xml @@ -7,4 +7,7 @@ AGPL Robin Appelman 3 + + + diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml index abf847b448..8fda775520 100644 --- a/apps/files_sharing/appinfo/info.xml +++ b/apps/files_sharing/appinfo/info.xml @@ -8,4 +8,7 @@ Michael Gapczynski 2 + + + diff --git a/files/ajax/download.php b/files/ajax/download.php index 198069f3fa..39852613ab 100644 --- a/files/ajax/download.php +++ b/files/ajax/download.php @@ -21,6 +21,9 @@ * */ +// only need filesystem apps +$RUNTIME_APPTYPES=array('filesystem'); + // Init owncloud require_once('../../lib/base.php'); diff --git a/files/ajax/list.php b/files/ajax/list.php index 8a414827e1..ec9ab7342d 100644 --- a/files/ajax/list.php +++ b/files/ajax/list.php @@ -1,5 +1,8 @@ children() as $child){ - $data[$child->getName()]=(string)$child; + if($child->getName()=='types'){ + $data['types']=array(); + foreach($child->children() as $type){ + $data['types'][]=$type->getName(); + } + }else{ + $data[$child->getName()]=(string)$child; + } } + self::$appInfo[$appid]=$data; return $data; } diff --git a/lib/base.php b/lib/base.php index b07ac5af41..b031572f17 100644 --- a/lib/base.php +++ b/lib/base.php @@ -333,8 +333,13 @@ class OC{ // Load Apps // This includes plugins for users and filesystems as well global $RUNTIME_NOAPPS; + global $RUNTIME_APPTYPES; if(!$RUNTIME_NOAPPS ){ - OC_App::loadApps(); + if($RUNTIME_APPTYPES){ + OC_App::loadApps($RUNTIME_APPTYPES); + }else{ + OC_App::loadApps(); + } } //make sure temporary files are cleaned up From 7552390031ee10ed5006ef927fa1f55cdd148554 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 30 Mar 2012 18:12:33 +0200 Subject: [PATCH 137/302] add path_hash to the filesystem cache --- db_structure.xml | 28 ++++++++++++++++++++++------ lib/filecache.php | 28 ++++++++++++++-------------- lib/util.php | 2 +- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index 5eef44d8e8..82d2a731d4 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -64,6 +64,15 @@ 512 + + path_hash + text + + + true + 32 + + parent integer @@ -79,7 +88,7 @@ true - 512 + 300 @@ -159,14 +168,13 @@ 1 - + parent_index @@ -176,6 +184,14 @@ + + name_index + + name + ascending + + + parent_name_index diff --git a/lib/filecache.php b/lib/filecache.php index 280a9929db..a8c48e3f14 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -59,8 +59,8 @@ class OC_FileCache{ $root=''; } $path=$root.$path; - $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path=?'); - $result=$query->execute(array($path))->fetchRow(); + $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path_hash=?'); + $result=$query->execute(array(md5($path)))->fetchRow(); if(is_array($result)){ return $result; }else{ @@ -111,8 +111,8 @@ class OC_FileCache{ } $mimePart=dirname($data['mimetype']); $user=OC_User::getUser(); - $query=OC_DB::prepare('INSERT INTO *PREFIX*fscache(parent, name, path, size, mtime, ctime, mimetype, mimepart,user,writable,encrypted,versioned) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)'); - $result=$query->execute(array($parent,basename($path),$path,$data['size'],$data['mtime'],$data['ctime'],$data['mimetype'],$mimePart,$user,$data['writable'],$data['encrypted'],$data['versioned'])); + $query=OC_DB::prepare('INSERT INTO *PREFIX*fscache(parent, name, path, path_hash, size, mtime, ctime, mimetype, mimepart,user,writable,encrypted,versioned) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)'); + $result=$query->execute(array($parent,basename($path),$path,md5($path),$data['size'],$data['mtime'],$data['ctime'],$data['mimetype'],$mimePart,$user,$data['writable'],$data['encrypted'],$data['versioned'])); if(OC_DB::isError($result)){ OC_Log::write('files','error while writing file('.$path.') to cache',OC_Log::ERROR); } @@ -162,8 +162,8 @@ class OC_FileCache{ $oldPath=$root.$oldPath; $newPath=$root.$newPath; $newParent=self::getParentId($newPath); - $query=OC_DB::prepare('UPDATE *PREFIX*fscache SET parent=? ,name=?, path=? WHERE path=?'); - $query->execute(array($newParent,basename($newPath),$newPath,$oldPath)); + $query=OC_DB::prepare('UPDATE *PREFIX*fscache SET parent=? ,name=?, path=?, path_hash=? WHERE path_hash=?'); + $query->execute(array($newParent,basename($newPath),$newPath,md5($newPath),md5($oldPath))); } /** @@ -285,12 +285,12 @@ class OC_FileCache{ * @return int */ private static function getFileId($path){ - $query=OC_DB::prepare('SELECT id FROM *PREFIX*fscache WHERE path=?'); + $query=OC_DB::prepare('SELECT id FROM *PREFIX*fscache WHERE path_hash=?'); if(OC_DB::isError($query)){ OC_Log::write('files','error while getting file id of '.$path,OC_Log::ERROR); return -1; } - $result=$query->execute(array($path)); + $result=$query->execute(array(md5($path))); if(OC_DB::isError($result)){ OC_Log::write('files','error while getting file id of '.$path,OC_Log::ERROR); return -1; @@ -367,8 +367,8 @@ class OC_FileCache{ } } $path=$root.$path; - $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path=?'); - $result=$query->execute(array($path))->fetchRow(); + $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path_hash=?'); + $result=$query->execute(array(md5($path)))->fetchRow(); if(is_array($result)){ if(isset(self::$savedData[$path])){ $result=array_merge($result,self::$savedData[$path]); @@ -389,8 +389,8 @@ class OC_FileCache{ } } $path=$root.$path; - $query=OC_DB::prepare('SELECT size FROM *PREFIX*fscache WHERE path=?'); - $result=$query->execute(array($path)); + $query=OC_DB::prepare('SELECT size FROM *PREFIX*fscache WHERE path_hash=?'); + $result=$query->execute(array(md5($path))); if($row=$result->fetchRow()){ return $row['size']; }else{//file not in cache @@ -579,8 +579,8 @@ class OC_FileCache{ $mtime=$view->filemtime($path); $isDir=$view->is_dir($path); $path=$root.$path; - $query=OC_DB::prepare('SELECT mtime FROM *PREFIX*fscache WHERE path=?'); - $result=$query->execute(array($path)); + $query=OC_DB::prepare('SELECT mtime FROM *PREFIX*fscache WHERE path_hash=?'); + $result=$query->execute(array(md5($path))); if($row=$result->fetchRow()){ $cachedMTime=$row['mtime']; return ($mtime>$cachedMTime); diff --git a/lib/util.php b/lib/util.php index fa5b3daaab..529b6d958f 100644 --- a/lib/util.php +++ b/lib/util.php @@ -66,7 +66,7 @@ class OC_Util { * @return array */ public static function getVersion(){ - return array(3,00,3); + return array(3,00,4); } /** From 011132feb33b4f7954eb5df5e2283e9c8f8ae944 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 30 Mar 2012 18:10:16 +0200 Subject: [PATCH 138/302] renaming remote storage support to External storage support to clear up naming conflict --- apps/files_external/appinfo/app.php | 11 +++++++++++ .../{files_remote => files_external}/appinfo/info.xml | 6 +++--- apps/{files_remote => files_external}/lib/ftp.php | 0 apps/{files_remote => files_external}/lib/google.php | 0 apps/{files_remote => files_external}/lib/webdav.php | 0 .../{files_remote => files_external}/tests/config.php | 0 apps/{files_remote => files_external}/tests/ftp.php | 2 +- .../{files_remote => files_external}/tests/google.php | 4 ++-- .../{files_remote => files_external}/tests/webdav.php | 2 +- apps/files_remote/appinfo/app.php | 11 ----------- 10 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 apps/files_external/appinfo/app.php rename apps/{files_remote => files_external}/appinfo/info.xml (58%) rename apps/{files_remote => files_external}/lib/ftp.php (100%) rename apps/{files_remote => files_external}/lib/google.php (100%) rename apps/{files_remote => files_external}/lib/webdav.php (100%) rename apps/{files_remote => files_external}/tests/config.php (100%) rename apps/{files_remote => files_external}/tests/ftp.php (89%) rename apps/{files_remote => files_external}/tests/google.php (94%) rename apps/{files_remote => files_external}/tests/webdav.php (89%) delete mode 100644 apps/files_remote/appinfo/app.php diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php new file mode 100644 index 0000000000..95770b44b7 --- /dev/null +++ b/apps/files_external/appinfo/app.php @@ -0,0 +1,11 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_external/lib/ftp.php'; +OC::$CLASSPATH['OC_Filestorage_DAV']='apps/files_external/lib/webdav.php'; +OC::$CLASSPATH['OC_Filestorage_Google']='apps/files_external/lib/google.php'; diff --git a/apps/files_remote/appinfo/info.xml b/apps/files_external/appinfo/info.xml similarity index 58% rename from apps/files_remote/appinfo/info.xml rename to apps/files_external/appinfo/info.xml index 8cf66ddbc3..fb58297ff1 100644 --- a/apps/files_remote/appinfo/info.xml +++ b/apps/files_external/appinfo/info.xml @@ -1,8 +1,8 @@ - files_remote - Remote storage support - Mount remote storage sources + files_external + External storage support + Mount external storage sources 0.1 AGPL Robin Appelman diff --git a/apps/files_remote/lib/ftp.php b/apps/files_external/lib/ftp.php similarity index 100% rename from apps/files_remote/lib/ftp.php rename to apps/files_external/lib/ftp.php diff --git a/apps/files_remote/lib/google.php b/apps/files_external/lib/google.php similarity index 100% rename from apps/files_remote/lib/google.php rename to apps/files_external/lib/google.php diff --git a/apps/files_remote/lib/webdav.php b/apps/files_external/lib/webdav.php similarity index 100% rename from apps/files_remote/lib/webdav.php rename to apps/files_external/lib/webdav.php diff --git a/apps/files_remote/tests/config.php b/apps/files_external/tests/config.php similarity index 100% rename from apps/files_remote/tests/config.php rename to apps/files_external/tests/config.php diff --git a/apps/files_remote/tests/ftp.php b/apps/files_external/tests/ftp.php similarity index 89% rename from apps/files_remote/tests/ftp.php rename to apps/files_external/tests/ftp.php index 03633b7c0d..aa565751ba 100644 --- a/apps/files_remote/tests/ftp.php +++ b/apps/files_external/tests/ftp.php @@ -12,7 +12,7 @@ class Test_Filestorage_FTP extends Test_FileStorage { public function setUp(){ $id=uniqid(); - $this->config=include('apps/files_remote/tests/config.php'); + $this->config=include('apps/files_external/tests/config.php'); $this->config['ftp']['root'].='/'.$id;//make sure we have an new empty folder to work in $this->instance=new OC_Filestorage_FTP($this->config['ftp']); } diff --git a/apps/files_remote/tests/google.php b/apps/files_external/tests/google.php similarity index 94% rename from apps/files_remote/tests/google.php rename to apps/files_external/tests/google.php index b49f9e4647..1c02894522 100644 --- a/apps/files_remote/tests/google.php +++ b/apps/files_external/tests/google.php @@ -27,7 +27,7 @@ class Test_Filestorage_Google extends Test_FileStorage { public function setUp(){ $id=uniqid(); - $this->config=include('apps/files_remote/tests/config.php'); + $this->config=include('apps/files_external/tests/config.php'); $this->config['google']['root'].='/'.$id;//make sure we have an new empty folder to work in $this->instance=new OC_Filestorage_Google($this->config['google']); } @@ -35,4 +35,4 @@ class Test_Filestorage_Google extends Test_FileStorage { public function tearDown(){ $this->instance->rmdir('/'); } -} \ No newline at end of file +} diff --git a/apps/files_remote/tests/webdav.php b/apps/files_external/tests/webdav.php similarity index 89% rename from apps/files_remote/tests/webdav.php rename to apps/files_external/tests/webdav.php index 219fff8852..5179929054 100644 --- a/apps/files_remote/tests/webdav.php +++ b/apps/files_external/tests/webdav.php @@ -12,7 +12,7 @@ class Test_Filestorage_DAV extends Test_FileStorage { public function setUp(){ $id=uniqid(); - $this->config=include('apps/files_remote/tests/config.php'); + $this->config=include('apps/files_external/tests/config.php'); $this->config['webdav']['root'].='/'.$id;//make sure we have an new empty folder to work in $this->instance=new OC_Filestorage_DAV($this->config['webdav']); } diff --git a/apps/files_remote/appinfo/app.php b/apps/files_remote/appinfo/app.php deleted file mode 100644 index 02c1c3ae31..0000000000 --- a/apps/files_remote/appinfo/app.php +++ /dev/null @@ -1,11 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_remote/lib/ftp.php'; -OC::$CLASSPATH['OC_Filestorage_DAV']='apps/files_remote/lib/webdav.php'; -OC::$CLASSPATH['OC_Filestorage_Google']='apps/files_remote/lib/google.php'; From dde5660915232102f531ba5309ad691e9932b150 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 30 Mar 2012 18:15:03 +0200 Subject: [PATCH 139/302] renamed Gallery to Pictures, still needs to be changed in the code to also reflect in the path --- apps/gallery/appinfo/app.php | 4 ++-- apps/gallery/appinfo/info.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/gallery/appinfo/app.php b/apps/gallery/appinfo/app.php index 3e7e38301c..f7e0651275 100644 --- a/apps/gallery/appinfo/app.php +++ b/apps/gallery/appinfo/app.php @@ -32,14 +32,14 @@ $l = new OC_L10N('gallery'); OC_App::register(array( 'order' => 20, 'id' => 'gallery', - 'name' => 'Gallery')); + 'name' => 'Pictures')); OC_App::addNavigationEntry( array( 'id' => 'gallery_index', 'order' => 20, 'href' => OC_Helper::linkTo('gallery', 'index.php'), 'icon' => OC_Helper::imagePath('core', 'places/picture.svg'), - 'name' => $l->t('Gallery'))); + 'name' => $l->t('Pictures'))); class OC_GallerySearchProvider implements OC_Search_Provider{ static function search($query){ diff --git a/apps/gallery/appinfo/info.xml b/apps/gallery/appinfo/info.xml index 19c5dc8b25..4c8c1cee24 100644 --- a/apps/gallery/appinfo/info.xml +++ b/apps/gallery/appinfo/info.xml @@ -1,11 +1,11 @@ gallery - Gallery + Pictures 0.4 AGPL Bartek Przybylski 2 - Gallery application for ownCloud + Dedicated pictures application From 284955573c59e91ed4cab7771487d1cebbb44262 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 30 Mar 2012 18:18:09 +0200 Subject: [PATCH 140/302] clarifying remoteStorage description --- apps/remoteStorage/appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/remoteStorage/appinfo/info.xml b/apps/remoteStorage/appinfo/info.xml index 121587795d..0936bf9bd0 100644 --- a/apps/remoteStorage/appinfo/info.xml +++ b/apps/remoteStorage/appinfo/info.xml @@ -2,7 +2,7 @@ remoteStorage remoteStorage compatibility - Enables your users to use ownCloud as their remote storage for unhosted applications. + Enables you to use ownCloud as their remote storage for unhosted applications. This app requires the Webfinger app to be enabled as well. More info on the website of the unhosted movement. 0.5 AGPL or MIT Michiel de Jong From 65eee1f69dbbbe86bb4bf0716a464dc9f2c66a67 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 30 Mar 2012 19:44:38 +0200 Subject: [PATCH 141/302] clean pre-path_hash fscache entries --- db_structure.xml | 3 +-- files/ajax/scan.php | 1 + lib/filecache.php | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index 82d2a731d4..2df218d359 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -67,8 +67,7 @@ path_hash text - - + true 32 diff --git a/files/ajax/scan.php b/files/ajax/scan.php index 565275911b..db09b7d5c6 100644 --- a/files/ajax/scan.php +++ b/files/ajax/scan.php @@ -17,6 +17,7 @@ if($force or !OC_FileCache::inCache('')){ if(!$checkOnly){ OC_DB::beginTransaction(); OC_FileCache::scan('',$eventSource); + OC_FileCache::clean(); OC_DB::commit(); $eventSource->send('success',true); }else{ diff --git a/lib/filecache.php b/lib/filecache.php index a8c48e3f14..86d865ed9f 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -637,6 +637,14 @@ class OC_FileCache{ self::fileSystemWatcherWrite(array('path'=>$path),$root); } } + + /** + * clean old pre-path_hash entries + */ + public static function clean(){ + $query=OC_DB::prepare('DELETE FROM *PREFIX*fscache WHERE LENGTH(path_hash)<30'); + $query->execute(); + } } //watch for changes and try to keep the cache up to date From 6abb2cb92edc1edaefea7e37cd46a6c866239c27 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Fri, 30 Mar 2012 21:14:01 +0200 Subject: [PATCH 142/302] fix sharing nested galleries --- apps/gallery/js/album_cover.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/gallery/js/album_cover.js b/apps/gallery/js/album_cover.js index 061bbcd0b4..cd26001964 100644 --- a/apps/gallery/js/album_cover.js +++ b/apps/gallery/js/album_cover.js @@ -43,8 +43,9 @@ function shareGallery() { {text: 'Shared gallery address', name: 'address', type: 'text', value: existing_token}]; OC.dialogs.form(form_fields, t('gallery', 'Share gallery'), function(values){ var p = ''; - for (var i in paths) p += '/'+paths[i]; + for (var i in paths) p += paths[i]+'/'; if (p == '') p = '/'; + alert(p); $.getJSON(OC.filePath('gallery', 'ajax', 'galleryOp.php'), {operation: 'share', path: p, share: values[0].value, recursive: values[1].value}, function(r) { if (r.status == 'success') { Albums.shared = r.sharing; From 5161758921efb5bb50e579d8e6debfa93e595a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Fri, 30 Mar 2012 21:35:09 +0200 Subject: [PATCH 143/302] create static host-meta instead of symlink and .htaccess --- apps/user_webfinger/appinfo/install.php | 57 ++++++++++++------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/apps/user_webfinger/appinfo/install.php b/apps/user_webfinger/appinfo/install.php index 775141dce4..678d57ae8f 100644 --- a/apps/user_webfinger/appinfo/install.php +++ b/apps/user_webfinger/appinfo/install.php @@ -4,33 +4,32 @@ $thisAppDir = dirname($appInfoDir); $appsDir = dirname($thisAppDir); $ownCloudDir = dirname($appsDir); $docRoot = $_SERVER['DOCUMENT_ROOT']; -if(file_exists($docRoot . '/.well-known/host-meta')) { - OC_Log::write( - 'user_webfinger', - $docRoot . "/.well-known already exists; installation aborted", - OC_Log::ERROR - ); -} else { - if(@symlink($thisAppDir, $docRoot . '/.well-known')) { - OC_Log::write( - 'user_webfinger', - "Webfinger symlink created at " . $docRoot . "/.well-known", - OC_Log::INFO - ); - } else { - if(@symlink($thisAppDir, $ownCloudDir . '/.well-known')) { - OC_Log::write( - 'user_webfinger', - "Couldn't create webfinger symlink in document root, linked to " . $ownCloudDir . "/.well-known instead", - OC_Log::WARN - ); - } else { - OC_Log::write( - 'user_webfinger', - "Couldn't create webfinger symlink, either check write permissions or create the link manually!", - OC_Log::ERROR - ); - } - } +try { + $webRoot = substr(realpath($ownCloudDir), strlen(realpath($docRoot))); +} catch(Exception $e) { + // some servers fail on realpath(), let's try it the unsecure way: + $webRoot = substr($ownCloudDir, strlen($docRoot)); } -?> +$serverName = $_SERVER['SERVER_NAME']; +$lrddTmpl = 'http'; +if(isset($_SERVER['HTTPS'])) { + $lrddTmpl .= 's'; +} +$lrddTmpl .= '://' . $serverName . $webRoot . '/apps/user_webfinger/webfinger.php?q={uri}'; +$hostMetaPath = $docRoot . '/.well-known/host-meta'; +$hostMetaContents = " + + " . $serverName . " + + Resource Descriptor + +"; +@mkdir(dirname($hostMetaPath)); +$hostMeta = fopen($hostMetaPath, 'w'); +if(!$hostMeta) { + die("Could not open " . $hostMetaPath . " for writing, please check permissions!"); +} +if(!fwrite($hostMeta, $hostMetaContents, strlen($hostMetaContents))) { + die("Could not write to " . $hostMetaPath . ", please check permissions!"); +} +fclose($hostMeta); From 6b704a780dbe3daa4c13ad49ad4265c1db4a67aa Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Fri, 30 Mar 2012 22:50:57 +0200 Subject: [PATCH 144/302] select field added to oc.dialogs.form, gallery ported to use it --- apps/gallery/js/album_cover.js | 46 +++++++++++--------------------- apps/gallery/templates/index.php | 39 +-------------------------- core/js/oc-dialogs.js | 14 +++++++--- 3 files changed, 28 insertions(+), 71 deletions(-) diff --git a/apps/gallery/js/album_cover.js b/apps/gallery/js/album_cover.js index cd26001964..d44e7f83d1 100644 --- a/apps/gallery/js/album_cover.js +++ b/apps/gallery/js/album_cover.js @@ -113,42 +113,28 @@ function scanForAlbums(cleanup) { } function settings() { - $( '#g-dialog-settings' ).dialog({ - height: 180, - width: 350, - modal: false, - buttons: [ - { - text: t('gallery', 'Apply'), - click: function() { - var scanning_root = $('#g-scanning-root').val(); - var disp_order = $('#g-display-order option:selected').val(); + OC.dialogs.form([{text: t('gallery', 'Scanning root'), name: 'root', type:'text', value:gallery_scanning_root}, + {text: t('gallery', 'Default order'), name: 'order', type:'select', value:gallery_default_order, options:[ + {text:t('gallery', 'Ascending'), value:'ASC'}, {text: t('gallery', 'Descending'), value:'DESC'} ]}], + t('gallery', 'Settings'), + function(values) { + var scanning_root = values[0].value; + var disp_order = values[1].value; if (scanning_root == '') { - alert('Scanning root cannot be empty'); + OC.dialogs.alert(t('gallery', 'Scanning root cannot be empty'), t('gallery', 'Error')); return; } $.getJSON(OC.filePath('gallery','ajax','galleryOp.php'), {operation: 'store_settings', root: scanning_root, order: disp_order}, function(r) { if (r.status == 'success') { - if (r.rescan == 'yes') { - $('#g-dialog-settings').dialog('close'); - Albums.clear(document.getElementById('gallery_list')); - scanForAlbums(true); - return; - } + if (r.rescan == 'yes') { + Albums.clear(document.getElementById('gallery_list')); + scanForAlbums(true); + } + gallery_scanning_root = scanning_root; } else { - alert('Error: ' + r.cause); - return; + OC.dialogs.alert(t('gallery', 'Error: ') + r.cause, t('gallery', 'Error')); + return; } - $('#g-dialog-settings').dialog('close'); }); - } - }, - { - text: t('gallery', 'Cancel'), - click: function() { - $(this).dialog('close'); - } - } - ], - }); + }); } diff --git a/apps/gallery/templates/index.php b/apps/gallery/templates/index.php index c6373d3b0a..9bec5db1b9 100644 --- a/apps/gallery/templates/index.php +++ b/apps/gallery/templates/index.php @@ -9,7 +9,7 @@ OC_Util::addScript('files_imageviewer', 'jquery.fancybox-1.3.4.pack'); OC_Util::addStyle( 'files_imageviewer', 'jquery.fancybox-1.3.4' ); $l = new OC_L10N('gallery'); ?> - +
@@ -29,40 +29,3 @@ $l = new OC_L10N('gallery');
- - - - - - - diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index c11ac13332..35d0a0c5c4 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -84,10 +84,18 @@ OCdialogs = { } else content += '>'; } else if (type == 'text' || type == 'password' && fields[a].value) content += ' value="'+fields[a].value+'">'; + } else if (type == 'select') { + content += ''; } - content += "" + content += ''; } - content += ""; + content += ''; OCdialogs.message(content, title, OCdialogs.FORM_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback); }, message:function(content, title, dialog_type, buttons, callback) { @@ -144,7 +152,7 @@ OCdialogs = { if (callback != undefined) { var r = []; var c = 0; - $(c_id + ' input').each(function(i, elem) { + $(c_id + ' input, '+c_id+' select').each(function(i, elem) { r[c] = {name: $(elem).attr('name'), value: OCdialogs.determineValue(elem)}; c++; }); From 3a4521a012fe75d8ec3685e1eb87374e5abd9da2 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 30 Mar 2012 23:15:48 +0200 Subject: [PATCH 145/302] Add support for logging to syslog --- lib/log.php | 71 +++++++++--------------------------- lib/log/owncloud.php | 78 ++++++++++++++++++++++++++++++++++++++++ lib/log/syslog.php | 37 +++++++++++++++++++ settings/ajax/getlog.php | 2 +- settings/log.php | 2 +- 5 files changed, 133 insertions(+), 57 deletions(-) create mode 100644 lib/log/owncloud.php create mode 100644 lib/log/syslog.php diff --git a/lib/log.php b/lib/log.php index 4e450a027f..8bb2839be6 100644 --- a/lib/log.php +++ b/lib/log.php @@ -1,78 +1,39 @@ . - * + * Copyright (c) 2012 Bart Visscher + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ /** - *logging utilities + * logging utilities * - * Log is saved at data/owncloud.log (on default) + * Log is saved by default at data/owncloud.log using OC_Log_Owncloud. + * Selecting other backend is done with a config option 'log_type'. */ -class OC_Log{ +class OC_Log { const DEBUG=0; const INFO=1; const WARN=2; const ERROR=3; const FATAL=4; + static protected $class = null; + /** * write a message in the log * @param string $app * @param string $message * @param int level */ - public static function write($app,$message,$level){ - $minLevel=OC_Config::getValue( "loglevel", 2 ); - if($level>=$minLevel){ - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); - $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); - $entry=array('app'=>$app,'message'=>$message,'level'=>$level,'time'=>time()); - $fh=fopen($logFile,'a'); - fwrite($fh,json_encode($entry)."\n"); - fclose($fh); + public static function write($app, $message, $level) { + if (!self::$class) { + self::$class = 'OC_Log_'.ucfirst(OC_Config::getValue('log_type', 'owncloud')); + call_user_func(array(self::$class, 'init')); } - } - - /** - * get entries from the log in reverse chronological order - * @param int limit - * @param int offset - * @return array - */ - public static function getEntries($limit=50,$offset=0){ - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); - $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); - $entries=array(); - if(!file_exists($logFile)){ - return array(); - } - $contents=file($logFile); - if(!$contents){//error while reading log - return array(); - } - $end=max(count($contents)-$offset-1,0); - $start=max($end-$limit,0); - for($i=$end;$i>$start;$i--){ - $entries[]=json_decode($contents[$i]); - } - return $entries; + $log_class=self::$class; + $log_class::write($app, $message, $level); } } diff --git a/lib/log/owncloud.php b/lib/log/owncloud.php new file mode 100644 index 0000000000..6df346e9b1 --- /dev/null +++ b/lib/log/owncloud.php @@ -0,0 +1,78 @@ +. + * + */ + +/** + * logging utilities + * + * Log is saved at data/owncloud.log (on default) + */ + +class OC_Log_Owncloud { + /** + * Init class data + */ + public static function init() { + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int level + */ + public static function write($app, $message, $level) { + $minLevel=OC_Config::getValue( "loglevel", 2 ); + if($level>=$minLevel){ + $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); + $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); + $entry=array('app'=>$app, 'message'=>$message, 'level'=>$level,'time'=>time()); + $fh=fopen($logFile, 'a'); + fwrite($fh, json_encode($entry)."\n"); + fclose($fh); + } + } + + /** + * get entries from the log in reverse chronological order + * @param int limit + * @param int offset + * @return array + */ + public static function getEntries($limit=50, $offset=0){ + $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); + $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); + $entries=array(); + if(!file_exists($logFile)) { + return array(); + } + $contents=file($logFile); + if(!$contents) {//error while reading log + return array(); + } + $end=max(count($contents)-$offset-1, 0); + $start=max($end-$limit,0); + for($i=$end;$i>$start;$i--) { + $entries[]=json_decode($contents[$i]); + } + return $entries; + } +} diff --git a/lib/log/syslog.php b/lib/log/syslog.php new file mode 100644 index 0000000000..d1fb28d8b0 --- /dev/null +++ b/lib/log/syslog.php @@ -0,0 +1,37 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Log_Syslog { + static protected $levels = array( + OC_Log::DEBUG => LOG_DEBUG, + OC_Log::INFO => LOG_INFO, + OC_Log::WARN => LOG_WARNING, + OC_Log::ERROR => LOG_ERR, + OC_Log::FATAL => LOG_CRIT, + ); + + /** + * Init class data + */ + public static function init() { + openlog('ownCloud', LOG_PID | LOG_CONS, LOG_USER); + // Close at shutdown + register_shutdown_function('closelog'); + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int level + */ + public static function write($app, $message, $level) { + $syslog_level = self::$levels[$level]; + syslog($syslog_level, '{'.$app.'} '.$message); + } +} diff --git a/settings/ajax/getlog.php b/settings/ajax/getlog.php index 600ebefcec..ed48b2cae1 100644 --- a/settings/ajax/getlog.php +++ b/settings/ajax/getlog.php @@ -13,5 +13,5 @@ OC_JSON::checkAdminUser(); $count=(isset($_GET['count']))?$_GET['count']:50; $offset=(isset($_GET['offset']))?$_GET['offset']:0; -$entries=OC_Log::getEntries($count,$offset); +$entries=OC_Log_Owncloud::getEntries($count,$offset); OC_JSON::success(array("data" => $entries)); diff --git a/settings/log.php b/settings/log.php index 946f2b6f8e..ddbf72c443 100644 --- a/settings/log.php +++ b/settings/log.php @@ -28,7 +28,7 @@ OC_Util::addStyle( "settings", "settings" ); OC_Util::addScript( "settings", "apps" ); OC_App::setActiveNavigationEntry( "core_log" ); -$entries=OC_Log::getEntries(); +$entries=OC_Log_Owncloud::getEntries(); OC_Util::addScript('settings','log'); OC_Util::addStyle('settings','settings'); From a7438189f315288c5e57bbf3bfb59a37c896cd6c Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 30 Mar 2012 23:31:05 +0200 Subject: [PATCH 146/302] Move more from base init to separate functions --- lib/base.php | 64 ++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/lib/base.php b/lib/base.php index b031572f17..e9788f54b6 100644 --- a/lib/base.php +++ b/lib/base.php @@ -229,6 +229,39 @@ class OC{ } } + public static function initTemplateEngine() { + // if the formfactor is not yet autodetected do the autodetection now. For possible forfactors check the detectFormfactor documentation + if(!isset($_SESSION['formfactor'])){ + $_SESSION['formfactor']=OC::detectFormfactor(); + } + // allow manual override via GET parameter + if(isset($_GET['formfactor'])){ + $_SESSION['formfactor']=$_GET['formfactor']; + } + + // Add the stuff we need always + OC_Util::addScript( "jquery-1.6.4.min" ); + OC_Util::addScript( "jquery-ui-1.8.16.custom.min" ); + OC_Util::addScript( "jquery-showpassword" ); + OC_Util::addScript( "jquery.infieldlabel.min" ); + OC_Util::addScript( "jquery-tipsy" ); + OC_Util::addScript( "oc-dialogs" ); + OC_Util::addScript( "js" ); + OC_Util::addScript( "eventsource" ); + OC_Util::addScript( "config" ); + //OC_Util::addScript( "multiselect" ); + OC_Util::addScript('search','result'); + OC_Util::addStyle( "styles" ); + OC_Util::addStyle( "multiselect" ); + OC_Util::addStyle( "jquery-ui-1.8.16.custom" ); + OC_Util::addStyle( "jquery-tipsy" ); + } + + public static function initSession() { + ini_set('session.cookie_httponly','1;'); + session_start(); + } + public static function init(){ // register autoloader spl_autoload_register(array('OC','autoload')); @@ -272,35 +305,8 @@ class OC{ self::checkSSL(); self::checkUpgrade(); - ini_set('session.cookie_httponly','1;'); - session_start(); - - // if the formfactor is not yet autodetected do the autodetection now. For possible forfactors check the detectFormfactor documentation - if(!isset($_SESSION['formfactor'])){ - $_SESSION['formfactor']=OC::detectFormfactor(); - } - // allow manual override via GET parameter - if(isset($_GET['formfactor'])){ - $_SESSION['formfactor']=$_GET['formfactor']; - } - - - // Add the stuff we need always - OC_Util::addScript( "jquery-1.6.4.min" ); - OC_Util::addScript( "jquery-ui-1.8.16.custom.min" ); - OC_Util::addScript( "jquery-showpassword" ); - OC_Util::addScript( "jquery.infieldlabel.min" ); - OC_Util::addScript( "jquery-tipsy" ); - OC_Util::addScript( "oc-dialogs" ); - OC_Util::addScript( "js" ); - OC_Util::addScript( "eventsource" ); - OC_Util::addScript( "config" ); - //OC_Util::addScript( "multiselect" ); - OC_Util::addScript('search','result'); - OC_Util::addStyle( "styles" ); - OC_Util::addStyle( "multiselect" ); - OC_Util::addStyle( "jquery-ui-1.8.16.custom" ); - OC_Util::addStyle( "jquery-tipsy" ); + self::initSession(); + self::initTemplateEngine(); $errors=OC_Util::checkServer(); if(count($errors)>0) { From 3300d6ea532a973e987c7aeeef1af63a60487c58 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 30 Mar 2012 23:33:36 +0200 Subject: [PATCH 147/302] checkUpgrade has to be after template initialization The error path of checkUpgrade uses the template --- lib/base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/base.php b/lib/base.php index e9788f54b6..22f7f4ea48 100644 --- a/lib/base.php +++ b/lib/base.php @@ -303,10 +303,10 @@ class OC{ self::checkInstalled(); self::checkSSL(); - self::checkUpgrade(); self::initSession(); self::initTemplateEngine(); + self::checkUpgrade(); $errors=OC_Util::checkServer(); if(count($errors)>0) { From 20fc23c82bbcaff56caafe6a6cc0ef15db9b2bf8 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 30 Mar 2012 23:40:16 +0200 Subject: [PATCH 148/302] Move logfile determination to init function --- lib/log/owncloud.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/log/owncloud.php b/lib/log/owncloud.php index 6df346e9b1..5e14320556 100644 --- a/lib/log/owncloud.php +++ b/lib/log/owncloud.php @@ -27,10 +27,14 @@ */ class OC_Log_Owncloud { + static protected $logFile; + /** * Init class data */ public static function init() { + $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); + self::$logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); } /** @@ -42,10 +46,8 @@ class OC_Log_Owncloud { public static function write($app, $message, $level) { $minLevel=OC_Config::getValue( "loglevel", 2 ); if($level>=$minLevel){ - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); - $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); $entry=array('app'=>$app, 'message'=>$message, 'level'=>$level,'time'=>time()); - $fh=fopen($logFile, 'a'); + $fh=fopen(self::$logFile, 'a'); fwrite($fh, json_encode($entry)."\n"); fclose($fh); } @@ -58,10 +60,9 @@ class OC_Log_Owncloud { * @return array */ public static function getEntries($limit=50, $offset=0){ - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); - $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); + self::init(); $entries=array(); - if(!file_exists($logFile)) { + if(!file_exists(self::$logFile)) { return array(); } $contents=file($logFile); From 71b70bb05f2c3a90a51d70c78c68332111400cf5 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 30 Mar 2012 23:40:29 +0200 Subject: [PATCH 149/302] Fix HTML, misspelled span close tag --- files/templates/index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/templates/index.php b/files/templates/index.php index 418a170fec..f591d066d8 100644 --- a/files/templates/index.php +++ b/files/templates/index.php @@ -63,12 +63,12 @@

- t('Files are being scanned, please wait.');?> + t('Files are being scanned, please wait.');?>

- t('Current scanning');?> + t('Current scanning');?>

- \ No newline at end of file + From f9f91a08b47c0a086c43966b1ecdf87185ceef83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Sat, 31 Mar 2012 02:42:41 +0200 Subject: [PATCH 150/302] webfinger reimplementation started --- apps/remoteStorage/appinfo/info.xml | 2 +- apps/remoteStorage/appinfo/webfinger.php | 6 +++ apps/user_webfinger/.htaccess | 3 -- apps/user_webfinger/appinfo/info.xml | 6 +-- apps/user_webfinger/host-meta | 1 - apps/user_webfinger/host-meta.php | 16 ------- apps/user_webfinger/webfinger.php | 57 +++++++++++++++++------- 7 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 apps/remoteStorage/appinfo/webfinger.php delete mode 100644 apps/user_webfinger/.htaccess delete mode 100644 apps/user_webfinger/host-meta delete mode 100644 apps/user_webfinger/host-meta.php diff --git a/apps/remoteStorage/appinfo/info.xml b/apps/remoteStorage/appinfo/info.xml index 121587795d..1ab55e8c09 100644 --- a/apps/remoteStorage/appinfo/info.xml +++ b/apps/remoteStorage/appinfo/info.xml @@ -3,7 +3,7 @@ remoteStorage remoteStorage compatibility Enables your users to use ownCloud as their remote storage for unhosted applications. - 0.5 + 0.6 AGPL or MIT Michiel de Jong 2 diff --git a/apps/remoteStorage/appinfo/webfinger.php b/apps/remoteStorage/appinfo/webfinger.php new file mode 100644 index 0000000000..bb3fe1681b --- /dev/null +++ b/apps/remoteStorage/appinfo/webfinger.php @@ -0,0 +1,6 @@ + + diff --git a/apps/user_webfinger/.htaccess b/apps/user_webfinger/.htaccess deleted file mode 100644 index 4d4d2e9c58..0000000000 --- a/apps/user_webfinger/.htaccess +++ /dev/null @@ -1,3 +0,0 @@ -RewriteEngine On -RewriteBase / -RewriteRule host-meta$ \/\.well-known\/host-meta\.php [L] diff --git a/apps/user_webfinger/appinfo/info.xml b/apps/user_webfinger/appinfo/info.xml index 55cf2cf220..d47fb723a3 100644 --- a/apps/user_webfinger/appinfo/info.xml +++ b/apps/user_webfinger/appinfo/info.xml @@ -2,9 +2,9 @@ user_webfinger Webfinger - Provide WebFinger for all users so they get a user address like user@owncloudinstance which can be used for unhosted applications. If you don't run ownCloud in the root of your domain, for instance if you run it on example.com/owncloud/, then make sure you link example.com/.well-known/ to example.com/owncloud/apps/user_webfinger/ - by running something like "ln -s /var/www/owncloud/apps/user_webfinger /var/www/.well-known". Only enable this app if you run this ownCloud installation on a public web address, not if you run it on an intranet or on localhost. - 0.2 + Provide WebFinger for all users so they get a user address like user@owncloudinstance which can be used for external applications. Other apps can provide information for webfinger requests, such as remoteStorage compatibility. + 0.3 AGPL or MIT - Michiel de Jong + Michiel de Jong, Florian Hülsmann 2 diff --git a/apps/user_webfinger/host-meta b/apps/user_webfinger/host-meta deleted file mode 100644 index dfaf363614..0000000000 --- a/apps/user_webfinger/host-meta +++ /dev/null @@ -1 +0,0 @@ -please run 'a2enmod rewrite' on your server, set 'AllowOverride All' for /var/www in /etc/apache2/sites-enabled/000-default or equivalent, and then run '/etc/init.d/apache2 restart' diff --git a/apps/user_webfinger/host-meta.php b/apps/user_webfinger/host-meta.php deleted file mode 100644 index ac577cf9a0..0000000000 --- a/apps/user_webfinger/host-meta.php +++ /dev/null @@ -1,16 +0,0 @@ - -?xml version="1.0" encoding="UTF-8"?> - - - - - - diff --git a/apps/user_webfinger/webfinger.php b/apps/user_webfinger/webfinger.php index 5c2a24aa07..ecbfeed8e4 100644 --- a/apps/user_webfinger/webfinger.php +++ b/apps/user_webfinger/webfinger.php @@ -1,41 +1,68 @@ /apps/myApp/profile.php?user="> + * + * + '* but can also use complex database queries to generate the webfinger result + **/ // calculate the documentroot // modified version of the one in lib/base.php that takes the .well-known symlink into account -$DOCUMENTROOT=realpath($_SERVER['DOCUMENT_ROOT']); +/*$DOCUMENTROOT=realpath($_SERVER['DOCUMENT_ROOT']); $SERVERROOT=str_replace("\\",'/',dirname(dirname(dirname(dirname(__FILE__))))); $SUBURI=substr(realpath($_SERVER["SCRIPT_FILENAME"]),strlen($SERVERROOT)); $WEBROOT=substr($SUBURI,0,-34); +*/ +require_once('../../lib/base.php'); +$id = $_GET['q']; if($_GET['q']) { $bits = explode('@', $_GET['q']); $userName = $bits[0]; } else { + $id = ''; $userName = ''; } if(substr($userName, 0, 5) == 'acct:') { $userName = substr($userName, 5); } if(isset($_SERVER['HTTPS'])) { - $baseAddress = 'https://'.$_SERVER['SERVER_NAME'].'/apps/remoteStorage/'; + $baseAddress = 'https://'; } else { - $baseAddress = 'http://'.$_SERVER['SERVER_NAME'].'/apps/remoteStorage/'; + $baseAddress = 'http://'; } +$baseAddress .= $_SERVER['SERVER_NAME'].OC::$WEBROOT; +define('WF_USER', $userName); +define('WF_ADDRESS', $id); +define('WF_ROOT', $baseAddress); echo "<"; ?> ?xml version="1.0" encoding="UTF-8"?> - - + + acct: + From ad495a92180da7e5dc369f7d8606a525dfe8a4e5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 31 Mar 2012 16:10:29 +0200 Subject: [PATCH 151/302] fix potential problem when using multiply eventsource's --- core/js/eventsource.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/js/eventsource.js b/core/js/eventsource.js index dece1a69d0..34bce60c24 100644 --- a/core/js/eventsource.js +++ b/core/js/eventsource.js @@ -33,8 +33,12 @@ */ OC.EventSource=function(src,data){ var dataStr=''; - for(name in data){ - dataStr+=name+'='+encodeURIComponent(data[name])+'&'; + this.typelessListeners=[]; + this.listeners={}; + if(data){ + for(name in data){ + dataStr+=name+'='+encodeURIComponent(data[name])+'&'; + } } if(!this.useFallBack && typeof EventSource !='undefined'){ this.source=new EventSource(src+'?'+dataStr); @@ -42,7 +46,7 @@ OC.EventSource=function(src,data){ for(var i=0;i Date: Sat, 31 Mar 2012 16:20:32 +0200 Subject: [PATCH 152/302] make sure output buffering is dissabled when using eventsource --- lib/eventsource.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/eventsource.php b/lib/eventsource.php index 523f72403c..dc28616c2d 100644 --- a/lib/eventsource.php +++ b/lib/eventsource.php @@ -32,6 +32,7 @@ class OC_EventSource{ private $fallBackId=0; public function __construct(){ + @ob_end_clean(); header('Cache-Control: no-cache'); $this->fallback=isset($_GET['fallback']) and $_GET['fallback']=='true'; if($this->fallback){ From 9d2f8aa717826c1db8f8ec5f49a4128af3651edb Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 31 Mar 2012 16:24:53 +0200 Subject: [PATCH 153/302] send more progress updates when scanning large folders --- lib/filecache.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/filecache.php b/lib/filecache.php index 86d865ed9f..59b0fb1b50 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -469,6 +469,10 @@ class OC_FileCache{ * @param string root (optionak) */ public static function scan($path,$eventSource=false,&$count=0,$root=''){ + if($eventSource){ + $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); + } + $lastSend=$count; if(!$root){ $view=OC_Filesystem::getView(); }else{ @@ -482,13 +486,15 @@ class OC_FileCache{ if($filename != '.' and $filename != '..'){ $file=$path.'/'.$filename; if($view->is_dir($file.'/')){ - if($eventSource){ - $eventSource->send('scanning',array('file'=>$file,'count'=>$count)); - } self::scan($file,$eventSource,$count,$root); }else{ $totalSize+=self::scanFile($file,$root); $count++; + if($count>$lastSend+25){ + if($eventSource){ + $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); + } + } } } } From ebc7a6a0a6249213122bf97104f850d2ce6b7c1a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 31 Mar 2012 16:28:22 +0200 Subject: [PATCH 154/302] dont send to much when scanning large folders --- lib/filecache.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/filecache.php b/lib/filecache.php index 59b0fb1b50..4a4183cbdb 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -490,10 +490,9 @@ class OC_FileCache{ }else{ $totalSize+=self::scanFile($file,$root); $count++; - if($count>$lastSend+25){ - if($eventSource){ - $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); - } + if($count>$lastSend+25 and $eventSource){ + $lastSend=$count; + $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); } } } From 2f68b084919437013cc6d977c0f077e541cf83f9 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 31 Mar 2012 16:40:42 +0200 Subject: [PATCH 155/302] fix eventsource for ie --- core/js/eventsource.js | 2 +- lib/eventsource.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/js/eventsource.js b/core/js/eventsource.js index 34bce60c24..08259e02ca 100644 --- a/core/js/eventsource.js +++ b/core/js/eventsource.js @@ -68,7 +68,7 @@ OC.EventSource=function(src,data){ OC.EventSource.fallBackSources=[]; OC.EventSource.iframeCount=0;//number of fallback iframes OC.EventSource.fallBackCallBack=function(id,type,data){ - OC.EventSource.fallBackSources[id].fallBackCallBack(type,JSON.parse(data)); + OC.EventSource.fallBackSources[id].fallBackCallBack(type,data); } OC.EventSource.prototype={ typelessListeners:[], diff --git a/lib/eventsource.php b/lib/eventsource.php index dc28616c2d..cf10660b94 100644 --- a/lib/eventsource.php +++ b/lib/eventsource.php @@ -59,7 +59,7 @@ class OC_EventSource{ $type=null; } if($this->fallback){ - $response=''.PHP_EOL; + $response=''.PHP_EOL; echo $response; }else{ if($type){ From 4e327295c65b25fc5d6ceec5a8242eecf57b94e2 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Sun, 1 Apr 2012 00:30:52 +0200 Subject: [PATCH 156/302] adding callback when ok click on alert dialog --- core/js/oc-dialogs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index 35d0a0c5c4..17c987ae87 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -115,7 +115,7 @@ OCdialogs = { var f; switch(dialog_type) { case OCdialogs.ALERT_DIALOG: - f = function(){$(c_id).dialog('close'); }; + f = function(){$(c_id).dialog('close'); callback();}; break; case OCdialogs.PROMPT_DIALOG: f = function(){OCdialogs.prompt_ok_handler(callback, c_id)}; From d20eea9761238e0569f538c6f8b1bb553068bf7b Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 31 Mar 2012 22:41:43 +0000 Subject: [PATCH 157/302] Use ajax to download file, OC_Dialogs for errors --- apps/user_migrate/ajax/export.php | 63 ++++++++++++++++++++++++ apps/user_migrate/appinfo/app.php | 1 + apps/user_migrate/js/export.js | 27 ++++++++++ apps/user_migrate/settings.php | 22 ++------- apps/user_migrate/templates/settings.php | 14 +++--- lib/migrate.php | 4 +- 6 files changed, 102 insertions(+), 29 deletions(-) create mode 100644 apps/user_migrate/ajax/export.php create mode 100644 apps/user_migrate/js/export.js diff --git a/apps/user_migrate/ajax/export.php b/apps/user_migrate/ajax/export.php new file mode 100644 index 0000000000..ef947c610f --- /dev/null +++ b/apps/user_migrate/ajax/export.php @@ -0,0 +1,63 @@ +. + * + */ +// Init owncloud +require_once('../../../lib/base.php'); + +// Check if we are a user +OC_JSON::checkLoggedIn(); +OC_Util::checkAppEnabled('user_migrate'); + OC_JSON::error(); + die(); +// Which operation +if( $_GET['operation']=='create' ){ +$uid = !empty( $_POST['uid'] ) ? $_POST['uid'] : OC_User::getUser(); +if( $uid != OC_User::getUser() ){ + // Needs to be admin to export someone elses account + OC_JSON::error(); + die(); +} +// Create the export zip +if( !$path = OC_Migrate::export( $uid ) ){ + // Error + OC_JSON::error(); + die(); +} else { + // Save path in session + $_SESSION['ocuserexportpath'] = $path; +} +OC_JSON::success(); +die(); +} else if( $_GET['operation']=='download' ){ + // Download the export + $path = isset( $_SESSION['ocuserexportpath'] ) ? $_SESSION['ocuserexportpath'] : false; + if( !$path ){ + die(); + } + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($path)); + header("Content-Length: " . filesize($path)); + @ob_end_clean(); + readfile($path); + unlink( $path ); + $_SESSION['ocuserexportpath'] = ''; +} diff --git a/apps/user_migrate/appinfo/app.php b/apps/user_migrate/appinfo/app.php index 18ea8f52b2..a59b6dd705 100644 --- a/apps/user_migrate/appinfo/app.php +++ b/apps/user_migrate/appinfo/app.php @@ -23,6 +23,7 @@ OC_APP::registerPersonal( 'user_migrate', 'settings' ); OC_APP::registerAdmin( 'user_migrate', 'admin' ); +OC_Util::addScript( 'user_migrate', 'export'); // add settings page to navigation $entry = array( diff --git a/apps/user_migrate/js/export.js b/apps/user_migrate/js/export.js new file mode 100644 index 0000000000..0e1e396f65 --- /dev/null +++ b/apps/user_migrate/js/export.js @@ -0,0 +1,27 @@ +$(document).ready(function(){ + // Do the export + $('#exportbtn').click(function(){ + // Show loader + $('.loading').show(); + $.getJSON( + OC.filePath('user_migrate','ajax','export.php'), + {operation:'create'}, + function(result){ + if(result.status == 'success'){ + // Download the file + window.location = OC.filePath('user_migrate','ajax','export.php?operation=download') ; + $('.loading').hide(); + $('#exportbtn').val(t('user_migrate', 'Export')); + } else { + // Cancel loading + $('#exportbtn').html('Failed'); + // Show Dialog + OC.dialogs.alert(t('user_migrate', 'Something went wrong while the export file was being generated'), t('user_migrate', 'An error has occurred'), function(){ + $('#exportbtn').html(t('user_migrate', 'Export')+''); + }); + } + } + // End ajax + ); + }); +}); \ No newline at end of file diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php index 38eee990b4..62f347b355 100644 --- a/apps/user_migrate/settings.php +++ b/apps/user_migrate/settings.php @@ -24,22 +24,6 @@ */ OC_Util::checkAppEnabled('user_migrate'); -if (isset($_POST['user_export'])) { - // Create the export zip - if( !$path = OC_Migrate::export() ){ - // Error - die('error'); - } else { - // Download it - header("Content-Type: application/zip"); - header("Content-Disposition: attachment; filename=" . basename($path)); - header("Content-Length: " . filesize($path)); - @ob_end_clean(); - readfile($path); - unlink( $path ); - } -} else { - // fill template - $tmpl = new OC_Template('user_migrate', 'settings'); - return $tmpl->fetchPage(); -} \ No newline at end of file +// fill template +$tmpl = new OC_Template('user_migrate', 'settings'); +return $tmpl->fetchPage(); \ No newline at end of file diff --git a/apps/user_migrate/templates/settings.php b/apps/user_migrate/templates/settings.php index 389de563a6..5f4857de5f 100644 --- a/apps/user_migrate/templates/settings.php +++ b/apps/user_migrate/templates/settings.php @@ -1,8 +1,6 @@ -
-
- t('Export your user account');?> -

t('This will create a compressed file that contains your ownCloud account.');?> -

- -
-
\ No newline at end of file +
+ t('Export your user account');?> +

t('This will create a compressed file that contains your ownCloud account.');?> +

+ +
diff --git a/lib/migrate.php b/lib/migrate.php index fe5ac45600..db3852fb83 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -73,12 +73,12 @@ class OC_Migrate{ /** * @breif exports a user, or owncloud instance + * @param optional $uid string user id of user to export if export type is user, defaults to current * @param ootional $type string type of export, defualts to user * @param otional $path string path to zip output folder - * @param optional $uid string user id of user to export if export type is user, defaults to current * @return false on error, path to zip on success */ - public static function export( $type='user', $path=null, $uid=null ){ + public static function export( $uid=null, $type='user', $path=null ){ $datadir = OC_Config::getValue( 'datadirectory' ); // Validate export type $types = array( 'user', 'instance', 'system', 'userfiles' ); From aba3182a7d6c2dc5573a5cca75b2ed5e8abbc3e0 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 31 Mar 2012 22:47:38 +0000 Subject: [PATCH 158/302] Fix loading image after export failure --- apps/user_migrate/js/export.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user_migrate/js/export.js b/apps/user_migrate/js/export.js index 0e1e396f65..2d660b2de6 100644 --- a/apps/user_migrate/js/export.js +++ b/apps/user_migrate/js/export.js @@ -17,7 +17,7 @@ $(document).ready(function(){ $('#exportbtn').html('Failed'); // Show Dialog OC.dialogs.alert(t('user_migrate', 'Something went wrong while the export file was being generated'), t('user_migrate', 'An error has occurred'), function(){ - $('#exportbtn').html(t('user_migrate', 'Export')+''); + $('#exportbtn').html(t('user_migrate', 'Export')+''); }); } } From 3e84d8548296152112c09aa0e4a3a4c02b816494 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 31 Mar 2012 22:50:57 +0000 Subject: [PATCH 159/302] remove debug --- apps/user_migrate/ajax/export.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/user_migrate/ajax/export.php b/apps/user_migrate/ajax/export.php index ef947c610f..fac96577fa 100644 --- a/apps/user_migrate/ajax/export.php +++ b/apps/user_migrate/ajax/export.php @@ -26,8 +26,6 @@ require_once('../../../lib/base.php'); // Check if we are a user OC_JSON::checkLoggedIn(); OC_Util::checkAppEnabled('user_migrate'); - OC_JSON::error(); - die(); // Which operation if( $_GET['operation']=='create' ){ $uid = !empty( $_POST['uid'] ) ? $_POST['uid'] : OC_User::getUser(); From d01b78a4b486860ab7110677e9969a37ee2a832f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 1 Apr 2012 00:55:12 +0200 Subject: [PATCH 160/302] prevent an possible xss exploit --- core/templates/login.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/templates/login.php b/core/templates/login.php index 82222c8212..4ba92221a7 100644 --- a/core/templates/login.php +++ b/core/templates/login.php @@ -7,7 +7,7 @@

- autocomplete="off" required /> + autocomplete="off" required />

From ce89ff15aaa971ec930196db64e7437c2fe47fa5 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 31 Mar 2012 23:20:08 +0000 Subject: [PATCH 161/302] Remove db tmp file after export creation --- lib/migration/content.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/migration/content.php b/lib/migration/content.php index d25b5af293..a9fa05596b 100644 --- a/lib/migration/content.php +++ b/lib/migration/content.php @@ -28,7 +28,7 @@ class OC_Migration_Content{ private $zip=false; // Holds the MDB2 object - private $db=false; + private $db=null; // Holds an array of tmpfiles to delete after zip creation private $tmpfiles=false; @@ -38,11 +38,18 @@ class OC_Migration_Content{ * @param optional $db a MDB2 database object (required for exporttype user) * @return bool */ - public function __construct( $zip, $db=false ){ + public function __construct( $zip, $db=null ){ $this->zip = $zip; $this->db = $db; + if( !is_null( $db ) ){ + // Get db path + $db = $this->db->getDatabase(); + $this->tmpfiles[] = $db; + OC_Log::write('user-migrate',$db, OC_Log::INFO); + } + } // @breif prepares the db From daf742c086fefab9d715be3308088e626de1216c Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 31 Mar 2012 23:55:41 +0000 Subject: [PATCH 162/302] Fix owncloud log --- apps/contacts/appinfo/migrate.php | 68 +++++++++++++++++++++++++++++++ lib/log/owncloud.php | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 apps/contacts/appinfo/migrate.php diff --git a/apps/contacts/appinfo/migrate.php b/apps/contacts/appinfo/migrate.php new file mode 100644 index 0000000000..a6c6bc20fa --- /dev/null +++ b/apps/contacts/appinfo/migrate.php @@ -0,0 +1,68 @@ +'contacts_addressbooks', + 'matchcol'=>'userid', + 'matchval'=>$this->uid, + 'idcol'=>'id' + ); + $ids = $this->content->copyRows( $options ); + + $options = array( + 'table'=>'contacts_cards', + 'matchcol'=>'addressbookid', + 'matchval'=>$ids + ); + + // Export tags + $ids2 = $this->content->copyRows( $options ); + + // If both returned some ids then they worked + if( is_array( $ids ) && is_array( $ids2 ) ) + { + return true; + } else { + return false; + } + + } + + // Import function for bookmarks + function import( ){ + switch( $this->appinfo->version ){ + default: + // All versions of the app have had the same db structure, so all can use the same import function + $query = $this->content->prepare( "SELECT * FROM contacts_addressbooks WHERE userid LIKE ?" ); + $results = $query->execute( array( $this->olduid ) ); + $idmap = array(); + while( $row = $results->fetchRow() ){ + // Import each bookmark, saving its id into the map + $query = OC_DB::prepare( "INSERT INTO *PREFIX*contacts_addressbooks (`userid`, `displayname`, `uri`, `description`, `ctag`) VALUES (?, ?, ?, ?, ?)" ); + $query->execute( array( $this->uid, $row['displayname'], $row['uri'], $row['description'], $row['ctag'] ) ); + // Map the id + $idmap[$row['id']] = OC_DB::insertid(); + } + // Now tags + foreach($idmap as $oldid => $newid){ + $query = $this->content->prepare( "SELECT * FROM contacts_cards WHERE addressbookid LIKE ?" ); + $results = $query->execute( array( $oldid ) ); + while( $row = $results->fetchRow() ){ + // Import the tags for this bookmark, using the new bookmark id + $query = OC_DB::prepare( "INSERT INTO *PREFIX*contacts_cards (`addressbookid`, `fullname`, `carddata`, `uri`, `lastmodified`) VALUES (?, ?, ?, ?, ?)" ); + $query->execute( array( $newid, $row['fullname'], $row['carddata'], $row['uri'], $row['lastmodified'] ) ); + } + } + // All done! + break; + } + + return true; + } + +} + +// Load the provider +new OC_Migration_Provider_Contacts( 'contacts' ); \ No newline at end of file diff --git a/lib/log/owncloud.php b/lib/log/owncloud.php index 5e14320556..0ed3051013 100644 --- a/lib/log/owncloud.php +++ b/lib/log/owncloud.php @@ -65,7 +65,7 @@ class OC_Log_Owncloud { if(!file_exists(self::$logFile)) { return array(); } - $contents=file($logFile); + $contents=file(self::$logFile); if(!$contents) {//error while reading log return array(); } From ffbd72bbcf775ac31a43b958ec1e8eaddf0f8356 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 1 Apr 2012 00:07:39 +0000 Subject: [PATCH 163/302] Fix user app data export --- lib/migration/content.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/migration/content.php b/lib/migration/content.php index a9fa05596b..d304051f3e 100644 --- a/lib/migration/content.php +++ b/lib/migration/content.php @@ -47,7 +47,6 @@ class OC_Migration_Content{ // Get db path $db = $this->db->getDatabase(); $this->tmpfiles[] = $db; - OC_Log::write('user-migrate',$db, OC_Log::INFO); } } @@ -110,7 +109,8 @@ class OC_Migration_Content{ foreach( $options['matchval'] as $matchval ){ // Run the query for this match value (where x = y value) - $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?" ); + $sql = "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?"; + $query = OC_DB::prepare( $sql ); $results = $query->execute( array( $matchval ) ); $newreturns = $this->insertData( $results, $options ); $return = array_merge( $return, $newreturns ); @@ -118,7 +118,8 @@ class OC_Migration_Content{ } else { // Just get everything - $query = OC_DB::prepare( "SELECT * FROM *PREFIX*" . $options['table'] ); + $sql = "SELECT * FROM *PREFIX*" . $options['table']; + $query = OC_DB::prepare( $sql ); $results = $query->execute(); $return = $this->insertData( $results, $options ); @@ -136,6 +137,7 @@ class OC_Migration_Content{ */ private function insertData( $data, $options ){ $return = array(); + // Foreach row of data to insert while( $row = $data->fetchRow() ){ // Now save all this to the migration.db foreach($row as $field=>$value){ @@ -166,6 +168,8 @@ class OC_Migration_Content{ $return[] = reset($row); } } + $fields = ''; + $values = ''; } return $return; } From eba6a65908ab848741a29467a2c054818e3c740c Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 1 Apr 2012 00:25:47 +0000 Subject: [PATCH 164/302] try to use transactions to replace db --- apps/admin_export/settings.php | 2 +- lib/db.php | 9 +++------ lib/migrate.php | 10 ++++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php index 269147a3bc..719bedb66e 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_export/settings.php @@ -28,7 +28,7 @@ OC_Util::checkAppEnabled('admin_export'); // Export? if (isset($_POST['admin_export'])) { // Create the export zip - if( !$path = OC_Migrate::export( $_POST['export_type'] ) ){ + if( !$path = OC_Migrate::export( null, $_POST['export_type'] ) ){ // Error die('error'); } else { diff --git a/lib/db.php b/lib/db.php index a7b7ae75da..9c46a40add 100644 --- a/lib/db.php +++ b/lib/db.php @@ -489,7 +489,7 @@ class OC_DB { public static function replaceDB( $file ){ $apps = OC_App::getAllApps(); - + self::beginTransaction(); // Delete the old tables self::removeDBStructure( OC::$SERVERROOT . '/db_structure.xml' ); @@ -501,11 +501,8 @@ class OC_DB { } // Create new tables - if( self::createDBFromStructure( $file ) ){ - return true; - } else { - return false; - } + self::createDBFromStructure( $file ); + self::commit(); } diff --git a/lib/migrate.php b/lib/migrate.php index db3852fb83..815333b4d8 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -257,28 +257,30 @@ class OC_Migrate{ return $appsimported; break; case 'instance': + /* // Check for new data dir and dbexport before doing anything // TODO - /* + // Delete current data folder. OC_Log::write( 'migration', "Deleting current data dir", OC_Log::INFO ); - if( self::unlink_r( $datadir, false ) ){ + if( !self::unlink_r( $datadir, false ) ){ OC_Log::write( 'migration', 'Failed to delete the current data dir', OC_Log::ERROR ); return false; } // Copy over data - if( !self::copy_r( $extractname . 'data', $datadir ) ){ + if( !self::copy_r( $extractpath . 'userdata', $datadir ) ){ OC_Log::write( 'migration', 'Failed to copy over data directory', OC_Log::ERROR ); return false; } - */ + // Import the db if( !OC_DB::replaceDB( $extractpath . 'dbexport.xml' ) ){ return false; } // Done return true; + */ break; } From 6bb48b2731185dd93ad1a1edc52f33725cd99061 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 1 Apr 2012 02:38:26 -0400 Subject: [PATCH 165/302] Check file handle exists before trying to read file --- lib/filesystemview.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/filesystemview.php b/lib/filesystemview.php index 39e47975b2..a3736f1976 100644 --- a/lib/filesystemview.php +++ b/lib/filesystemview.php @@ -137,13 +137,16 @@ class OC_FilesystemView { } public function readfile($path){ $handle=$this->fopen($path,'r'); - $chunkSize = 1024*1024;// 1 MB chunks - while (!feof($handle)) { - echo fread($handle, $chunkSize); - @ob_flush(); - flush(); + if ($handle) { + $chunkSize = 1024*1024;// 1 MB chunks + while (!feof($handle)) { + echo fread($handle, $chunkSize); + @ob_flush(); + flush(); + } + return $this->filesize($path); } - return $this->filesize($path); + return false; } public function is_readable($path){ return $this->basicOperation('is_readable',$path); From 6545e4878715c10d85a72b1cdb3f7cef73d7e383 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Sun, 1 Apr 2012 11:20:12 +0200 Subject: [PATCH 166/302] Show the different editions to the user. Used in the status call, on the personal settings page and in the updater to update to the next available version from the same edition. --- lib/updater.php | 1 + lib/util.php | 8 ++++++++ settings/templates/personal.php | 2 +- status.php | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/updater.php b/lib/updater.php index 57623797ae..196822ac35 100644 --- a/lib/updater.php +++ b/lib/updater.php @@ -36,6 +36,7 @@ class OC_Updater{ $version['installed']=OC_Config::getValue('installedat'); $version['updated']=OC_Appconfig::getValue('core', 'lastupdatedat', OC_Config::getValue( 'lastupdatedat')); $version['updatechannel']='stable'; + $version['edition']=OC_Util::getEditionString(); $versionstring=implode('x',$version); //fetch xml data from updater diff --git a/lib/util.php b/lib/util.php index 529b6d958f..722b7404d0 100644 --- a/lib/util.php +++ b/lib/util.php @@ -77,6 +77,14 @@ class OC_Util { return '3'; } + /** + * get the current installed edition of ownCloud. There is the community edition that just returns an empty string and the enterprise edition that returns "Enterprise". + * @return string + */ + public static function getEditionString(){ + return ''; + } + /** * add a javascript file * diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 57731d979d..d40da7eb77 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -50,7 +50,7 @@ };?>

- ownCloud
+ ownCloud
developed by the ownCloud community

source code licensed freely under AGPL diff --git a/status.php b/status.php index 94c8cfce84..81f339fa53 100644 --- a/status.php +++ b/status.php @@ -26,7 +26,7 @@ $RUNTIME_NOAPPS = TRUE; //no apps, yet require_once('lib/base.php'); if(OC_Config::getValue('installed')==1) $installed='true'; else $installed='false'; -$values=array('installed'=>$installed,'version'=>implode('.',OC_Util::getVersion()),'versionstring'=>OC_Util::getVersionString()); +$values=array('installed'=>$installed,'version'=>implode('.',OC_Util::getVersion()),'versionstring'=>OC_Util::getVersionString(),'edition'=>OC_Util::getEditionString()); echo(json_encode($values)); From 1b8a644c31a83279e9829aac74fbbc843d49f65d Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Sun, 1 Apr 2012 16:06:08 +0200 Subject: [PATCH 167/302] update outdated README --- README | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README b/README index 4d4be2728e..77379a4645 100644 --- a/README +++ b/README @@ -3,10 +3,11 @@ A personal cloud which runs on your own server. http://ownCloud.org -Installation instructions: http://owncloud.org/support/setup-and-installation/ -Source code: http://gitorious.org/owncloud +Installation instructions: http://owncloud.org/support +Source code: http://gitorious.org/owncloud Mailing list: http://mail.kde.org/mailman/listinfo/owncloud IRC channel: http://webchat.freenode.net/?channels=owncloud Diaspora: https://joindiaspora.com/u/owncloud Identi.ca: http://identi.ca/owncloud + From b758725bf7fad960e971adfeb826596e02673244 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Sun, 1 Apr 2012 17:02:32 +0200 Subject: [PATCH 168/302] =?UTF-8?q?Try=20to=20configure=20php=20to=20enabl?= =?UTF-8?q?e=20big=20file=20uploads.=20This=20doesn=C2=B4t=20work=20always?= =?UTF-8?q?=20depending=20on=20the=20webserver=20and=20php=20configuration?= =?UTF-8?q?.=20Let=C2=B4s=20try=20to=20overwrite=20some=20defaults=20anywa?= =?UTF-8?q?ys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/base.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/base.php b/lib/base.php index 22f7f4ea48..a4a94e8696 100644 --- a/lib/base.php +++ b/lib/base.php @@ -277,6 +277,24 @@ class OC{ date_default_timezone_set('Europe/Berlin'); ini_set('arg_separator.output','&'); + //try to configure php to enable big file uploads. + //this doesn´t work always depending on the webserver and php configuration. + //Let´s try to overwrite some defaults anyways + + //try to set the maximum execution time to 60min + @set_time_limit(3600); + @ini_set('max_execution_time',3600); + @ini_set('max_input_time',3600); + + //try to set the maximum filesize to 10G + @ini_set('upload_max_filesize','10G'); + @ini_set('post_max_size','10G'); + @ini_set('file_uploads','50'); + + //try to set the session lifetime to 60min + @ini_set('gc_maxlifetime','3600'); + + //set http auth headers for apache+php-cgi work around if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) { From 39e8981bc2ef098a020476a9d94a4b0cc4d9e1c9 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 1 Apr 2012 17:31:44 +0200 Subject: [PATCH 169/302] oc_db is not pdo also pgsql does not like double quotes --- lib/app.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/app.php b/lib/app.php index 6c882963a0..625ef88967 100755 --- a/lib/app.php +++ b/lib/app.php @@ -103,9 +103,9 @@ class OC_App{ */ public static function getEnabledApps(){ $apps=array(); - $query = OC_DB::prepare( 'SELECT appid FROM *PREFIX*appconfig WHERE configkey = "enabled" AND configvalue="yes"' ); - $query->execute(); - while($row=$query->fetchRow()){ + $query = OC_DB::prepare( 'SELECT appid FROM *PREFIX*appconfig WHERE configkey = \'enabled\' AND configvalue=\'yes\'' ); + $result=$query->execute(); + while($row=$result->fetchRow()){ $apps[]=$row['appid']; } return $apps; @@ -452,7 +452,7 @@ class OC_App{ */ public static function getAppVersions(){ $versions=array(); - $query = OC_DB::prepare( 'SELECT appid, configvalue FROM *PREFIX*appconfig WHERE configkey = "installed_version"' ); + $query = OC_DB::prepare( 'SELECT appid, configvalue FROM *PREFIX*appconfig WHERE configkey = \'installed_version\'' ); $result = $query->execute(); while($row = $result->fetchRow()){ $versions[$row['appid']]=$row['configvalue']; From cce59df2ae399bc43f1c11a3162b4855de70a1bc Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 1 Apr 2012 19:20:59 +0200 Subject: [PATCH 170/302] the core apps don't have types --- lib/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app.php b/lib/app.php index 625ef88967..7d5e8fa91c 100755 --- a/lib/app.php +++ b/lib/app.php @@ -55,7 +55,7 @@ class OC_App{ // Our very own core apps are hardcoded foreach( array('files', 'settings') as $app ){ - if(is_null($types) or self::isType($app,$types)){ + if(is_null($types)){ require( $app.'/appinfo/app.php' ); } } From ff4b0c4d7f6b422c9426205897c015b497429b03 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 1 Apr 2012 13:30:41 -0400 Subject: [PATCH 171/302] Move writable check into local filestorage so shared files can be renamed --- lib/filestorage/local.php | 4 ++++ lib/filesystemview.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/filestorage/local.php b/lib/filestorage/local.php index 688501aee9..bd757f52ce 100644 --- a/lib/filestorage/local.php +++ b/lib/filestorage/local.php @@ -86,6 +86,10 @@ class OC_Filestorage_Local extends OC_Filestorage{ return $this->delTree($path); } public function rename($path1,$path2){ + if (!$this->is_writable($path1)) { + OC_Log::write('core','unable to rename, file is not writable : '.$path1,OC_Log::ERROR); + return false; + } if(! $this->file_exists($path1)){ OC_Log::write('core','unable to rename, file does not exists : '.$path1,OC_Log::ERROR); return false; diff --git a/lib/filesystemview.php b/lib/filesystemview.php index a3736f1976..9d530c7ad6 100644 --- a/lib/filesystemview.php +++ b/lib/filesystemview.php @@ -192,7 +192,7 @@ class OC_FilesystemView { return $this->basicOperation('unlink',$path,array('delete')); } public function rename($path1,$path2){ - if(OC_FileProxy::runPreProxies('rename',$path1,$path2) and $this->is_writable($path1) and OC_Filesystem::isValidPath($path2)){ + if(OC_FileProxy::runPreProxies('rename',$path1,$path2) and OC_Filesystem::isValidPath($path2)){ $run=true; OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_rename, array( OC_Filesystem::signal_param_oldpath => $path1 , OC_Filesystem::signal_param_newpath=>$path2, OC_Filesystem::signal_param_run => &$run)); if($run){ From 3e150ff548a8bf3b29729ab9b04a1f38538737f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Mon, 2 Apr 2012 06:31:00 +0200 Subject: [PATCH 172/302] webfinger protocol compatible + minor changes --- apps/remoteStorage/appinfo/webfinger.php | 4 +-- apps/user_webfinger/appinfo/install.php | 18 +++++++++++++- apps/user_webfinger/webfinger.php | 31 +++++++++++++----------- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/apps/remoteStorage/appinfo/webfinger.php b/apps/remoteStorage/appinfo/webfinger.php index bb3fe1681b..7c0ab84605 100644 --- a/apps/remoteStorage/appinfo/webfinger.php +++ b/apps/remoteStorage/appinfo/webfinger.php @@ -1,6 +1,6 @@ + auth="/apps/remoteStorage/auth.php/"> diff --git a/apps/user_webfinger/appinfo/install.php b/apps/user_webfinger/appinfo/install.php index 678d57ae8f..c8d9a42742 100644 --- a/apps/user_webfinger/appinfo/install.php +++ b/apps/user_webfinger/appinfo/install.php @@ -1,4 +1,8 @@ '*', + 'Content-Type' => 'application/xml+xrd' +); $appInfoDir = __DIR__; $thisAppDir = dirname($appInfoDir); $appsDir = dirname($thisAppDir); @@ -17,6 +21,7 @@ if(isset($_SERVER['HTTPS'])) { } $lrddTmpl .= '://' . $serverName . $webRoot . '/apps/user_webfinger/webfinger.php?q={uri}'; $hostMetaPath = $docRoot . '/.well-known/host-meta'; +$hostMetaDir = $docRoot . '/.well-known'; $hostMetaContents = " " . $serverName . " @@ -24,7 +29,7 @@ $hostMetaContents = " Resource Descriptor "; -@mkdir(dirname($hostMetaPath)); +@mkdir($hostMetaDir); $hostMeta = fopen($hostMetaPath, 'w'); if(!$hostMeta) { die("Could not open " . $hostMetaPath . " for writing, please check permissions!"); @@ -33,3 +38,14 @@ if(!fwrite($hostMeta, $hostMetaContents, strlen($hostMetaContents))) { die("Could not write to " . $hostMetaPath . ", please check permissions!"); } fclose($hostMeta); + +// write custom headers into .htaccess: +$htaccess = fopen($hostMetaDir . '/.htaccess', 'w'); +//TODO: check compatibility! +fwrite($htaccess, " +\n"); +foreach($hostMetaHeader as $header => $value) { + fwrite($htaccess, "Header set " . $header . " \"" . $value . "\"\n"); +} +fwrite($htaccess, "\n"); +fclose($htaccess); diff --git a/apps/user_webfinger/webfinger.php b/apps/user_webfinger/webfinger.php index ecbfeed8e4..9ada473ca8 100644 --- a/apps/user_webfinger/webfinger.php +++ b/apps/user_webfinger/webfinger.php @@ -6,13 +6,13 @@ header("Content-Type: application/xrd+xml"); * To include your app in the webfinger XML, add a new script with file name * 'webfinger.php' to /apps/yourapp/appinfo/, which prints out the XML parts * to be included. That script can make use of the constants WF_USER (e. g. - * "user"), WF_ADDRESS ("user@host") and WF_ROOT ("https://host/owncloud"). + * "user"), WF_ID (user@host) and WF_BASEURL (e. g. https://host/owncloud). * An example could look like this: * * + * href="/apps/myApp/profile.php?user="> * * '* but can also use complex database queries to generate the webfinger result @@ -24,19 +24,25 @@ $SERVERROOT=str_replace("\\",'/',dirname(dirname(dirname(dirname(__FILE__))))); $SUBURI=substr(realpath($_SERVER["SCRIPT_FILENAME"]),strlen($SERVERROOT)); $WEBROOT=substr($SUBURI,0,-34); */ -require_once('../../lib/base.php'); -$id = $_GET['q']; +require_once('../../lib/base.php'); +$request = urldecode($_GET['q']); if($_GET['q']) { - $bits = explode('@', $_GET['q']); - $userName = $bits[0]; + $reqParts = explode('@', $request); + $userName = $reqParts[0]; + $hostName = $reqParts[1]; } else { - $id = ''; $userName = ''; + $hostName = ''; } if(substr($userName, 0, 5) == 'acct:') { $userName = substr($userName, 5); } +if($userName == "") { + $id = ""; +} else { + $id = $userName . '@' . $hostName; +} if(isset($_SERVER['HTTPS'])) { $baseAddress = 'https://'; } else { @@ -44,22 +50,19 @@ if(isset($_SERVER['HTTPS'])) { } $baseAddress .= $_SERVER['SERVER_NAME'].OC::$WEBROOT; define('WF_USER', $userName); -define('WF_ADDRESS', $id); -define('WF_ROOT', $baseAddress); +define('WF_ID', $id); +define('WF_BASEURL', $baseAddress); echo "<"; ?> ?xml version="1.0" encoding="UTF-8"?> - - acct: + + acct: Date: Mon, 2 Apr 2012 17:12:52 +0200 Subject: [PATCH 173/302] webfinger protocol compatible + minor changes --- apps/remoteStorage/appinfo/webfinger.php | 4 +-- apps/user_webfinger/appinfo/install.php | 18 +++++++++++++- apps/user_webfinger/webfinger.php | 31 +++++++++++++----------- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/apps/remoteStorage/appinfo/webfinger.php b/apps/remoteStorage/appinfo/webfinger.php index bb3fe1681b..7c0ab84605 100644 --- a/apps/remoteStorage/appinfo/webfinger.php +++ b/apps/remoteStorage/appinfo/webfinger.php @@ -1,6 +1,6 @@ + auth="/apps/remoteStorage/auth.php/"> diff --git a/apps/user_webfinger/appinfo/install.php b/apps/user_webfinger/appinfo/install.php index 678d57ae8f..c8d9a42742 100644 --- a/apps/user_webfinger/appinfo/install.php +++ b/apps/user_webfinger/appinfo/install.php @@ -1,4 +1,8 @@ '*', + 'Content-Type' => 'application/xml+xrd' +); $appInfoDir = __DIR__; $thisAppDir = dirname($appInfoDir); $appsDir = dirname($thisAppDir); @@ -17,6 +21,7 @@ if(isset($_SERVER['HTTPS'])) { } $lrddTmpl .= '://' . $serverName . $webRoot . '/apps/user_webfinger/webfinger.php?q={uri}'; $hostMetaPath = $docRoot . '/.well-known/host-meta'; +$hostMetaDir = $docRoot . '/.well-known'; $hostMetaContents = " " . $serverName . " @@ -24,7 +29,7 @@ $hostMetaContents = " Resource Descriptor "; -@mkdir(dirname($hostMetaPath)); +@mkdir($hostMetaDir); $hostMeta = fopen($hostMetaPath, 'w'); if(!$hostMeta) { die("Could not open " . $hostMetaPath . " for writing, please check permissions!"); @@ -33,3 +38,14 @@ if(!fwrite($hostMeta, $hostMetaContents, strlen($hostMetaContents))) { die("Could not write to " . $hostMetaPath . ", please check permissions!"); } fclose($hostMeta); + +// write custom headers into .htaccess: +$htaccess = fopen($hostMetaDir . '/.htaccess', 'w'); +//TODO: check compatibility! +fwrite($htaccess, " +\n"); +foreach($hostMetaHeader as $header => $value) { + fwrite($htaccess, "Header set " . $header . " \"" . $value . "\"\n"); +} +fwrite($htaccess, "\n"); +fclose($htaccess); diff --git a/apps/user_webfinger/webfinger.php b/apps/user_webfinger/webfinger.php index ecbfeed8e4..9ada473ca8 100644 --- a/apps/user_webfinger/webfinger.php +++ b/apps/user_webfinger/webfinger.php @@ -6,13 +6,13 @@ header("Content-Type: application/xrd+xml"); * To include your app in the webfinger XML, add a new script with file name * 'webfinger.php' to /apps/yourapp/appinfo/, which prints out the XML parts * to be included. That script can make use of the constants WF_USER (e. g. - * "user"), WF_ADDRESS ("user@host") and WF_ROOT ("https://host/owncloud"). + * "user"), WF_ID (user@host) and WF_BASEURL (e. g. https://host/owncloud). * An example could look like this: * * + * href="/apps/myApp/profile.php?user="> * * '* but can also use complex database queries to generate the webfinger result @@ -24,19 +24,25 @@ $SERVERROOT=str_replace("\\",'/',dirname(dirname(dirname(dirname(__FILE__))))); $SUBURI=substr(realpath($_SERVER["SCRIPT_FILENAME"]),strlen($SERVERROOT)); $WEBROOT=substr($SUBURI,0,-34); */ -require_once('../../lib/base.php'); -$id = $_GET['q']; +require_once('../../lib/base.php'); +$request = urldecode($_GET['q']); if($_GET['q']) { - $bits = explode('@', $_GET['q']); - $userName = $bits[0]; + $reqParts = explode('@', $request); + $userName = $reqParts[0]; + $hostName = $reqParts[1]; } else { - $id = ''; $userName = ''; + $hostName = ''; } if(substr($userName, 0, 5) == 'acct:') { $userName = substr($userName, 5); } +if($userName == "") { + $id = ""; +} else { + $id = $userName . '@' . $hostName; +} if(isset($_SERVER['HTTPS'])) { $baseAddress = 'https://'; } else { @@ -44,22 +50,19 @@ if(isset($_SERVER['HTTPS'])) { } $baseAddress .= $_SERVER['SERVER_NAME'].OC::$WEBROOT; define('WF_USER', $userName); -define('WF_ADDRESS', $id); -define('WF_ROOT', $baseAddress); +define('WF_ID', $id); +define('WF_BASEURL', $baseAddress); echo "<"; ?> ?xml version="1.0" encoding="UTF-8"?> - - acct: + + acct: Date: Mon, 2 Apr 2012 16:35:11 +0000 Subject: [PATCH 174/302] use OC_Dialogs instead of alert() --- apps/files_texteditor/js/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_texteditor/js/editor.js b/apps/files_texteditor/js/editor.js index 02d39b9843..9a87601a4f 100644 --- a/apps/files_texteditor/js/editor.js +++ b/apps/files_texteditor/js/editor.js @@ -210,7 +210,7 @@ function showFileEditor(dir,filename){ }); } else { // Failed to get the file. - alert(result.data.message); + OC.dialogs.alert(result.data.message, t('files_texteditor','An error occurred!')); } // End success } From dc499c5b4e9dc1c64ca81487bfa979121bd407f6 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Mon, 2 Apr 2012 17:27:06 +0000 Subject: [PATCH 175/302] add * to filename when changes have been made --- apps/files_texteditor/js/editor.js | 68 ++++++++++++++++++------------ 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/apps/files_texteditor/js/editor.js b/apps/files_texteditor/js/editor.js index 9a87601a4f..ca6a3a965f 100644 --- a/apps/files_texteditor/js/editor.js +++ b/apps/files_texteditor/js/editor.js @@ -141,31 +141,38 @@ function doSearch(){ // Tries to save the file. function doFileSave(){ if(editorIsShown()){ - // Get file path - var path = $('#editor').attr('data-dir')+'/'+$('#editor').attr('data-filename'); - // Get original mtime - var mtime = $('#editor').attr('data-mtime'); - // Show saving spinner - $("#editor_save").die('click',doFileSave); - $('#save_result').remove(); - $('#editor_save').text(t('files_texteditor','Saving...')); - // Get the data - var filecontents = window.aceEditor.getSession().getValue(); - // Send the data - $.post(OC.filePath('files_texteditor','ajax','savefile.php'), { filecontents: filecontents, path: path, mtime: mtime },function(jsondata){ - if(jsondata.status!='success'){ - // Save failed - $('#editor_save').text(t('files_texteditor','Save')); - $('#editor_save').after('

Failed to save file

'); - $("#editor_save").live('click',doFileSave); - } else { - // Save OK - // Update mtime - $('#editor').attr('data-mtime',jsondata.data.mtime); - $('#editor_save').text(t('files_texteditor','Save')); - $("#editor_save").live('click',doFileSave); - } - },'json'); + // Changed contents? + if($('#editor').attr('data-edited')=='true'){ + // Get file path + var path = $('#editor').attr('data-dir')+'/'+$('#editor').attr('data-filename'); + // Get original mtime + var mtime = $('#editor').attr('data-mtime'); + // Show saving spinner + $("#editor_save").die('click',doFileSave); + $('#save_result').remove(); + $('#editor_save').text(t('files_texteditor','Saving...')); + // Get the data + var filecontents = window.aceEditor.getSession().getValue(); + // Send the data + $.post(OC.filePath('files_texteditor','ajax','savefile.php'), { filecontents: filecontents, path: path, mtime: mtime },function(jsondata){ + if(jsondata.status!='success'){ + // Save failed + $('#editor_save').text(t('files_texteditor','Save')); + $('#editor_save').after('

Failed to save file

'); + $("#editor_save").live('click',doFileSave); + } else { + // Save OK + // Update mtime + $('#editor').attr('data-mtime',jsondata.data.mtime); + $('#editor_save').text(t('files_texteditor','Save')); + $("#editor_save").live('click',doFileSave); + // Update titles + $('#editor').attr('data-edited', 'false'); + $('#breadcrumb_file').text($('#editor').attr('data-filename')); + document.title = $('#editor').attr('data-filename')+' - ownCloud'; + } + },'json'); + } } }; @@ -192,10 +199,11 @@ function showFileEditor(dir,filename){ // Show the control bar showControls(filename,result.data.write); // Update document title - document.title = filename; + document.title = filename+' - ownCloud'; $('#editor').text(result.data.filecontents); $('#editor').attr('data-dir', dir); $('#editor').attr('data-filename', filename); + $('#editor').attr('data-edited', 'false'); window.aceEditor = ace.edit("editor"); aceEditor.setShowPrintMargin(false); aceEditor.getSession().setUseWrapMode(true); @@ -207,6 +215,13 @@ function showFileEditor(dir,filename){ OC.addScript('files_texteditor','aceeditor/theme-clouds', function(){ window.aceEditor.setTheme("ace/theme/clouds"); }); + window.aceEditor.getSession().on('change', function(){ + if($('#editor').attr('data-edited')!='true'){ + $('#editor').attr('data-edited', 'true'); + $('#breadcrumb_file').text($('#breadcrumb_file').text()+' *'); + document.title = $('#editor').attr('data-filename')+' * - ownCloud'; + } + }); }); } else { // Failed to get the file. @@ -287,4 +302,5 @@ $(document).ready(function(){ $('#editor').remove(); // Binds the save keyboard shortcut events //$(document).unbind('keydown').bind('keydown',checkForSaveKeyPress); + }); From f21d6d4f9fcd48ffe4e2e42b4f3a4e8b7bcfd142 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Mon, 2 Apr 2012 19:39:24 +0200 Subject: [PATCH 176/302] dialogs filepicker first draft --- core/css/styles.css | 6 +++ core/js/js.js | 8 +++- core/js/oc-dialogs.js | 91 ++++++++++++++++++++++++++++++++++++------ files/ajax/rawlist.php | 23 +++++++++++ 4 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 files/ajax/rawlist.php diff --git a/core/css/styles.css b/core/css/styles.css index f5a181c452..1c50df9e58 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -131,3 +131,9 @@ li.error { width:640px; margin:4em auto; padding:1em 1em 1em 4em; background:#ff .separator { display: inline; border-left: 1px solid #d3d3d3; border-right: 1px solid #fff; height: 10px; width:0px; margin: 4px; } a.bookmarklet { background-color: #ddd; border:1px solid #ccc; padding: 5px;padding-top: 0px;padding-bottom: 2px; text-decoration: none; margin-top: 5px } + +/* ---- DIALOGS ---- */ + +#dirtree {width: 100%;} +#filelist {height: 270px; overflow:scroll; background-color: white;} +.filepicker_element_selected { background-color: lightblue;} diff --git a/core/js/js.js b/core/js/js.js index df1b5c6ce7..44b4f503b8 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -126,7 +126,13 @@ OC={ }); } }, - dialogs:OCdialogs + dialogs:OCdialogs, + mtime2date:function(mtime) { + mtime = parseInt(mtime); + var date = new Date(1000*mtime); + var ret = date.getDate()+'.'+(date.getMonth()+1)+'.'+date.getFullYear()+', '+date.getHours()+':'+date.getMinutes(); + return ret; + } }; OC.search.customResults={}; OC.search.currentResult=-1; diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index 17c987ae87..31aa76d96c 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -17,7 +17,6 @@ * You should have received a copy of the GNU Affero General Public * License along with this library. If not, see . * - * todo(bartek): add select option in form */ /** @@ -30,9 +29,9 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press OK */ - alert:function(text, title, callback) { + alert:function(text, title, callback, modal) { var content = '

'+text+'

'; - OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.OK_BUTTON, callback); + OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.OK_BUTTON, callback, modal); }, /** * displays info dialog @@ -40,9 +39,9 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press OK */ - info:function(text, title, callback) { + info:function(text, title, callback, modal) { var content = '

'+text+'

'; - OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.OK_BUTTON, callback); + OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.OK_BUTTON, callback, modal); }, /** * displays confirmation dialog @@ -50,9 +49,9 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press YES or NO (true or false would be passed to callback respectively) */ - confirm:function(text, title, callback) { + confirm:function(text, title, callback, modal) { var content = '

'+text+'

'; - OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.YES_NO_BUTTONS, callback); + OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.YES_NO_BUTTONS, callback, modal); }, /** * prompt for user input @@ -60,9 +59,9 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press OK (input text will be passed to callback) */ - prompt:function(text, title, default_value, callback) { + prompt:function(text, title, default_value, callback, modal) { var content = '

'+text+':

'; - OCdialogs.message(content, title, OCdialogs.PROMPT_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback); + OCdialogs.message(content, title, OCdialogs.PROMPT_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback, modal); }, /** * prompt user for input with custom form @@ -71,7 +70,7 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press OK (user answers will be passed to callback in following format: [{name:'return name', value: 'user value'},...]) */ - form:function(fields, title, callback) { + form:function(fields, title, callback, modal) { var content = ''; for (var a in fields) { content += ''; } content += '
'+fields[a].text+''; @@ -96,12 +95,41 @@ OCdialogs = { content += '
'; - OCdialogs.message(content, title, OCdialogs.FORM_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback); + OCdialogs.message(content, title, OCdialogs.FORM_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback, modal); }, - message:function(content, title, dialog_type, buttons, callback) { + filepicker:function(title, name_filter, mimetype_filter, callback, modal) { + var c_name = 'oc-dialog-'+OCdialogs.dialogs_counter+'-content'; + var c_id = '#'+c_name; + var d = '
'; + if (modal == undefined) modal = false; + $('body').append(d); + $(c_id + ' #dirtree').focus(function() { var t = $(this); t.data('oldval', t.val())}) + .change({dcid: c_id}, OC.dialogs.handleTreeListSelect); + $(c_id).ready(function(){ + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', function(r){OC.dialogs.fillFilePicker(r, c_id, callback)}); + }); + // build buttons + var b = [ + {text: t('dialogs', 'Choose'), click: function(){ + if (callback != undefined) { + var p = $(c_id).attr('data'); + if (p == undefined) p = ''; + callback(p+'/'+$(c_id+' .filepicker_element_selected #filename').text()); + $(c_id).dialog('close'); + } + } + }, + {text: t('dialogs', 'Cancel'), click: function(){$(c_id).dialog('close'); }} + ]; + $(c_id).dialog({width: 4*$(document).width()/9, height: 400, modal: modal, buttons: b}); + OCdialogs.dialogs_counter++; + }, + // guts, dont use, dont touch + message:function(content, title, dialog_type, buttons, callback, modal) { var c_name = 'oc-dialog-'+OCdialogs.dialogs_counter+'-content'; var c_id = '#'+c_name; var d = '
'+content+'
'; + if (modal == undefined) modal = false; $('body').append(d); var b = []; switch (buttons) { @@ -128,7 +156,7 @@ OCdialogs = { break; } var possible_height = ($('tr', d).size()+1)*30; - $(c_id).dialog({width: 4*$(document).width()/9, height: possible_height + 120, modal: false, buttons: b}); + $(c_id).dialog({width: 4*$(document).width()/9, height: possible_height + 120, modal: modal, buttons: b}); OCdialogs.dialogs_counter++; }, // dialogs buttons types @@ -161,5 +189,42 @@ OCdialogs = { } else { $(c_id).dialog('close'); } + }, + fillFilePicker:function(r, dialog_content_id) { + var entry_template = '
*NAME*
*LASTMODDATE*
'; + var names = ''; + for (var a in r.data) { + names += entry_template.replace('*LASTMODDATE*', OC.mtime2date(r.data[a].mtime)).replace('*NAME*', r.data[a].name).replace('*MIMETYPEICON*', OC.webroot+'/core/img/filetypes/'+(r.data[a].type=='dir'?'folder':r.data[a].mimetype.replace('/','-'))+'.png').replace('*ENTRYNAME*', r.data[a].name).replace('*ENTRYTYPE*', r.data[a].type); + } + $(dialog_content_id + ' #filelist').html(names); + }, + handleTreeListSelect:function(event) { + var newval = parseInt($(this).val()); + var oldval = parseInt($(this).data('oldval')); + while (newval != oldval && oldval > 0) { + $('option:last', this).remove(); + $('option:last', this).attr('selected','selected'); + oldval--; + } + var skip_first = true; + var path = ''; + $(this).children().each(function(i, element) { if (skip_first) {skip_first = false; return; }path += '/'+$(element).text(); }); + $(event.data.dcid).attr('data', path); + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: path}, function(r){OC.dialogs.fillFilePicker(r, event.data.dcid)}); + }, + // this function is in early development state, please dont use it unlsess you know what you are doing + handlePickerClick:function(element, name, dcid) { + var p = $(dcid).attr('data'); + if (p == undefined) p = ''; + p = p+'/'+name; + if ($(element).attr('data') == 'file'){ + $(element).toggleClass('filepicker_element_selected'); + return; + } + $(dcid).attr('data', p); + $(dcid + ' #dirtree option:last').removeAttr('selected'); + var newval = parseInt($(dcid + ' #dirtree option:last').val())+1; + $(dcid + ' #dirtree').append(''); + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: p}, function(r){OC.dialogs.fillFilePicker(r, dcid)}); } }; diff --git a/files/ajax/rawlist.php b/files/ajax/rawlist.php new file mode 100644 index 0000000000..0abe81e672 --- /dev/null +++ b/files/ajax/rawlist.php @@ -0,0 +1,23 @@ + $files)); + +?> From adc9d906e4b45082dc08463afce6835a49feb438 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Mon, 2 Apr 2012 17:44:15 +0000 Subject: [PATCH 177/302] confirm close when there are unsaved changes --- apps/files_texteditor/js/editor.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/files_texteditor/js/editor.js b/apps/files_texteditor/js/editor.js index ca6a3a965f..1e136fe68e 100644 --- a/apps/files_texteditor/js/editor.js +++ b/apps/files_texteditor/js/editor.js @@ -76,7 +76,7 @@ function showControls(filename,writeperms){ function bindControlEvents(){ $("#editor_save").die('click',doFileSave).live('click',doFileSave); - $('#editor_close').die('click',hideFileEditor).live('click',hideFileEditor); + $('#editor_close').die('click',closeBtnClick).live('click',closeBtnClick); $('#gotolineval').die('keyup', goToLine).live('keyup', goToLine); $('#editorsearchval').die('keyup', doSearch).live('keyup', doSearch); $('#clearsearchbtn').die('click', resetSearch).live('click', resetSearch); @@ -235,6 +235,19 @@ function showFileEditor(dir,filename){ } } +function closeBtnClick(){ + if($('#editor').attr('data-edited')=='true'){ + // Show confirm + OC.dialogs.confirm(t('files_texteditor','You have unsaved changes that will be lost! Do you still want to close?'),t('files_texteditor','Really close?'),function(close){ + if(close){ + hideFileEditor(); + } + }); + } else { + hideFileEditor(); + } +} + // Fades out the editor. function hideFileEditor(){ // Fades out editor controls From ff5dbc52b8137bc1d4e082c422049271d5f89978 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Mon, 2 Apr 2012 21:31:34 +0200 Subject: [PATCH 178/302] multiselect handle for filepicker --- core/js/oc-dialogs.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index 31aa76d96c..d40c433bda 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -97,24 +97,34 @@ OCdialogs = { content += ''; OCdialogs.message(content, title, OCdialogs.FORM_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback, modal); }, - filepicker:function(title, name_filter, mimetype_filter, callback, modal) { + filepicker:function(title, callback, multiselect, mimetype_filter, modal) { var c_name = 'oc-dialog-'+OCdialogs.dialogs_counter+'-content'; var c_id = '#'+c_name; var d = '
'; - if (modal == undefined) modal = false; + if (!modal) modal = false; + if (!multiselect) multiselect = false; $('body').append(d); $(c_id + ' #dirtree').focus(function() { var t = $(this); t.data('oldval', t.val())}) .change({dcid: c_id}, OC.dialogs.handleTreeListSelect); $(c_id).ready(function(){ $.getJSON(OC.webroot+'/files/ajax/rawlist.php', function(r){OC.dialogs.fillFilePicker(r, c_id, callback)}); - }); + }).data('multiselect', multiselect); // build buttons var b = [ {text: t('dialogs', 'Choose'), click: function(){ if (callback != undefined) { - var p = $(c_id).attr('data'); - if (p == undefined) p = ''; - callback(p+'/'+$(c_id+' .filepicker_element_selected #filename').text()); + var p; + if ($(c_id).data('multiselect') == true) { + p = []; + $(c_id+' .filepicker_element_selected #filename').each(function(i, elem) { + p.push(($(c_id).data('path')?$(c_id).data('path'):'')+'/'+$(elem).text()); + }); + } else { + var p = $(c_id).data('path'); + if (p == undefined) p = ''; + p = p+'/'+$(c_id+' .filepicker_element_selected #filename').text() + } + callback(p); $(c_id).dialog('close'); } } @@ -209,19 +219,21 @@ OCdialogs = { var skip_first = true; var path = ''; $(this).children().each(function(i, element) { if (skip_first) {skip_first = false; return; }path += '/'+$(element).text(); }); - $(event.data.dcid).attr('data', path); + $(event.data.dcid).data('path', path); $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: path}, function(r){OC.dialogs.fillFilePicker(r, event.data.dcid)}); }, // this function is in early development state, please dont use it unlsess you know what you are doing handlePickerClick:function(element, name, dcid) { - var p = $(dcid).attr('data'); + var p = $(dcid).data('path'); if (p == undefined) p = ''; p = p+'/'+name; if ($(element).attr('data') == 'file'){ + if ($(dcid).data('multiselect') != true) + $(dcid+' .filepicker_element_selected').removeClass('filepicker_element_selected'); $(element).toggleClass('filepicker_element_selected'); return; } - $(dcid).attr('data', p); + $(dcid).data('path', p); $(dcid + ' #dirtree option:last').removeAttr('selected'); var newval = parseInt($(dcid + ' #dirtree option:last').val())+1; $(dcid + ' #dirtree').append(''); From 896bff57480448456b2ca7d57f5ff2b0439315f6 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Thu, 29 Mar 2012 18:54:06 +0200 Subject: [PATCH 179/302] Contacts: rescan categories on load if there are none. --- apps/contacts/index.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/contacts/index.php b/apps/contacts/index.php index 04f6c65a14..076b10c2ee 100644 --- a/apps/contacts/index.php +++ b/apps/contacts/index.php @@ -34,7 +34,28 @@ if(!is_null($id)) { } $property_types = OC_Contacts_App::getAddPropertyOptions(); $phone_types = OC_Contacts_App::getTypesOfProperty('TEL'); -$categories = OC_Contacts_App::$categories->categories(); +$categories = OC_Contacts_App::getCategories(); +if(count($categories) == 0) { + $vcaddressbooks = OC_Contacts_Addressbook::all(OC_User::getUser()); + if(count($vcaddressbooks) > 0) { + $vcaddressbookids = array(); + foreach($vcaddressbooks as $vcaddressbook) { + $vcaddressbookids[] = $vcaddressbook['id']; + } + $vccontacts = OC_Contacts_VCard::all($vcaddressbookids); + if(count($vccontacts) == 0) { + bailOut(OC_Contacts_App::$l10n->t('No contacts found.')); + } + + $cards = array(); + foreach($vccontacts as $vccontact) { + $cards[] = $vccontact['carddata']; + } + + OC_Contacts_App::$categories->rescan($cards); + $categories = OC_Contacts_App::$categories->categories(); + } +} $upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); $post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); @@ -61,7 +82,6 @@ $tmpl = new OC_Template( "contacts", "index", "user" ); $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); $tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize)); $tmpl->assign('property_types',$property_types); -$tmpl->assign('categories',OC_Contacts_App::getCategories()); $tmpl->assign('phone_types',$phone_types); $tmpl->assign('categories',$categories); $tmpl->assign('addressbooks', $addressbooks); From b3f107dddaf4f3b5b65d8dd67d66bf1bd36d9ae1 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 3 Apr 2012 03:28:12 +0200 Subject: [PATCH 180/302] Contacts: First work on cleaner design. --- apps/contacts/ajax/addcontact.php | 5 +- apps/contacts/css/contacts.css | 32 ++++---- apps/contacts/img/person_large.png | Bin 11517 -> 7929 bytes apps/contacts/js/contacts.js | 74 ++++++++++-------- apps/contacts/js/jquery.combobox.js | 23 +++--- apps/contacts/js/jquery.multi-autocomplete.js | 4 +- apps/contacts/templates/part.contact.php | 73 ++++++++--------- 7 files changed, 113 insertions(+), 98 deletions(-) diff --git a/apps/contacts/ajax/addcontact.php b/apps/contacts/ajax/addcontact.php index 839a391998..947b35bab5 100644 --- a/apps/contacts/ajax/addcontact.php +++ b/apps/contacts/ajax/addcontact.php @@ -39,7 +39,10 @@ foreach ($_POST as $key=>$element) { debug('_POST: '.$key.'=>'.$element); } -$aid = $_POST['aid']; +$aid = isset($_POST['aid'])?$_POST['aid']:null; +if(!$aid) { + $aid = min(OC_Contacts_Addressbook::activeIds()); // first active addressbook. +} OC_Contacts_App::getAddressbook( $aid ); // is owner access check $fn = trim($_POST['fn']); diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 76b5972ba3..9d238c36f3 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -14,21 +14,21 @@ #contacts_propertymenu li:hover { background-color: #1d2d44; } #contacts_propertymenu li a:hover { color: #fff } #actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0;} -#card { /*max-width: 70em; border: thin solid lightgray; display: block;*/ } +#card { width: auto;/*max-width: 70em; border: thin solid lightgray; display: block;*/ } #firstrun { width: 100%; position: absolute; top: 5em; left: 0; text-align: center; font-weight:bold; font-size:1.5em; color:#777; } #firstrun #selections { font-size:0.8em; margin: 2em auto auto auto; clear: both; } -#card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 14em; } +#card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 14em; float: left; } .categories { float: left; width: 16em; } -#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #f8f8f8; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } +#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #fefefe; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } #card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } -input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { background-color: #ffc0c0 !important; } +input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { color: #bbb !important; } dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } -.form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; font-weight: bold; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } +.form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom; color: #bbb;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } .form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } .loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } - +.float { float: left; } .listactions { height: 1em; width:60px; float: left; clear: right; } .add,.edit,.delete,.mail, .globe { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; display: none; } .add { background:url('../../../core/img/actions/add.svg') no-repeat center; clear: both; } @@ -43,18 +43,18 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } #edit_address_dialog { /*width: 30em;*/ } #edit_address_dialog > input { width: 15em; } #edit_photo_dialog_img { display: block; width: 150; height: 200; border: thin solid black; } -#fn { float: left; } -/** - * Create classes form, floateven and floatodd which flows left and right respectively. - */ -.contactsection { float: left; min-width: 30em; max-width: 40em; margin: 0.5em; border: thin solid lightgray; -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8; } +#fn { float: left !important; width: 18em !important; } +#name { /*position: absolute; top: 0px; left: 0px;*/ min-width: 25em; height: 2em; clear: right; display: block; } +#identityprops { /*position: absolute; top: 2.5em; left: 0px;*/ } +/*#contact_photo { max-width: 250px; }*/ +#contact_identity { min-width: 30em; } +.contactsection { position: relative; float: left; /*max-width: 40em;*/ padding: 0.5em; height: auto: border: thin solid lightgray;/* -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8;*/ } .contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } #cropbox { margin: auto; } - -#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; padding: 0.5em; margin: 1em 1em 1em 7em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; clear: right; } +#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; padding: 0.5em; margin: 0.3em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; display: block; /* clear: right;*/ } #contacts_details_photo:hover { background: #fff; } -#contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; } +/*#contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; }*/ /* Address editor */ #addressdisplay { padding: 0.5em; } dl.addresscard { background-color: #fff; float: left; width: 45%; margin: 0 0.3em 0.3em 0.3em; padding: 0; border: thin solid lightgray; } @@ -72,8 +72,10 @@ dl.addresscard dd > ul { margin: 0.3em; padding: 0.3em; } #file_upload_target, #crop_target { display:none; } -#file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; position:absolute; left:0; top:0; cursor:pointer; width:0; height:0;} +#file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; /*position:absolute; left:0; top:0;*/ cursor:pointer; width:0; height:0;} input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; } +.big { font-weight:bold; font-size:1.2em; } +.huge { font-weight:bold; font-size:1.5em; } .propertycontainer dd { float: left; width: 25em; } .propertylist { clear: none; max-width: 28em; } .propertylist li { /*background-color: cyan; */ min-width: 25em; /*max-width: 30em;*/ display: block; clear: right; } diff --git a/apps/contacts/img/person_large.png b/apps/contacts/img/person_large.png index f57511e1000690c223bc5de2313ddfface12f29a..4edba0c5489d1e92b35f9f813189bd8b7c391f17 100644 GIT binary patch literal 7929 zcmW-m1z1yW8^#aau%U#cj2aRmDIlO@v?7j@ZcrMeOHg8floAd^q)V9y3~7*1VbY7< z4DKkOAasps!B;q~6B_(Z=c8l(1OS-3|7~QM;>_Ftz?Z9wyki{vW-}t}h1F!p?6ykK zOsz^kuV4O@bs{V~V^Pmo|3=O4FU;vb?Iq1&?wN_jhA8?muT3trxFTx4(31UwJ(Ule zBEs<(ZKYuiMnyzc`7RdjzKP2 z4*L{R%L5U!`^~dKZTorK;qf~+I={aXZD#5^3`6=WMor_Vw2>6}TfjSS;zx5>j?3~D zpcWVaf+umCMx@!N7xP-GVS?|HBLVdn$;k<=VI&cX=ngDo*cCX{00`(u3_#I9|4)24 zQ-`oiWD+{kA!m7TCS)Zqwn#g*FH7}Q*++wQuZaH){vEFkoH9o>qst!;e#f^1JF^L^ zB@{Hw5^@_&KfW+&eoMF{isOp&<&VO$4#T5-lDtgpmY*OGTAa;A)h~_)@8ee0m=@v$ z7RSwOTjSAhX?92n@UfIInJ4M!nM~CA#>rOmHkRN!Xz|krF%!IytRwR3$P9U~jfR^K zDj{R+q|vvpMuBfTX<9(~-lZ((&QB7rOX*yyNjKPm)3Qf$$D${s?LqA3y`1Mv!i$UF zU!5TjXpjd&Q3GyYsh{6L7CrC}3UchvlBA9Al4hhp&~j>3q%?SPqx62_k`V<_?vvH_ zzP`{#E9TP26QOS|%eH^R&$e8gpSb4iJ#+7fgYF$~U|DiXk7m)Uoo-(>VNga!#&=jO zOI}`{!Q3PR0hg{m6_J>#wg=7}KLn>!1l}HC?2izyHD0_M_$8jT3sVs7I5@fZc3cM83;1R{BY-xp+*+ z)^u}?<5y-%atldQYL`HEG2!A2XF^L7NAzu=VJ1{OaC?SH;962NMgxDk6mjx5A~YG> zEK+yRnO8lsWmNh_HrdpQi3x&s%CBFNlb3g#J%yvM&crnHnt#a*W0%ie2W&>jcYBxc zgJJE@K68lJVxP4?W}bw|I%}ft=KUJotA#X}BLJG-Y>1{66cqf~7SW7NL{WL=h}<2O z5d6|Qd&UnicHIX5+fAOougHN8oF~DKpde+_vMT)I^-&aL{%TbAge`sjFneaee0RbJ zm$#3Upo3k>0hl{dPi zrDbHEZEbBOo?Lnmg>DP~H_(O@T6u^aQJitX*r7T@{vh-gYXUG-^~#|J9j>-r{>ow* z>y`gF=b?o~WqHSRcTAVP$7~yKr=6i0&2$$&e$tYe_|7`>Sm4o^qpdB!g6D5z+z)F= zj{`KzS?Q!Dc2hfL>n8BcrBeo%Q`fRdVqhBqPLIc8hccfQ;12lx9+lA=PF z=_BswT{)82J}dom9kS#W6wiV9bKGif5~tp=4ODQjD!_bc|XH>svA$H2_WmP87Un{yQvbS=Xo#!(NsfF?=8RK7;j_2M&)i8nox6{`Ygx`uTW}+Z78v zS}2k1XY$LV)o{-^dpj^qolDCb(i?2X{Aw;IXnvv?_GGT| zT}jNtEH;{t-P!dCo+<>C9aKFLzHzOq?qj8Dm)fY4jz!>9x|t&^<0>72Pjb$%|lO z2>dcRREg;ppiDHR-u|ma-~LES8l$d5^pa{aQ=2^JCz^+uz zywD0^-bmKPOhiNXXKMvMi6b@tOG79+C_>^l&NHO71sttG=EF+W#M)VCanX#szF56I z((3olu5h>9k7p1V!_~Q&>Q|740dFeA5>z{oFcqA9 zMPTmsKCm38t@s-|sUNo$4mdL#)R(uh^54x@f7({U)Rh%a1|6nRt}mkXrMchS>=4m5 zRJoCCdf>G;9;k`l!jxpJr1Gh*6C6CKe?zka3>t3a4|d7j`bl@ zlO%IvvS^fe)WQ$BWX%|LaS~N`<&CRk*DDss1KVx4Ynrwkfwt#iuYY~=JShrb*S=RL zD3wHZI@0_=FTr2gTDhalVREyoG_g{0{U%jjY{@zcuK?12`CGc6_z%e(y*hCm35z+J zY~dbVlauHl2`nHl=lbvCTY5gYDDqB(kQ#N*U{#Cl=uoxch!Nh(K5ub9q@DUTd!*Zs z*1_fX!IDp& zc_Z~sZ}rC_$QrE-NYN5Mac7a!*IStDG!YX0O|X(TBrmWOWA1952%pp!a#!e&DQsh< zmi6=!tfW>}mReoRCy=%Mn(;Wur+|$KU4ib5AQf2^CL|kqF}wU!V-|7g96Qna^)rY3 zYi(^19d@f4TtY)b>-gebiX>zHn{A(ci8@D_(dz5#8>r4(8($4nH8oZpA+uuH-jynS z?BMLIsUl~fcho-^vGqi9ljgxA0;dM-n0o@gVtg&Ou~EtM4zP5?Dsuh%*=+M*t92}Y zqdIMUb#H2LjYUEGXb!3ZsS;n;+JiU4G!C{-f0D3j^rK4GL8?5XgR>QLku7O`(3@_p z?&WDx#nZ3(a~T;KMH0VCK+p1zXZ+3{<;H~Xbkby#wRCgsD`xl4?Je~?cMSl>XH6R) z{zQ+Y$y;jy3W}3dFmx%_}zyD3Y+lV9^UBNf69<@sNJi` z)$Q%C-@c(0s@dV@&NGkFEKq_maHFs-$Ks6Fxw&24-7&a$NQWFWVtH7b|BRqD8}Q-L z#!aGVe)ygsaFLENYNC$}3}nxivi75&Zib}G#lVdN)8kluEqm46DB)2Pe(7Wfrd2T=EBg%18bYTDShR<-h%@uzJ>p` z=H0XmRuGrwd_KQ-9kwhBF6oVUVX3#gyeus*zvTMB9I#12!Y->C@`=P*PdI7#vaSgk!y@SwU1yYzKWH|BKtuE;y z5O9{3oJG%KIlyGLa{-w^w7;H9EA6|Jw4&mQzSdR9ZCJX`T_*t~VYKsjP77B2N9wi! zC2IoZ-cbu~ffkC-2a%hqx7rXi>UKzeshP{4#}j3zbvc9p;PP_PuC?=w0{xF1fQ zfc&y8=X!`y;dhi@9V^J#+os)x@i4m=F%%BN|85fEb%aLJ8msonNhFdWYr?8b<~gr` z4QE`GOB80Mdy$fIY~h8{n|2hyxFS&+>M!PPO)VZL%wz;XPtxZE7 z4IH1G+_-fMJe>J5J(Z4F|EC-9mBO6g4v!wu2F-+sdNpq<92^{6%`gpkTi%D6|XS>f?)Pl3n3P=kDrzU!V?rHp~H`%EmpxU&6 zrUirZXLFEukB}6|w_qrwBy317g33?Q5@-Cz41Rg%FTwjnBufC6i@9L$V0qs8CulZw z<3mZ{ee9$H!X|99KE~NRpKNE4>{J)+J$7 z{LPo&(7+HfnC{ip@@*a%I8SU@S!R05%F2BMW+x`Mc0ut~-|73-ogJ_Oqz#ZyGY3~@ z)x!^c^j{6u#iZ@-?ixySo-)BgDLb&x;fc^KHb&}yD>yf2FZ!7Y&XQxgYaDrmo0^zd z{*haM6qz}BIMw*%bv~9;xPYQ41DKPC9orWe9)6$eimz&>oIsaC_dr)b^p=c_pGv04 z(Xk|;VkpDqHX{h3y*bKLp*O|*^Bwx5C*ix^2L=atSdEEUTjkU^EXF5Jx%>~P&aNM) z+#^LLd~;f6@gSTVbxEJEbUvra<5_62YBd~Wxp0BN~$z(_JN|RYp z`mlDeFm?sEU)eYLfcZBSP=Ri5I)QUB=%xmR7bmElq@|-eJMVxZ{iF$**i$G=is$0p z__0}4Ofys_!hyKTm8^V+)!N#c?MY_2yR*!des;th*h>KT6h{A5Q+A7b^sV|Z4Y4dmG@8A$E!dA z<|i|}w*1C#R@>UhS{*|BFHp|T&IT@r&z!(@#2&yeORK6TSD9gtQ~HQ^xH^QzmPjQT z>gHBfw@4+|BPy0(WOBb zq}I}3of4Gu5zfDYoDCXh|E{Qr;=2>;ZZJ=$9i-;r`}Yy^Wp9J!`+OrK`9M@?>kLp! zgIClp;7@J&Lstu$n$hlAc<@4b$b%Tjd_SJ2A|S<0Sd^9SU%q4RDn~OAL)|?IS~U;H z2I7jiV!`4B$}W!)4*Ba|Lae@MpJtm3Dt7u<2z_kjfc{dew?b}DcQ++FJG)y>|JLY( z99I%&lzPZ^D@m<#eCR;309-E!l-Fh1n^r&zW4?hlwspG6qXlL{mZW8xju2Q7!bRP8 z-?CUe^(9SW#d)a2N(5Cad#WYi*Xf#UiTIgC#YmD73CzRKfty?+qM}vFzlqPtS_bjg zSNs~c)poltwc&8My56r}i$LiOT#!X=djOT*%9y)_4GkRNlvK(crZ3=%`(_ZMtlodI z_MEh_h=BVwWp?K5>>M07MVhMd>p@9dVg% z>-diAv+CFk7f2aba^w^K(EJ|}Z75d`DXa3w3bOk=vKM~^Q*Z5snqO(Wc#)bqD_(M{ z51h?JAZS5b$quXGM4!(tk?h#T7Y~ezvs}H5Dqk0tVNl`cpWkRWt7@WdYQQR_?jmx24&-x8WQ5AnpCG7X%>@v ze4~}e-TLnmogPqVPp{u4-J%CIH&`~n7zC*R#LT}{j$W?I+5nFUcT3D^rGW{NL|Oo! zzSp_K3XEj?=6zYp9}F?Qub_#vbrPgJ+N?4y-|}?ZvW6j~Ig#+om8;St5t-m3S~XYh*3#-rQs$+iv!p z;>L@9b4{BKVhLkdg{DS}?EXqsUH^ls>aavEKH-^(oA1mKs>!aNX(^?9dD1Mjh^LXN|AL4aLjK1Wq zj1Vi`X0oy1ck3Zyc}ab*=?6`Vsf`WczUChv28`!xb%aoJWImJmY8sMXkabK|-w|ML zKT4CoG=pUdjSuq4PPF)-ml-weQlEO&i`ds_OE69c2&x(PZx5em<_PqpL z0!#e^%+@W>Xm@ie@ssy{n(D>!bEUJBE&|8YuceoIt>)3k6R&pT2gd%8y<2SPTk_Kw zI(Qt@HJA8|#5x~mJExeGVc|4(JnJ!DI?)>NqS|BZwh#-YEDIQi@yz^cR>rI-5e51_ z{AlYt0$I6ibX7&A*0+k%wj51^T3u9WdqW9UxmWH?oI+e&+#V1{fJr0RY3I`&M&|5& z5vU;3JGgj)F7O^Mzo~;WXBbGXy%2d&xOy?Lnp9u+)_`tD#=uq>J zy>CIa6#72%y{WD~nADFTp>x{k`>HSNJewIhK!2V*y5j??FR`lX;Y05^ao#nvkDz3X zMRZO&d5LhP=hU+ewGtpA{(iZ?r;U8Y11!Eml0b_Gv6sQT)rC z^vIf=g7;s3rZU-xGg^VdnU(yfZ5lC%0zQDwMD+7UlAD;6zn#>Q%=;|lvP<;d)G^g? z<2_qa;hWT%O;lgCA$&~m?&#DPeBKO2hIyINFiF6~&a%FaQ;{K~)5sI3Oo0f*D3ESN zF}uvbVj!wjn`tt}RqszhNPfdT^WQ)F8(oQsJC#c?c&;PXEYZKt1gttQC;IU74?39y zl+ad8w1(8)cJ3co^84LHzJv7+@CtTGVGszV%#LH>w1JKUlo}hEyD6KDgU4yas#UCA z`0rGC2bhZ)&fDbzAC1iH&GelMp0CSSu?VrrRD>)THM4O^dxgV&7(N z*S%i}eF@&vpN|*(v=3Mv%(J|4u=YI$>gSP4@!n8W--GQ~<8!fTjq;?Bg1wDnyhh|` zG=?{|XMWxx_L^BIOH-=R<~~ACiOX&|QCDROQ_yO-AO#x6Z(e}c!$|3@m^NM!DDl_w zU)m2T9)0W|5RiFAo=AB)^gM_G$Zjn(f?xg!ssSTsF$b{ty{QRtLwEdO)%wXF2;2uxkKDsUq#k0<*K` zHPLH^*A8Q2sYEf+v#^qGJf2bQbVp_SI01rnL>Y<8FZiCA?2*l{R*4C6{R zWmGiWWM(sbzs|Ds@W0K?0EzTeaiz+gX^o2UV^BDQf;NwY67dmYt>FCn4L|ULab+-n z(qI;e)W>;V6!?PJqu=A;%L1LAi?~X_)zsB3>LhX*c$l+iWoPH@PA4)Fb3vgeEp6z+ zSswrQ<)TPGug|33-W2JH`t#~Pt%KAVcTY#l?iU%tGxEF%3aE+<+1X6Dn>KCXSIX|^ z_nw}%V3o5`ZCt5eFVcWNjVo^n$s=hq&2zFX=9)7B0Rc+I<+~BrG+$bPKF;&!&wtjs z-nX2FuUEMXV^Dhk+9)@lo`?Us%CGs1Yo)voo~d_aKvOM6_s1K!bzF~)*nF|SIH*dR z!s?0HiWFM9t7ga$GR(~+*m+S?M^%6S`}I6eB`ESgV-xv**!QoabO*^sS-xd%LQQ;_ zix21PaJ;Wd93w=D7fq!;Jze-d82PtW9abpd(#B*$9dm6TX9$1yPy#{wAdA-+DkCck zlIzg)^jQtb`xh=*)^*?y$hse18~(D6hl|&y;5aW0`?{RxVV|CDv>S5w>#Y$dZSyb2 z%G8J;C66He8KMkas@GrpDW*kjT>2`$9}*hM1?th&_4N_v-|fSlc#!XcSMq{<*2ly| z=et!6kmLMBIdsA|hh;jK^CCon^k2N}V8*@gE0)T$#}f@&F^~%s?Ji|hVtH?yWAy17x8*By1P^91_9}zg&{=#G{OMVARr*E z@A3QI^|ID**Ua3r@4aWv+535Z=R|3$D-hsQ<6~f85GX0iYJ+1TcunHo1OMHf7mL9G z`c_6s7Z>~m;95n3_jqoKhHo)2-pT!YVGa&K2*HO`9&!dAI<7Vz-WKlG7~bCA-1aVx zZ(m!uS#!I(+vfZfr^djb!%&iaq3e@-nCG4NcrI{TO2XIXEhG|Y!k~#^uLVo^t<+eN zVr7WKz#BrDN@_M2Ub?+5m1c67t!71mD&uwOTrn z6z>t#t6|>6e8VY!J}`K!+eX7RVopHfxd7@;&r>a8u5R@#U^~O~@@aNy1HNOf$eVTc zLY&V~1KU$PH%!WwtAo{s*TWpBrC-+}jmpSYxAlQ27W5p#BxcF4pz;aCkD|3_37iqD zaXu@t@*67tW_`d5-)e~<00US5GRY8qgVZYY9*(DR)xGo;ifiS<)Bx8IIKDx4R4u1w zg!~0<5Q29Mk*>Vtee0j2g zf4mwZg#npyRvXcL`swfeITP>i(YHl6rhfSB@9Q&e~@K(SUgqUjrnofJ`iQa4-VwyC4GI0sP~?p(Zv|%7lhl}+f+0()=o~b zQBiomt^!2IA(=Rm4R5z6TvLs-YRu(`|6L#dm>Hc?GW!re%%CSo!oe!{T3#b zS8?XNing|_d3kwG-`msv{0X|gxlv(WXAKc_E9j{)4*IVPdT@An$A#uFJraQNz@l_x zeZ0O3#;LOCa6kNusx|5OvwYOT#k!Y`=<4d4!nd@uc9qaJ7g4iTXO8|ix9Q2?vsY&AVGZYtOLt)F*x zX^}lOBO_zy^F}S{o+{>4tpF$gQ zkK_~GTwLPyO?|_5ckOvv&7dnij&rq5WDcf-n%+=xzcV|=V@C6i1Bpk^T&$K_oXI@- zJNuNFx7|h6^TYS$)lj z_9TQt{NpP+?7BI!nwlEp!1Ec|4^zo~oRyWy>SZG~P34vNA|w;LI$ET~Q+7$G8=HAC zyyF_d*E-2cyA8Osi%qtZ-X*l!a{pmtCOj5}74lkMO1M~)L_z0_-x5U0_PzDauB^1; zr{d3wC0)y{HB5^e-e7{^%}Iq_J)07Y3k$;zy4|1^kdop#g$La;2H~E-OWlODc+|fP zC(_X(Tb!37hd1&^GDfU2b8;9{MleVh-dL{Q?qX+`@<~Ze&d+BuN98^yrI%}UUShfV ze4*)ME0mDUicVrwC`M9Pje?<<-qIrcuD9@0_OxJ}E_(@4UL!;P1FMk=dujmz0j87@ z28AJlUh~A+S-siXG{V3ji<9p&f)4qJ?9xEp=}*mGYY#rKw%bpaG9=6$4#VMT;HKT% z9!D1&B!5xAZhJUU6TqHk84$4CGS|QNJY0re*m+Twj@T`LB4+88+O*`}nTsQ)kcKo` zI7{aH_wOyg)5spe;mm2uyYcU&MIC1ElgiN-ObUpKR`}1kypfU90uSur?TwO@-}??H z5mG}^m<8}bmeG3LPZFBmY{<_gMx5Y~x8zGd)t)3uuB;sW@#A9^HTfaoy-=s+&q+Sw z+&XszF-P?_TGP;v^{Vn!Mn#_cvq}UNu(|2oPftu6bh=D6f8MnjyC_8tsxz=h%ZNO- z`QGly4{z1>i4cm(W+^PSZ&jKX^YHLcJokzW(kt@fD6vtg(DzwGKV~KU?V_L%5la(U zYA;k1?wF3?={%O9{5(}-{PwNDiO*v>t&jg@F6`8O%s%%!Iyg8;_GQ#d*3=@kY@i$; zALpN0`FzRk2oq9~M(c+Nx5|K&BKO@{T|PiK2fMudi~|S!uFs!W9X-D*8XykF`=sTQ zAr$qB1s!39@2k}#)Bd0PQ3CG$h2EW5aC=|$Zs?^J_3-}V#1vBN_;0-1-ob%^JVq(Z zv|2akWV(oEGaw@Qr%H*ooaekKlt(!IHps9JfAMLYO4z0bv6Q#a1-RNpFp74ToEKns; z{TyV+RC#9$SyUp}NU$#{j-4C5;>7m4h?Z{CAsS)Ffr7<)6P(@GC-2U8nLifPo*i>>#OV~$Yp zCrWWzTk0^E_ylZ+BPKb`dtFg_4i3|qpJ2$=90-FkFr;HPWZ}-N2e(%tC zkU4Sxz`~4X7z*JogKTZ`TQL_t$o-g4@huIX1J6!U4-WTQJIziF(!7CsyibF^FOf}+ zOA~#lY|}&f)DDNW1OC|GO-2vT0P+#nju~?~k_l^JzL0`wVOLa7J#viJG7UlYuGGF3 zDp+M+pY9c;Bz@ZO;Hvs(8hxp%xkSMvN{n7;7xN_nNg_;_cm9GUv#zbT3i_$4NB8Mx z2P{3>Z|w8&9z&-g(#VtHMn*+ZSxz|qs24}=A|fq}1M~FiHkpj@;0qgxepA)(;XF~! zSD$fl)Fh$a>gqim{;ubWpDlf@%w1~UtjDVv5_v^q%TKi+ zQg}w)gNn;$rgKv+{o2`Iw9Yq93$)nha;wJPtmYMf(ZYx!RqN8FT9k22NvFK{+4( zUDuQTu|rnRm|`$!dQaU-Fr%Tv#(Sbt@>J+lmZM5|(_Pf?#uZb9Ri~c1+?p)5)ZP{2 z?@)y*dm8(z>4lNdv%Sd7qgFBfy>6W@{w*2doSAK+4z!tT$Dy#RnI9F%WwJ7!<(03j z?q>xPH_@8fJ%m=<^IZO3ay64?h|E?m zi#eb#BaTTSO~o5dB}r~rdX_I>tJCP9BbJv=&|6*&t9Q!v;7CMmdZhheA+vCO{dC9=i`6~Y+{VwcS#hiq#Tb#KdcjOOOacY7*acr}mej=d|h53>$nX8rJ2 zu~bt?>l7-h4Z-hVOHvd`=U^tqa&PE$kF?vwG&%%h1t?Oh9FtR6{TBR~#(S9$YV5`V z>h4E9a_lQNRZ-!>v}>|qJ!@J0qP$9q0-FdFCKcK`>)nj(IEmZCT5U~YP2>n+DpjU= zxc(!}$3`YiIz4)ukh?FbnEM}ygO1Yyu4a=a8e98hHANa=593y zU0!1h{^{#`cR=?(Tfaw>6)O9UFc(z(g2@gW7NUEh*TmdZmus<^3F9|wZu94&m6dx7 zsZ5%ugt*nb5PTudjYyzWjg%UfVZ79F?X_kbQ-4Cmk`SI2SS!BUd7((TR`(%#Wfvn} z%1`4Rj^Wh;3=<0`txb0aTZqV;LHs()oh+zL`N*ilAdL zxhN4P^@+$>EX!{4IC&ZrL&N$O?Rf6V^|Z0C6^W8+uFwbRqV97GBxQ&d?@bPA}0 zoft6>(BPNz>l+dzk+)}M%FhZIvC{T>;$|`*tZ4?ljZ!VDsCde4+F4#liDto;8|64Q zgNQxkBgF@@Jw5STr8*o=MN4bzpzP;m{jZIbIxbR0n%r$ng)XRCmZ3MCz?JL4k*o|` z`D+fWRgH)NyNjsXCoYpfSdEM*4Gj-lcdTk7rNCIi{x1&N3CvBnklIaUY-o~l2_+Sk zg{5VmRx#CN)F&dB->=(XU)kP_&y9If^`MrQb7K!9O6|?MKpSKKcn56Hmq@u=dO9-P zoUAMhAD?;m`DB{5_g4HFDa?8B?^~}oZmpZHg^sE!M+ajly2@7skL#V*UP+rgbX)BX zqzDzyC6*AGUuIiUWt(6b18A{Gr?0vm^L;xO#LLc5IENN z=kWL95jVdNb7ln(pM8QDk9?}HM_yivfc7as+K@qJhA{Y2xiM>Sozwk>{DGfpb6bPx z=;)X(1{`$<-Z*$c+G#)tjGK!iDSUAjqaX{5kcI}ni2^B}y9Y01Ub{wJwZ}rC(J*{5 zB?&lu#ESm`0m1H%lz~-D4aML#Y8exl+ryswx6&=gx3}UHJf>DYJ_gFgxRe6!=?s`* z(-wrhOKG&!)W2YE;WGHO2}i3vX4y5(@7tNtf9`IYkTgC9L0&;Y`D<$y;5U@0*wAGA zWJO#>5;Y)7e|0d5TfrLXd$KOa!phq9aTW5}|L>nW&oV!3QXD)U~YXSZ;*J z<_6v{B3ZkZ1)e=qFf%ioO5pmCEazhSOsJAA`sYbDye^HMJb9ttFBqc^<8LpDpgFRg zl@1w#h4bfe0(~S%K@+*6{a_xKINOx5j5qxplhq})R-&zI$fasCjz8U=+?Sw9&V;&% zxuFrPZwzA$9zu-=jv&DtMh4S@Ha0dQZ?{yZAQ|d}c=IHxNcJE*1x~eTk>Ed_-DCAk zy66ck9707d3*-HA|3_aVUq%qhAxzPXjTdauGDb#^j9Q!wVHa38FCcnRkg#a4_Q}ZJ z-WT!;3f4GT_{+h^=-&1{m$!Y<40On%ml4?^M_5P6U97~oUuS2Bw#IWLY6`8#rXN1I zS3_05MTqvo}^=_GWsdj}a&nge)gKxmn1D-Run5JCf zb2|R4tNI8h%>2~<-&I;H394Xw_R^KCf zvrnwQ1}*uhsj2S)u%bmFu&mr0orZ|n!BJ#_2?`7Qwew}5_{7D>c6RI*;y#0^L_$Jh zdXJKrFv2i(MJUGxMO5vB$L0kBeAzzDZ|`Va-~?F!8v0^!aZ%X;aqJ@z>%anfWpR;R ze4k7WnZ}KI6N>{9?7+qw4YD7nzJN7k<#~=%w?MO0!@IjBHGb%jh5@(sTHHMi;Tg-^ zpZe<6tH?pieD;w|cfX#717dDCT-5(}^;{AB)2C0Q1m<#<&I5i!%B+Rfwt*@tDpLo~ zs6e;c%S1_Z(e5RF;mgj_O__+FQ&M-xB*MP zzP0+5zd1k-LEi!6nE#j%;UlgNV8ToZ95T!2Y2a<_Im|Y?7_WZttIDdmr($x`1^P0nEhhM+^5vu$d$b8CGu;*yF-KxJBE?fyj%SZ zRV~p5L*UI}^PFfvCF-GrPoII6;(n_Wp;bCkVXK5}m-3a|35|@o`MgWel0C zzKY6y;7uE2Seo@aU%&^HiTXb)OE-OQ1>->SS5m^))zwKP5;3QRir?AUK{ukfKXi0F zlGCzM(5i#gn*zCXua#dfemGmm(V)qO;Gg;L@9#f5Kj-7+MccEbNrTA=o&eZd3heAb zQjMtmWYA}f@ifo9?$@u@9Y-b7syDyOZ^C+sT*IY9i zk)>Mt#ojIDRikLsqeqV<;?glC@jJc1X$m1chbvu6H*|y4FTbm!{*fhI5sX{H<9O}8 zoxQ#FQI0x~@kU7@p=PTY_STEpsi`%wpS*eMr{7d~e5valkZi}q$7$a?yyu)k$~?^?^74rdPDMMb!nLu&~W@C)Ce7Az|V6d(Ed;;$X6MUTx(( zdDRVliKIZ(9WFEtFYU}xOMfT+7ipi3_^1_ezWLwG)Ku^9!=o`ST*01;L>aMPs zgp~eajyG7?_7`tPHJjw25KMr6hOW=|CF1-5uQX`HPbaIceI_V4A20NYy>`+@EuTXUphSO&3~C zPe}K1-~C5V726ZAGOi811*tcqIcf{5s;VNH8kfe#RHxd%s>i6kdZ9&1{pitoMWGFB zagd~_{@uP~UDpsuq6KwU_bwgL9M6{BUaFl{>xLmFrTzvG&&$17JRDH4#|gV!0TpQX z=g$^ZVaE2!+)~|%u`$vLQz{sBgjdugNEkq&Os%KsK}mi0#_&EV$jgg(?xog+GLWA* z<7=N>J#y`A;5;gO%*laS5d7zgfK>5(R619+SUT8-bt_UQvcLig*lU3Upu3o&vVxiG?CKM^r%b=gc-;eJEFe!skO^z2L!SWVVqx&o1w zQDYZ5ndj@7Bk77v(f>6l#?CJUL+FEJrsK^Vm-gL)yV_+! zGDh@Tf5dB~2{IU~wst@uJ3D*6-ID{b>~8}CGFJcP=H)R18>dp1HN%H4kB<9YlI5{= zXD3*R(<4{^%F7eZ_%I?o{GXnv=wfrZR6uJWY(ebOXmM$zH$?Jc`K<)f74r9?Tf#s( zqS(?r}^=a(F5mvFk3kR7ALKR>o|T>nHBkY%VLZ3_au2##+^bMKoe%z^CK-rhc6ZxQe$IxcW!MWKptqj*cHd(qp4)DP{wecBvw(u_vy<7@$l$ zyGX#`C+{;Vt>YdqbopQ8pM7Xd#=AUdniR&kz_@^Pb8-=b8+36gEhz~DJ@;*S+4$T0 zH_vmF6It`rm;6$OF8G9m27zV=e#eYT#Y&AoLx_auNdh47s{a1|Q-x5D2>A#11^)C} z1yPjoUj^j<)7SYD``(-n@kiXd@oraEy8Kbmnf@$jfk|(AmOfsD0 z(>)z?UW;j2`T1MRZEyW5U(qIE|9N(NW#rOk{W%q1{NZtyZN5ELK9;1xXg_qoj|~)6 zK|w*{m_Z3@)B&VLhKOdKa)Qiks%TA*HbuB7**NubRF z^HK+$;L^59uCcC{jnI&~JF`aenRm?1;sNw6_WZfMt1Bxk>g(69(hNy%2aH@{ZV;f2 z10;{`FN_VR%|CmB1EeILNSSb3UMu^DN_JpwKq)0V_q;&_W4d&n&l;^JcD;!Hf02cd zTFBGn6U)HOi3xRB6m-f?_lOL`_nl~(fGNptla=2CcCPW;dElVi{Vsk5T1~vPpqxh{ zF+xK_nbn>*BL(aK&KkK&FzC#uAv7e;|8qebAvX7-yl}&0s*PIXfB*K9DN5|t?)om$ z>CRlZGeXQWwOr-^@6P{CCP)96kR(ptM8WNOUMok+h}A#wcXxNk$(h~c^oja*rxCoHR)^1O-V;!avGjM}P0o%{dXC&`5 zn{4{B;7(7^4SR_u^;?$R4`NDQRQtt^Hd71_5BuJo##TC)pzXS5P0cZW&m z(|06(^gtjI9$6TwWRVK@?6PLza@9ur)^7Kv=5Q!elEyjq;5*C*gpvPlrVbMEl^s6$cE3uyRbF*AHLT|gW zraiAVDzK=iZ|yj_AxiI@YLX*bh6$ELh0*ceh*?Rx>S=;rv9E>23!;ikvI?G=t(2O% z{c}I&5|cJ3VHN(Y{}ebzvIU}UI+$uPFdn%4+Y2CTIRf({H|VQLc0wvLtvg(7o?a!} zI*llbNk#6!uSfGQUcz${GI9znxekVg{72Jy2)(c|H^xG{OjW9~;LHbY z)l79xhK8w*6r=e@+)sCPXFH`NB-D(ImS8EU2O~`A38=WojwZ?1O}&^Tgcm&}6{Nk0 zG-r}Xh{q^{qQcIzJG*jGWx<$xoBBB^y(onTjhILIF1k1L8lq%_$42%JEQ;3>k=(Vq zxG@@*I7za8X<6ED*2ZOfYgVFUcW#uOU|mYgX(@2rD8j~27<$lpycP9A zR?11H!*O#y5#{UTInIW8NcTFOt1lm+s4Q*BI(Xg;Gcf< zu9t4j&47heXMk@KrWxz^R1OghY}k!_E09wAPhQeD_6E=j|WNLkkm z3Cspu(rgP;^8TN4^Fn@YPTiXx@>Zi)YSLA2O%ukq4;I;;AFwAWeo=kLvz}>-m#N6~ zSb^#OXSIlG?3_u0v`=_l`DWY|cG!jy{7({4sT}K>l0i57W`F%KqOg0%`|AY9{lc_f zNOz38glWGeG-)6WpN=jKay8B0*7Fiwa=!47Z2kPxRRgd?O zVPVL2p#io^gn4y3i$Gs_(04_)QM>G+`FYA5VdrEP*@}Uu6hWUR?_V8Epz;MQgMzy4 zEHDg#1D{)FfATtH5c(gJd@sTicSx1LX`Jq5A20-~_c(R&O&9g>uX1bCWP~|@29cMS z|9qzHiicru41`8H86N!HSQ^nZQB~f$!Ocp zIa&eonBAz>F2xsA3bJE5=>}6~+u2$Wzu$-&H0gWcr2vQ}+^>niV!5ul_Otd82b}*p z%z(4Btms%-nhGeGtSDs_yvX;^R4Vrm_P_b0UsEv_m6nxh!Dpr-o5jrH@)OfDYCvKj=emD4+aS-6=wz7GbLN=4f!O%6>E3=;RaRq4ztU_(t|?$;nVq}=c}^j! zPY0G~a;!~1t8SX&iAYH$l-lR_L#o(lz$z&mo>fqA;ZZQ=y8j~4DZJHB=A$Jdjjm9U zkg;k-UrX{m3rTR0;g{Q%WEe5IC-&AHx5{%rNh50b#QN7;dk6p?-|#bhjTODguvM55gOtrvWc*8 z3&UPVADNHxT+ENYv5$sEwne8}o34krEGoN)8&8sGh(COwMM=Gp-LGG&UH7|ShwsfJ z>jwP40B3uo0J*51jo|NZWXj9tt>-JWUnHmM;+~e~feLPInJ}Y&F_Pl%9<90x9nh11 zW_}+*S(pPobNCw-T)33l1HVjg6RB1a;E`b9w@C*#MW01!&PJNtI1QHcO38jL)hZ+R zz=jZFT&N~IO_k*(d)lv>4=;Z3CS?^r@x>6Jx%H!%K8{ zzxiV!PA(@qxOesopzZ$cNKZd2}60kkOB8_`+ zFH|loc8nNG%EakO)e27~d~4cub>)k4i!EjYR154hD4T#R%rwemmo|5#H7a)>+?%q4 z!(-gffuq}L+Q!D?X!AN_uLcWiT5xzl1Q2+Dm;tdx$TlAdKmt2oG1+qZTO(P2jOpSj zz0B%#(j7S&IKO=P5peicS`$_ zCCUBQt*R3zL&z0~`2X=%0VmZHJCKR|KL-WS+|wgKkN>$O8XA+viJ0R7Sx&d{Y@zGG^{^;J1#hJFG_pI- zJV?WmWP9w$$;~bCJ_R=?eJPzWONs>;myZUn#+y^0L8% zmlyor_OC2pSKuQOz$}3Ybxiuf&rmS=c593(aIr)yn<|gk;+e7UW?HT2)`7N4mek?I z3}fu-($DGp_i%7BZpn-FWuaxZwzi_cvgap{r7N@3O7#ZcP(1YC+sNLx@&@)`;5;WI`tA7s7BG!F- z3krF;k5_#wl%@r_jXxv7vW>m7^UP^H)kd()DAi2e%r-||r;MYcqf*tg5v0@pCEf;+ zRuzj3d)j)0)U8F*M*(@p*zKZY39uIi*jQCn)x>cdr4nua7)`M?eYxQcv8<^Dkv;e)~K0S*5 z?a_gKWL?0XPQkb5`58a%2i=|wcZ852f4diyvPI|nt7?%-*cT;5>J0-R5OCQ~TWhGK zop8A@88ie)h}K8b!$?-Y?r*!F3hvHaF`i4_?P?SVl4O^jEIQVm{6g_8!PLQ(jBL0wss_-B+lbZ6vofhJ zD|8VvCT92LAP+kd3%T8(z6&RZp8UEJ&r+4Y7>H$x9o{hZKWzR7U5L0;bGkC7Brb#)kim?mkMonSlD zUsvoC6Z9YdpKaUBS$*HYqQybOuovVniBIcA%N3TKmYtp5*H)kz0ciwwn8Q?g%Uu-k z@vNMj*Ir)fAlU>~xg+na(cU0J{%cd0U#ijw1=>JCjDrv6v@XAlUtrUR2g`lXGTF>b z7z`45T-(kxL$2^1EIdmn)cLOZ9K2*3aLp|--wxamD7cE<9!isy4*3@VHy{&2bphdG z*4M+1-75MFGWP-A3y8hfL)=zrOc}##lk~+jGsx(T7b z#_Z*08GP5FCp7AMyi9fG4A-#Y`@%SSZ&9QWd2ktUxQBz+2zS>|`R z{Iv9}AXFxnv+Hw-YJu0yQ*%N`_c)1TB$m+JrWW39@LjoH!-I?BEU;A>LrG3uwpPac G-Twfpl}P*m diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index d314878cc0..e838d713d4 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -65,7 +65,7 @@ Contacts={ propertyTypeFor:function(obj) { return $(obj).parents('.propertycontainer').first().data('element'); }, - showHideContactInfo:function() { + /*showHideContactInfo:function() { var show = ($('#emaillist li.propertycontainer').length > 0 || $('#phonelist li.propertycontainer').length > 0 || $('#addressdisplay dl.propertycontainer').length > 0); console.log('showHideContactInfo: ' + show); if(show) { @@ -73,8 +73,8 @@ Contacts={ } else { $('#contact_communication').hide(); } - }, - checkListFor:function(obj) { + },*/ + /*checkListFor:function(obj) { var type = $(obj).parents('.propertycontainer').first().data('element'); console.log('checkListFor: ' + type); switch (type) { @@ -101,7 +101,7 @@ Contacts={ case 'BDAY': break; } - }, + },*/ loading:function(obj, state) { if(state) { $(obj).addClass('loading'); @@ -137,18 +137,14 @@ Contacts={ $(this).find('.add').fadeOut(500); } );*/ - $('.button,.action').tipsy(); - $('#contacts_deletecard').tipsy({gravity: 'ne'}); - $('#contacts_downloadcard').tipsy({gravity: 'ne'}); //$('#fn').jec(); $('#fn_select').combobox({ 'id': 'fn', 'name': 'value', - 'classes': ['contacts_property'], + 'classes': ['contacts_property', 'huge', 'tip', 'float'], + 'attributes': {'placeholder': t('contacts', 'Enter name')}, 'title': t('contacts', 'Format custom, Short name, Full name, Reverse or Reverse with comma')}); //$('.jecEditableOption').attr('title', t('contacts','Custom')); - $('#fn').tipsy(); - $('#contacts_details_photo_wrapper').tipsy(); $('#bday').datepicker({ dateFormat : 'dd-mm-yy' }); @@ -200,6 +196,9 @@ Contacts={ } ] ); $('#categories').multiple_autocomplete({source: categories}); + $('.button,.action,.tip').tipsy(); + $('#contacts_deletecard').tipsy({gravity: 'ne'}); + $('#contacts_downloadcard').tipsy({gravity: 'ne'}); Contacts.UI.loadListHandlers(); }, Card:{ @@ -267,7 +266,7 @@ Contacts={ import:function(){ Contacts.UI.notImplemented(); }, - add:function(n, fn, aid){ // add a new contact + add:function(n, fn, aid, isnew){ // add a new contact console.log('Add contact: ' + n + ', ' + fn + ' ' + aid); $.post(OC.filePath('contacts', 'ajax', 'addcontact.php'), { n: n, fn: fn, aid: aid }, function(jsondata) { @@ -291,7 +290,15 @@ Contacts={ if(!added) { $('#leftcontent ul').append(item); } - + if(isnew) { + Contacts.UI.Card.addProperty('EMAIL'); + Contacts.UI.Card.addProperty('TEL'); + Contacts.UI.Card.addProperty('BDAY'); + Contacts.UI.Card.addProperty('NICKNAME'); + Contacts.UI.Card.addProperty('ORG'); + Contacts.UI.Card.addProperty('CATEGORIES'); + $('#fn').focus(); + } } else{ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); @@ -356,7 +363,7 @@ Contacts={ return false; }, loadContact:function(jsondata){ - $('#contact_communication').hide(); + //$('#contact_communication').hide(); this.data = jsondata; this.id = this.data.id; $('#rightcontent').data('id',this.id); @@ -368,7 +375,6 @@ Contacts={ this.loadPhones(); this.loadAddresses(); this.loadSingleProperties(); - // TODO: load NOTE ;-) if(this.data.NOTE) { $('#note').data('checksum', this.data.NOTE[0]['checksum']); $('#note').find('textarea').val(this.data.NOTE[0]['value']); @@ -376,7 +382,7 @@ Contacts={ } else { $('#note').data('checksum', ''); $('#note').find('textarea').val(''); - $('#note').hide(); + //$('#note').hide(); } }, loadSingleProperties:function() { @@ -521,17 +527,18 @@ Contacts={ },*/ editNew:function(){ // add a new contact this.id = ''; this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = ''; - $.getJSON(OC.filePath('contacts', 'ajax', 'newcontact.php'),{},function(jsondata){ + Contacts.UI.Card.add(';;;;', '', '', true); + /*$.getJSON(OC.filePath('contacts', 'ajax', 'newcontact.php'),{},function(jsondata){ if(jsondata.status == 'success'){ id = ''; $('#rightcontent').data('id',''); $('#rightcontent').html(jsondata.data.page); - Contacts.UI.Card.editName(); + //Contacts.UI.Card.editName(); } else { OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } - }); + });*/ }, savePropertyInternal:function(name, fields, oldchecksum, checksum){ // TODO: Add functionality for new fields. @@ -627,8 +634,8 @@ Contacts={ },'json'); } }, - addProperty:function(obj){ - var type = $(obj).data('type'); + addProperty:function(type){ + //var type = $(obj).data('type'); console.log('addProperty:' + type); switch (type) { case 'PHOTO': @@ -647,21 +654,21 @@ Contacts={ $('#emails').show(); } Contacts.UI.Card.addMail(); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'TEL': if($('#phonelist>li').length == 1) { $('#phones').show(); } Contacts.UI.Card.addPhone(); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'ADR': if($('#addressdisplay>dl').length == 1) { $('#addresses').show(); } Contacts.UI.Card.editAddress('new', true); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'NICKNAME': case 'ORG': @@ -682,8 +689,8 @@ Contacts={ if(jsondata.status == 'success'){ if(type == 'list') { Contacts.UI.propertyContainerFor(obj).remove(); - Contacts.UI.showHideContactInfo(); - Contacts.UI.checkListFor(obj); + //Contacts.UI.showHideContactInfo(); + //Contacts.UI.checkListFor(obj); } else if(type == 'single') { var proptype = Contacts.UI.propertyTypeFor(obj); console.log('deleteProperty, hiding: ' + proptype); @@ -718,8 +725,8 @@ Contacts={ } else { // Property hasn't been saved so there's nothing to delete. if(type == 'list') { Contacts.UI.propertyContainerFor(obj).remove(); - Contacts.UI.showHideContactInfo(); - Contacts.UI.checkListFor(obj); + //Contacts.UI.showHideContactInfo(); + //Contacts.UI.checkListFor(obj); } else if(type == 'single') { var proptype = Contacts.UI.propertyTypeFor(obj); console.log('deleteProperty, hiding: ' + proptype); @@ -891,7 +898,7 @@ Contacts={ if(isnew) { container.remove(); } - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); } }, close : function(event, ui) { @@ -900,7 +907,7 @@ Contacts={ if(isnew) { container.remove(); } - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); }/*, open : function(event, ui) { // load 'ADR' property - maybe :-P @@ -973,7 +980,7 @@ Contacts={ } }, loadPhoto:function(force){ - if(this.data.PHOTO||force==true) { + //if(this.data.PHOTO||force==true) { $.getJSON('ajax/loadphoto.php',{'id':this.id},function(jsondata){ if(jsondata.status == 'success'){ //alert(jsondata.data.page); @@ -986,11 +993,11 @@ Contacts={ }); $('#file_upload_form').show(); $('#contacts_propertymenu a[data-type="PHOTO"]').parent().hide(); - } else { + /*} else { $('#contacts_details_photo_wrapper').empty(); $('#file_upload_form').hide(); $('#contacts_propertymenu a[data-type="PHOTO"]').parent().show(); - } + }*/ }, editPhoto:function(id, tmp_path){ //alert('editPhoto: ' + tmp_path); @@ -1430,7 +1437,8 @@ $(document).ready(function(){ } }); $('#contacts_propertymenu a').live('click',function(){ - Contacts.UI.Card.addProperty(this); + var type = $(this).data('type'); + Contacts.UI.Card.addProperty(type); $('#contacts_propertymenu').hide(); }); }); diff --git a/apps/contacts/js/jquery.combobox.js b/apps/contacts/js/jquery.combobox.js index 6da4ecb514..f46d7c14c1 100644 --- a/apps/contacts/js/jquery.combobox.js +++ b/apps/contacts/js/jquery.combobox.js @@ -72,17 +72,10 @@ .appendTo( ul ); }; - this.button = $( "" ) + /*this.button = $( "" ) .attr( "tabIndex", -1 ) .attr( "title", "Show All Items" ) .insertAfter( input ) - /*.button({ - icons: { - primary: "ui-icon-triangle-1-s" - }, - text: false - }) - .removeClass( "ui-corner-all" )*/ .addClass('svg') .addClass('action') .addClass('combo-button') @@ -99,7 +92,7 @@ // pass empty string as value to search for, displaying all results input.autocomplete( "search", "" ); input.focus(); - }); + });*/ $.each(this.options, function(key, value) { self._setOption(key, value); }); @@ -123,17 +116,23 @@ case "id": this.options['id'] = value; this.input.attr('id', value); - break; + break; case "name": this.options['name'] = value; this.input.attr('name', value); - break; + break; + case "attributes": + var input = this.input; + $.each(this.options['attributes'], function(key, value) { + input.attr(key, value); + }); + break; case "classes": var input = this.input; $.each(this.options['classes'], function(key, value) { input.addClass(value); }); - break; + break; } // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget $.Widget.prototype._setOption.apply( this, arguments ); diff --git a/apps/contacts/js/jquery.multi-autocomplete.js b/apps/contacts/js/jquery.multi-autocomplete.js index 7607de3f91..e1c5d63dc5 100644 --- a/apps/contacts/js/jquery.multi-autocomplete.js +++ b/apps/contacts/js/jquery.multi-autocomplete.js @@ -62,7 +62,7 @@ return false; } }); - this.button = $( "" ) + /*this.button = $( "" ) .attr( "tabIndex", -1 ) .attr( "title", "Show All Items" ) .insertAfter( this.element ) @@ -86,7 +86,7 @@ // pass empty string as value to search for, displaying all results self.element.autocomplete( "search", "" ); self.element.focus(); - }); + });*/ }, }); })( jQuery ); diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index d243c2b5e1..961ce693e6 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -21,12 +21,12 @@ $id = isset($_['id']) ? $_['id'] : '';
-
+
-
+ +
+ -
-
+
-
- -
-
- -
+ + + + +
- + - + - - - + + +
-
- +
+
+
+ + +
+
+
+
From fc555b48ec0f5600a7f9ae25354ffaca2e0533de Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 3 Apr 2012 07:29:47 +0200 Subject: [PATCH 181/302] Contacts: Added autocomplete using geonames.org. --- apps/contacts/css/contacts.css | 1 + apps/contacts/js/contacts.js | 88 +++++++++++++++++++++++++++- apps/contacts/templates/settings.php | 1 + 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 9d238c36f3..4e7d8c285a 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -28,6 +28,7 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } .form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } .loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } +.ui-autocomplete-loading { background: url('../../../core/img/loading.gif') right center no-repeat; } .float { float: left; } .listactions { height: 1em; width:60px; float: left; clear: right; } .add,.edit,.delete,.mail, .globe { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; display: none; } diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index e838d713d4..b72f3c9a67 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -908,10 +908,92 @@ Contacts={ container.remove(); } //Contacts.UI.showHideContactInfo(); - }/*, + }, open : function(event, ui) { - // load 'ADR' property - maybe :-P - }*/ + $( "#adr_city" ).autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "http://ws.geonames.org/searchJSON", + dataType: "jsonp", + data: { + featureClass: "P", + style: "full", + maxRows: 12, + name_startsWith: request.term + }, + success: function( data ) { + response( $.map( data.geonames, function( item ) { + /*for(var key in item) { + console.log(key + ': ' + item[key]); + }*/ + return { + label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName, + value: item.name, + country: item.countryName + } + })); + } + }); + }, + minLength: 2, + select: function( event, ui ) { + if(ui.item) { + $('#adr_country').val(ui.item.country); + } + /*log( ui.item ? + "Selected: " + ui.item.label : + "Nothing selected, input was " + this.value);*/ + }, + open: function() { + $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" ); + }, + close: function() { + $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" ); + } + }); + $( "#adr_country" ).autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "http://ws.geonames.org/searchJSON", + dataType: "jsonp", + data: { + /*featureClass: "A",*/ + featureCode: "PCLI", + /*countryBias: "true",*/ + /*style: "full",*/ + maxRows: 12, + name_startsWith: request.term + }, + success: function( data ) { + response( $.map( data.geonames, function( item ) { + for(var key in item) { + console.log(key + ': ' + item[key]); + } + return { + label: item.name, + value: item.name + } + })); + } + }); + }, + minLength: 2, + select: function( event, ui ) { + /*if(ui.item) { + $('#adr_country').val(ui.item.country); + } + log( ui.item ? + "Selected: " + ui.item.label : + "Nothing selected, input was " + this.value);*/ + }, + open: function() { + $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" ); + }, + close: function() { + $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" ); + } + }); + } }); } else { alert(jsondata.data.message); diff --git a/apps/contacts/templates/settings.php b/apps/contacts/templates/settings.php index f56de0ec8b..d77c4d3802 100644 --- a/apps/contacts/templates/settings.php +++ b/apps/contacts/templates/settings.php @@ -9,4 +9,5 @@
/principals//
+ Powered by geonames.org webservice From dd4e577f6db422aed3d528d12a8e77de9c3cfaef Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 3 Apr 2012 08:13:10 +0200 Subject: [PATCH 182/302] Contacts: l11n for geo autocomplete. Not sure if it works though. --- apps/contacts/js/contacts.js | 4 +++- apps/contacts/templates/index.php | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index b72f3c9a67..cc4da5837e 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -919,6 +919,7 @@ Contacts={ featureClass: "P", style: "full", maxRows: 12, + lang: lang, name_startsWith: request.term }, success: function( data ) { @@ -937,7 +938,7 @@ Contacts={ }, minLength: 2, select: function( event, ui ) { - if(ui.item) { + if(ui.item && $('#adr_country').val().trim().length == 0) { $('#adr_country').val(ui.item.country); } /*log( ui.item ? @@ -961,6 +962,7 @@ Contacts={ featureCode: "PCLI", /*countryBias: "true",*/ /*style: "full",*/ + lang: lang, maxRows: 12, name_startsWith: request.term }, diff --git a/apps/contacts/templates/index.php b/apps/contacts/templates/index.php index af159ce9c6..d68dd68f60 100644 --- a/apps/contacts/templates/index.php +++ b/apps/contacts/templates/index.php @@ -1,6 +1,7 @@
From cd05dfb9430da93f2584d928ef963c625562c8fa Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Tue, 3 Apr 2012 22:17:34 +0200 Subject: [PATCH 183/302] adding icons to filepicker --- core/js/oc-dialogs.js | 2 +- files/ajax/rawlist.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index d40c433bda..7ae9bc9d2a 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -204,7 +204,7 @@ OCdialogs = { var entry_template = '
*NAME*
*LASTMODDATE*
'; var names = ''; for (var a in r.data) { - names += entry_template.replace('*LASTMODDATE*', OC.mtime2date(r.data[a].mtime)).replace('*NAME*', r.data[a].name).replace('*MIMETYPEICON*', OC.webroot+'/core/img/filetypes/'+(r.data[a].type=='dir'?'folder':r.data[a].mimetype.replace('/','-'))+'.png').replace('*ENTRYNAME*', r.data[a].name).replace('*ENTRYTYPE*', r.data[a].type); + names += entry_template.replace('*LASTMODDATE*', OC.mtime2date(r.data[a].mtime)).replace('*NAME*', r.data[a].name).replace('*MIMETYPEICON*', r.data[a].mimetype_icon).replace('*ENTRYNAME*', r.data[a].name).replace('*ENTRYTYPE*', r.data[a].type); } $(dialog_content_id + ' #filelist').html(names); }, diff --git a/files/ajax/rawlist.php b/files/ajax/rawlist.php index 0abe81e672..88ba48a6c4 100644 --- a/files/ajax/rawlist.php +++ b/files/ajax/rawlist.php @@ -5,6 +5,7 @@ $RUNTIME_APPTYPES=array('filesystem'); // Init owncloud require_once('../../lib/base.php'); +require_once('../../lib/template.php'); OC_JSON::checkLoggedIn(); @@ -15,6 +16,7 @@ $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; $files = array(); foreach( OC_Files::getdirectorycontent( $dir ) as $i ){ $i["date"] = OC_Util::formatDate($i["mtime"] ); + $i['mimetype_icon'] = $i['type'] == 'dir' ? mimetype_icon('dir'): mimetype_icon($i['mimetype']); $files[] = $i; } From 95c2ac5d585bfd1106b173c00b4c29719c08b25f Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 3 Apr 2012 21:14:55 +0000 Subject: [PATCH 184/302] Dont typecast variables as integers --- core/lostpassword/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lostpassword/index.php b/core/lostpassword/index.php index da0428e3ce..9529c0c957 100644 --- a/core/lostpassword/index.php +++ b/core/lostpassword/index.php @@ -12,7 +12,7 @@ require_once('../../lib/base.php'); // Someone lost their password: if (isset($_POST['user'])) { if (OC_User::userExists($_POST['user'])) { - $token = sha1($_POST['user']+uniqId()); + $token = sha1($_POST['user'].uniqId()); OC_Preferences::setValue($_POST['user'], 'owncloud', 'lostpassword', $token); $email = OC_Preferences::getValue($_POST['user'], 'settings', 'email', ''); if (!empty($email)) { From acdce2b1e01f7c0a77b7e7949540e1b0ba94efd1 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Tue, 3 Apr 2012 22:31:34 +0000 Subject: [PATCH 185/302] Check blacklist before saving to filesystem --- lib/base.php | 3 +++ lib/filesystem.php | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/base.php b/lib/base.php index a4a94e8696..83dd0c98f4 100644 --- a/lib/base.php +++ b/lib/base.php @@ -365,6 +365,9 @@ class OC{ OC_App::loadApps(); } } + + // Check for blacklisted files + OC_Hook::connect('OC_Filesystem','write','OC_Filesystem','isBlacklisted'); //make sure temporary files are cleaned up register_shutdown_function(array('OC_Helper','cleanTmp')); diff --git a/lib/filesystem.php b/lib/filesystem.php index 12905d189f..b6909f5acd 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -298,6 +298,19 @@ class OC_Filesystem{ } return true; } + + /** + * checks if a file is blacklsited for storage in the filesystem + * @param array $data from hook + */ + static public function isBlacklisted($data){ + $blacklist = array('.htaccess'); + $filename = strtolower(basename($data['path'])); + if(in_array($filename,$blacklist)){ + $data['run'] = false; + } + } + /** * following functions are equivilent to their php buildin equivilents for arguments/return values. */ From 60e3b563e26478eab257413b5cac9b3f619570ac Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 4 Apr 2012 12:41:32 +0200 Subject: [PATCH 186/302] webdav needs to load authentication apps --- apps/user_ldap/appinfo/info.xml | 3 +++ apps/user_openid/appinfo/info.xml | 3 +++ files/webdav.php | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/user_ldap/appinfo/info.xml b/apps/user_ldap/appinfo/info.xml index 9a6ee1436f..99830dd1ff 100644 --- a/apps/user_ldap/appinfo/info.xml +++ b/apps/user_ldap/appinfo/info.xml @@ -7,4 +7,7 @@ AGPL Dominik Schmidt 2 + + + diff --git a/apps/user_openid/appinfo/info.xml b/apps/user_openid/appinfo/info.xml index 37be15abfd..721db1877e 100644 --- a/apps/user_openid/appinfo/info.xml +++ b/apps/user_openid/appinfo/info.xml @@ -7,4 +7,7 @@ AGPL Robin Appelman 2 + + + diff --git a/files/webdav.php b/files/webdav.php index 1120973787..25e3302447 100644 --- a/files/webdav.php +++ b/files/webdav.php @@ -27,7 +27,7 @@ $RUNTIME_NOSETUPFS = true; // only need filesystem apps -$RUNTIME_APPTYPES=array('filesystem'); +$RUNTIME_APPTYPES=array('filesystem','authentication'); require_once('../lib/base.php'); From 85f9869f6925ef52c1015916bbc28e13c15abc73 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Wed, 4 Apr 2012 13:17:03 +0000 Subject: [PATCH 187/302] Make the token really random --- core/lostpassword/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lostpassword/index.php b/core/lostpassword/index.php index 9529c0c957..a9b7d10804 100644 --- a/core/lostpassword/index.php +++ b/core/lostpassword/index.php @@ -12,7 +12,7 @@ require_once('../../lib/base.php'); // Someone lost their password: if (isset($_POST['user'])) { if (OC_User::userExists($_POST['user'])) { - $token = sha1($_POST['user'].uniqId()); + $token = sha1($_POST['user'].md5(uniqid(rand(), true))); OC_Preferences::setValue($_POST['user'], 'owncloud', 'lostpassword', $token); $email = OC_Preferences::getValue($_POST['user'], 'settings', 'email', ''); if (!empty($email)) { From 5b4a30367235a27f21d44b98eaae953e1cec1eac Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Thu, 5 Apr 2012 21:29:54 +0200 Subject: [PATCH 188/302] loading screen for filepicker --- core/css/styles.css | 1 + core/js/oc-dialogs.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/css/styles.css b/core/css/styles.css index 1c50df9e58..0eb299a899 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -137,3 +137,4 @@ a.bookmarklet { background-color: #ddd; border:1px solid #ccc; padding: 5px;padd #dirtree {width: 100%;} #filelist {height: 270px; overflow:scroll; background-color: white;} .filepicker_element_selected { background-color: lightblue;} +.filepicker_loader {height: 270px; width: 100%; background-color: #333; opacity: 0.3; visibility: visible; position:absolute; top:0; left:0; text-align:center; padding-top: 150px;} diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index 7ae9bc9d2a..e027ffb510 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -100,7 +100,7 @@ OCdialogs = { filepicker:function(title, callback, multiselect, mimetype_filter, modal) { var c_name = 'oc-dialog-'+OCdialogs.dialogs_counter+'-content'; var c_id = '#'+c_name; - var d = '
'; + var d = '
'; if (!modal) modal = false; if (!multiselect) multiselect = false; $('body').append(d); @@ -207,6 +207,7 @@ OCdialogs = { names += entry_template.replace('*LASTMODDATE*', OC.mtime2date(r.data[a].mtime)).replace('*NAME*', r.data[a].name).replace('*MIMETYPEICON*', r.data[a].mimetype_icon).replace('*ENTRYNAME*', r.data[a].name).replace('*ENTRYTYPE*', r.data[a].type); } $(dialog_content_id + ' #filelist').html(names); + $(dialog_content_id + ' .filepicker_loader').css('visibility', 'hidden'); }, handleTreeListSelect:function(event) { var newval = parseInt($(this).val()); @@ -220,6 +221,7 @@ OCdialogs = { var path = ''; $(this).children().each(function(i, element) { if (skip_first) {skip_first = false; return; }path += '/'+$(element).text(); }); $(event.data.dcid).data('path', path); + $(event.data.dcid + ' .filepicker_loader').css('visibility', 'visible'); $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: path}, function(r){OC.dialogs.fillFilePicker(r, event.data.dcid)}); }, // this function is in early development state, please dont use it unlsess you know what you are doing @@ -237,6 +239,7 @@ OCdialogs = { $(dcid + ' #dirtree option:last').removeAttr('selected'); var newval = parseInt($(dcid + ' #dirtree option:last').val())+1; $(dcid + ' #dirtree').append(''); + $(dcid + ' .filepicker_loader').css('visibility', 'visible'); $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: p}, function(r){OC.dialogs.fillFilePicker(r, dcid)}); } }; From fe3d3be399f7b64b3f81c4f8fcd932b2d810ee45 Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Thu, 5 Apr 2012 22:25:58 +0200 Subject: [PATCH 189/302] filepicker loading style fixup --- core/css/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/css/styles.css b/core/css/styles.css index 0eb299a899..9d9f6d9830 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -137,4 +137,4 @@ a.bookmarklet { background-color: #ddd; border:1px solid #ccc; padding: 5px;padd #dirtree {width: 100%;} #filelist {height: 270px; overflow:scroll; background-color: white;} .filepicker_element_selected { background-color: lightblue;} -.filepicker_loader {height: 270px; width: 100%; background-color: #333; opacity: 0.3; visibility: visible; position:absolute; top:0; left:0; text-align:center; padding-top: 150px;} +.filepicker_loader {height: 120px; width: 100%; background-color: #333; opacity: 0.3; visibility: visible; position:absolute; top:0; left:0; text-align:center; padding-top: 150px;} From 96c99125da5fe74d4a8fa329869cca7f775805cf Mon Sep 17 00:00:00 2001 From: Bartek Przybylski Date: Thu, 5 Apr 2012 23:18:44 +0200 Subject: [PATCH 190/302] mimetype filter for filepicker --- core/js/oc-dialogs.js | 8 ++++---- files/ajax/rawlist.php | 3 ++- lib/filecache.php | 6 +++--- lib/files.php | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index e027ffb510..a3aa1e8c14 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -107,8 +107,8 @@ OCdialogs = { $(c_id + ' #dirtree').focus(function() { var t = $(this); t.data('oldval', t.val())}) .change({dcid: c_id}, OC.dialogs.handleTreeListSelect); $(c_id).ready(function(){ - $.getJSON(OC.webroot+'/files/ajax/rawlist.php', function(r){OC.dialogs.fillFilePicker(r, c_id, callback)}); - }).data('multiselect', multiselect); + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {mimetype: mimetype_filter} ,function(r){OC.dialogs.fillFilePicker(r, c_id, callback)}); + }).data('multiselect', multiselect).data('mimetype',mimetype_filter); // build buttons var b = [ {text: t('dialogs', 'Choose'), click: function(){ @@ -222,7 +222,7 @@ OCdialogs = { $(this).children().each(function(i, element) { if (skip_first) {skip_first = false; return; }path += '/'+$(element).text(); }); $(event.data.dcid).data('path', path); $(event.data.dcid + ' .filepicker_loader').css('visibility', 'visible'); - $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: path}, function(r){OC.dialogs.fillFilePicker(r, event.data.dcid)}); + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: path, mimetype: $(event.data.dcid).data('mimetype')}, function(r){OC.dialogs.fillFilePicker(r, event.data.dcid)}); }, // this function is in early development state, please dont use it unlsess you know what you are doing handlePickerClick:function(element, name, dcid) { @@ -240,6 +240,6 @@ OCdialogs = { var newval = parseInt($(dcid + ' #dirtree option:last').val())+1; $(dcid + ' #dirtree').append(''); $(dcid + ' .filepicker_loader').css('visibility', 'visible'); - $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: p}, function(r){OC.dialogs.fillFilePicker(r, dcid)}); + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: p, mimetype: $(dcid).data('mimetype')}, function(r){OC.dialogs.fillFilePicker(r, dcid)}); } }; diff --git a/files/ajax/rawlist.php b/files/ajax/rawlist.php index 88ba48a6c4..e02c5b6273 100644 --- a/files/ajax/rawlist.php +++ b/files/ajax/rawlist.php @@ -11,10 +11,11 @@ OC_JSON::checkLoggedIn(); // Load the files $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; +$mimetype = isset($_GET['mimetype']) ? $_GET['mimetype'] : ''; // make filelist $files = array(); -foreach( OC_Files::getdirectorycontent( $dir ) as $i ){ +foreach( OC_Files::getdirectorycontent( $dir, $mimetype ) as $i ){ $i["date"] = OC_Util::formatDate($i["mtime"] ); $i['mimetype_icon'] = $i['type'] == 'dir' ? mimetype_icon('dir'): mimetype_icon($i['mimetype']); $files[] = $i; diff --git a/lib/filecache.php b/lib/filecache.php index 4a4183cbdb..cdd91dcbfa 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -240,7 +240,7 @@ class OC_FileCache{ * - encrypted * - versioned */ - public static function getFolderContent($path,$root=''){ + public static function getFolderContent($path,$root='',$mimetype_filter=''){ if(self::isUpdated($path,$root)){ self::updateFolder($path,$root); } @@ -252,8 +252,8 @@ class OC_FileCache{ } $path=$root.$path; $parent=self::getFileId($path); - $query=OC_DB::prepare('SELECT name,ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE parent=?'); - $result=$query->execute(array($parent))->fetchAll(); + $query=OC_DB::prepare('SELECT name,ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE parent=? AND (mimetype LIKE ? OR mimetype = ?)'); + $result=$query->execute(array($parent, $mimetype_filter.'%', 'httpd/unix-directory'))->fetchAll(); if(is_array($result)){ return $result; }else{ diff --git a/lib/files.php b/lib/files.php index e7bfbbc19b..a68c29ad98 100644 --- a/lib/files.php +++ b/lib/files.php @@ -32,11 +32,11 @@ class OC_Files { * get the content of a directory * @param dir $directory */ - public static function getDirectoryContent($directory){ + public static function getDirectoryContent($directory, $mimetype_filter = ''){ if(strpos($directory,OC::$CONFIG_DATADIRECTORY)===0){ $directory=substr($directory,strlen(OC::$CONFIG_DATADIRECTORY)); } - $files=OC_FileCache::getFolderContent($directory); + $files=OC_FileCache::getFolderContent($directory, '', $mimetype_filter); foreach($files as &$file){ $file['directory']=$directory; $file['type']=($file['mimetype']=='httpd/unix-directory')?'dir':'file'; From 2fe0716cb4ba74d338d6af04a8b02fbb8eb1f903 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 6 Apr 2012 20:52:41 +0000 Subject: [PATCH 191/302] Show nice errors --- apps/user_migrate/admin.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index 61b83c2a14..280f7b7196 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -36,12 +36,18 @@ if (isset($_POST['user_import'])) { $from = $_FILES['owncloud_import']['tmp_name']; $to = get_temp_dir().'/'.$importname.'.zip'; if( !move_uploaded_file( $from, $to ) ){ + $errors[] = array('error'=>'Failed to move the uploaded file','hint'=>'Try checking the permissions of the '.get_temp_dir().' dir.'); OC_Log::write( 'user_migrate', "Failed to copy the uploaded file", OC_Log::ERROR ); - exit(); + $t = new OC_Template( '', 'error', 'user' ); + $t->assign('errors',$errors); + $t->fetchPage(); } if( !$appsstatus = OC_Migrate::import( $to, 'user' ) ){ - die( 'failed to to import' ); + $errors[] = array('error'=>'There was an error while importing the user!','hint'=>'Please check the logs for a more detailed explaination'); + $t = new OC_Template( '', 'error', 'user' ); + $t->assign('errors',$errors); + $t->fetchPage(); } else { // Check import status foreach( $appsstatus as $app => $status ){ @@ -57,10 +63,20 @@ if (isset($_POST['user_import'])) { // Any problems? if( isset( $notsupported ) || isset( $failed ) ){ if( count( $failed ) > 0 ){ - die( 'Some apps failed to import. View the log please.' ); + $errors[] = array('error'=>'Some app data failed to import','hint'=>'App data for: '.implode(', ', $failed).' failed to import.'); + $t = new OC_Template( '', 'error', 'user' ); + $t->assign('errors',$errors); + $t->fetchPage(); } else if( count( $notsupported ) > 0 ){ - die( 'Some apps were not found in this owncloud instance and therefore could not be installed' ); + $errors[] = array('error'=>'Some app data could not be imported, as the apps are not installed on this instance','hint'=>'App data for: '.implode(', ', $notsupported).' failed to import as they were not found. Please install the apps and try again'); + $t = new OC_Template( '', 'error', 'user' ); + $t->assign('errors',$errors); + $t->fetchPage(); } + } else { + // Went swimmingly! + $tmpl = new OC_Template('user_migrate', 'admin'); + return $tmpl->fetchPage(); } } From 22466010fc8d5a7d5cad876b8603f2bffee58182 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 6 Apr 2012 21:37:28 +0000 Subject: [PATCH 192/302] Provide undo when closing editor with unsaved changes --- apps/files_texteditor/js/editor.js | 100 ++++++++++++++++------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/apps/files_texteditor/js/editor.js b/apps/files_texteditor/js/editor.js index 1e136fe68e..3d98a91c05 100644 --- a/apps/files_texteditor/js/editor.js +++ b/apps/files_texteditor/js/editor.js @@ -76,7 +76,7 @@ function showControls(filename,writeperms){ function bindControlEvents(){ $("#editor_save").die('click',doFileSave).live('click',doFileSave); - $('#editor_close').die('click',closeBtnClick).live('click',closeBtnClick); + $('#editor_close').die('click',hideFileEditor).live('click',hideFileEditor); $('#gotolineval').die('keyup', goToLine).live('keyup', goToLine); $('#editorsearchval').die('keyup', doSearch).live('keyup', doSearch); $('#clearsearchbtn').die('click', resetSearch).live('click', resetSearch); @@ -183,6 +183,8 @@ function giveEditorFocus(){ // Loads the file editor. Accepts two parameters, dir and filename. function showFileEditor(dir,filename){ + // Delete any old editors + $('#editor').remove(); if(!editorIsShown()){ // Loads the file editor and display it. $('#content').append('
'); @@ -235,52 +237,56 @@ function showFileEditor(dir,filename){ } } -function closeBtnClick(){ - if($('#editor').attr('data-edited')=='true'){ - // Show confirm - OC.dialogs.confirm(t('files_texteditor','You have unsaved changes that will be lost! Do you still want to close?'),t('files_texteditor','Really close?'),function(close){ - if(close){ - hideFileEditor(); - } - }); - } else { - hideFileEditor(); - } -} - // Fades out the editor. function hideFileEditor(){ - // Fades out editor controls - $('#editorcontrols').fadeOut('slow',function(){ - $(this).remove(); - $(".crumb:last").addClass('last'); - }); - // Fade out editor - $('#editor').fadeOut('slow', function(){ - $(this).remove(); - // Reset document title - document.title = "ownCloud"; - var editorhtml = '
'; - $('table').after(editorhtml); - $('.actions,#file_access_panel').fadeIn('slow'); - $('table').fadeIn('slow'); - }); - is_editor_shown = false; + if($('#editor').attr('data-edited') == 'true'){ + // Hide, not remove + $('#editorcontrols').fadeOut('slow',function(){ + // Check if there is a folder in the breadcrumb + if($('.crumb.ui-droppable').length){ + $('.crumb.ui-droppable:last').addClass('last'); + } + }); + // Fade out editor + $('#editor').fadeOut('slow', function(){ + // Reset document title + document.title = "ownCloud"; + $('.actions,#file_access_panel').fadeIn('slow'); + $('table').fadeIn('slow'); + }); + $('#notification').text(t('files_texteditor','There were unsaved changes, click here to go back')); + $('#notification').data('reopeneditor',true); + $('#notification').fadeIn(); + is_editor_shown = false; + } else { + // Remove editor + $('#editorcontrols').fadeOut('slow',function(){ + $(this).remove(); + $(".crumb:last").addClass('last'); + }); + // Fade out editor + $('#editor').fadeOut('slow', function(){ + $(this).remove(); + // Reset document title + document.title = "ownCloud"; + $('.actions,#file_access_panel').fadeIn('slow'); + $('table').fadeIn('slow'); + }); + is_editor_shown = false; + } } -// Keyboard Shortcuts -var ctrlBtn = false; +// Reopens the last document +function reopenEditor(){ + $('.actions,#file_action_panel').fadeOut('slow'); + $('table').fadeOut('slow', function(){ + $('#controls .last').not('#breadcrumb_file').removeClass('last'); + $('#editor').fadeIn('fast'); + $('#editorcontrols').fadeIn('fast', function(){ -// TODO fix detection of ctrl keyup -// returns true if ctrl+s or cmd+s is being pressed -function checkForSaveKeyPress(e){ - if(e.which == 17 || e.which == 91) ctrlBtn=true; - if(e.which == 83 && ctrlBtn == true) { - e.preventDefault(); - $('#editor_save').trigger('click'); - return false; - - } + }); + }); + is_editor_shown = true; } // resizes the editor window @@ -313,7 +319,11 @@ $(document).ready(function(){ // Binds the file save and close editor events, and gotoline button bindControlEvents(); $('#editor').remove(); - // Binds the save keyboard shortcut events - //$(document).unbind('keydown').bind('keydown',checkForSaveKeyPress); - + $('#notification').click(function(){ + if($('#notification').data('reopeneditor')) + { + reopenEditor(); + } + $('#notification').fadeOut(); + }); }); From a098c7c6858a7c2c646e38d1ba150b13821f4085 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Fri, 6 Apr 2012 21:38:38 +0000 Subject: [PATCH 193/302] Give editor focus after saving --- apps/files_texteditor/js/editor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/files_texteditor/js/editor.js b/apps/files_texteditor/js/editor.js index 3d98a91c05..016632c997 100644 --- a/apps/files_texteditor/js/editor.js +++ b/apps/files_texteditor/js/editor.js @@ -174,6 +174,7 @@ function doFileSave(){ },'json'); } } + giveEditorFocus(); }; // Gives the editor focus From 42e110b49cc2efa63a46f39289f1bfb3e7dbca6f Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 7 Apr 2012 13:07:44 +0000 Subject: [PATCH 194/302] Update url --- settings/templates/apps.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 27133e9e67..1e49b4c892 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -5,7 +5,7 @@ */?>
    From 660951dc64087e7ebedaad5c0ae1104d8dd4d0cf Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 7 Apr 2012 13:32:00 +0000 Subject: [PATCH 195/302] Add some filetypes --- apps/files_texteditor/js/editor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/files_texteditor/js/editor.js b/apps/files_texteditor/js/editor.js index 016632c997..bc8a20408c 100644 --- a/apps/files_texteditor/js/editor.js +++ b/apps/files_texteditor/js/editor.js @@ -22,9 +22,11 @@ function setSyntaxMode(ext){ filetype["css"] = "css"; filetype["groovy"] = "groovy"; filetype["haxe"] = "hx"; + filetype["htm"] = "html"; filetype["html"] = "html"; filetype["java"] = "java"; filetype["js"] = "javascript"; + filetype["jsm"] = "javascript"; filetype["json"] = "json"; filetype["latex"] = "latex"; filetype["ly"] = "latex"; From b71cf1a4f97bb660a279a16ca4da864b56986a59 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 7 Apr 2012 16:01:50 +0200 Subject: [PATCH 196/302] Fix bug oc-413: PHP fatal error in contacts page when no contacts in ownCloud. --- apps/contacts/index.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/contacts/index.php b/apps/contacts/index.php index 076b10c2ee..776c57ca60 100644 --- a/apps/contacts/index.php +++ b/apps/contacts/index.php @@ -43,17 +43,15 @@ if(count($categories) == 0) { $vcaddressbookids[] = $vcaddressbook['id']; } $vccontacts = OC_Contacts_VCard::all($vcaddressbookids); - if(count($vccontacts) == 0) { - bailOut(OC_Contacts_App::$l10n->t('No contacts found.')); + if(count($vccontacts) > 0) { + $cards = array(); + foreach($vccontacts as $vccontact) { + $cards[] = $vccontact['carddata']; + } + + OC_Contacts_App::$categories->rescan($cards); + $categories = OC_Contacts_App::$categories->categories(); } - - $cards = array(); - foreach($vccontacts as $vccontact) { - $cards[] = $vccontact['carddata']; - } - - OC_Contacts_App::$categories->rescan($cards); - $categories = OC_Contacts_App::$categories->categories(); } } From d1ae6512cc760c76798a5a8636d1d7908c706ae8 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 7 Apr 2012 16:03:11 +0200 Subject: [PATCH 197/302] Contacts: Misc. cleanup tweaks. --- apps/contacts/ajax/saveproperty.php | 58 +++++++++---------- apps/contacts/css/contacts.css | 11 ++-- apps/contacts/js/contacts.js | 29 +++------- apps/contacts/templates/part.contact.php | 19 +++--- .../templates/part.edit_address_dialog.php | 14 ++--- apps/contacts/templates/settings.php | 2 +- core/js/oc-vcategories.js | 4 +- 7 files changed, 64 insertions(+), 73 deletions(-) diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php index 924d873652..4cef4d1e7a 100644 --- a/apps/contacts/ajax/saveproperty.php +++ b/apps/contacts/ajax/saveproperty.php @@ -97,39 +97,39 @@ switch($element) { } break; case 'CATEGORIES': - /* multi autocomplete triggers an save with empty value */ + /* multi autocomplete triggers an save with empty value if (!$value) { $value = $vcard->getAsString('CATEGORIES'); } - break; + break;*/ case 'EMAIL': $value = strtolower($value); break; } if(!$value) { - bailOut(OC_Contacts_App::$l10n->t('Cannot save empty value.')); -} - -/* setting value */ -switch($element) { - case 'BDAY': - case 'FN': - case 'N': - case 'ORG': - case 'NOTE': - case 'NICKNAME': - case 'CATEGORIES': - debug('Setting string:'.$name.' '.$value); - $vcard->setString($name, $value); - break; - case 'EMAIL': - case 'TEL': - case 'ADR': // should I delete the property if empty or throw an error? - debug('Setting element: (EMAIL/TEL/ADR)'.$element); - if(!$value) { - unset($vcard->children[$line]); // Should never happen... - } else { + unset($vcard->children[$line]); + $checksum = ''; +} else { + /* setting value */ + switch($element) { + case 'BDAY': + case 'FN': + case 'N': + case 'ORG': + case 'NOTE': + case 'NICKNAME': + debug('Setting string:'.$name.' '.$value); + $vcard->setString($name, $value); + break; + case 'CATEGORIES': + debug('Setting string:'.$name.' '.$value); + $vcard->children[$line]->setValue($value); + break; + case 'EMAIL': + case 'TEL': + case 'ADR': // should I delete the property if empty or throw an error? + debug('Setting element: (EMAIL/TEL/ADR)'.$element); $vcard->children[$line]->setValue($value); $vcard->children[$line]->parameters = array(); if(!is_null($parameters)) { @@ -142,12 +142,12 @@ switch($element) { } } } - } - break; + break; + } + // Do checksum and be happy + $checksum = md5($vcard->children[$line]->serialize()); } -// Do checksum and be happy -$checksum = md5($vcard->children[$line]->serialize()); -debug('New checksum: '.$checksum); +//debug('New checksum: '.$checksum); if(!OC_Contacts_VCard::edit($id,$vcard)) { bailOut(OC_Contacts_App::$l10n->t('Error updating contact property.')); diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 4e7d8c285a..5d3ebf65fb 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -13,19 +13,22 @@ #contacts_propertymenu li a { padding: 3px; display: block } #contacts_propertymenu li:hover { background-color: #1d2d44; } #contacts_propertymenu li a:hover { color: #fff } -#actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0;} +#actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0; z-index: 1000; } #card { width: auto;/*max-width: 70em; border: thin solid lightgray; display: block;*/ } #firstrun { width: 100%; position: absolute; top: 5em; left: 0; text-align: center; font-weight:bold; font-size:1.5em; color:#777; } #firstrun #selections { font-size:0.8em; margin: 2em auto auto auto; clear: both; } #card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 14em; float: left; } .categories { float: left; width: 16em; } -#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #fefefe; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } -#card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } -input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { color: #bbb !important; } +#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select, textarea { background-color: #fefefe; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } +#card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active, textarea:focus, textarea:hover { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } +input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid, textarea:invalid { color: #bbb !important; } +textarea { min-height: 4em; } dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } .form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom; color: #bbb;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } .form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } +#address.form dt { min-width: 5em; } +#address.form dl { min-width: 10em; } .loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } .ui-autocomplete-loading { background: url('../../../core/img/loading.gif') right center no-repeat; } diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index cc4da5837e..3b264c0197 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -116,7 +116,7 @@ Contacts={ }, loadListHandlers:function() { //$('.add,.delete').hide(); - $('.globe,.mail,.delete,.edit').tipsy(); + $('.globe,.mail,.delete,.edit,.tip').tipsy(); $('.addresscard,.propertylist li,.propertycontainer').hover( function () { $(this).find('.globe,.mail,.delete,.edit').fadeIn(100); @@ -171,10 +171,6 @@ Contacts={ // Contacts.UI.Card.editAddress(); // return false; // }); - $('#n').click(function(){ - Contacts.UI.Card.editName(); - //return false; - }); $('#edit_name').click(function(){ Contacts.UI.Card.editName(); return false; @@ -258,12 +254,12 @@ Contacts={ }); } }, - export:function() { + do_export:function() { document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + this.id; //$.get(OC.linkTo('contacts', 'export.php'),{'contactid':this.id},function(jsondata){ //}); }, - import:function(){ + do_import:function(){ Contacts.UI.notImplemented(); }, add:function(n, fn, aid, isnew){ // add a new contact @@ -293,11 +289,11 @@ Contacts={ if(isnew) { Contacts.UI.Card.addProperty('EMAIL'); Contacts.UI.Card.addProperty('TEL'); - Contacts.UI.Card.addProperty('BDAY'); Contacts.UI.Card.addProperty('NICKNAME'); Contacts.UI.Card.addProperty('ORG'); Contacts.UI.Card.addProperty('CATEGORIES'); $('#fn').focus(); + $('#fn').select(); } } else{ @@ -315,7 +311,7 @@ Contacts={ } }); }, - delete:function() { + do_delete:function() { $('#contacts_deletecard').tipsy('hide'); OC.dialogs.confirm(t('contacts', 'Are you sure you want to delete this contact?'), t('contacts', 'Warning'), function(answer) { if(answer == true) { @@ -1256,7 +1252,7 @@ Contacts={ }); } }, - import:function(){ + do_import:function(){ Contacts.UI.notImplemented(); }, submit:function(button, bookid){ @@ -1289,9 +1285,7 @@ Contacts={ } }, Contacts:{ - /** - * Reload the contacts list. - */ + // Reload the contacts list. update:function(){ console.log('Contacts.update, start'); $.getJSON('ajax/contacts.php',{},function(jsondata){ @@ -1306,9 +1300,7 @@ Contacts={ }); setTimeout(Contacts.UI.Contacts.lazyupdate, 500); }, - /** - * Add thumbnails to the contact list as they become visible in the viewport. - */ + // Add thumbnails to the contact list as they become visible in the viewport. lazyupdate:function(){ $('#contacts li').live('inview', function(){ if (!$(this).find('a').attr('style')) { @@ -1328,9 +1320,6 @@ $(document).ready(function(){ OCCategories.changed = Contacts.UI.Card.categoriesChanged; OCCategories.app = 'contacts'; - /** - * Show the Addressbook chooser - */ $('#chooseaddressbook').click(function(){ Contacts.UI.Addressbooks.overview(); return false; @@ -1363,7 +1352,7 @@ $(document).ready(function(){ }); $('#contacts_deletecard').live('click',function(){ - Contacts.UI.Card.delete(); + Contacts.UI.Card.do_delete(); }); $('#contacts li').bind('inview', function(event, isInView, visiblePartX, visiblePartY) { diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index 961ce693e6..03d2fad853 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -17,7 +17,7 @@ $id = isset($_['id']) ? $_['id'] : '';
  • t('Categories'); ?>
- + @@ -37,7 +37,7 @@ $id = isset($_['id']) ? $_['id'] : ''; - +
@@ -62,7 +62,7 @@ $id = isset($_['id']) ? $_['id'] : '';
-
+
@@ -73,7 +73,7 @@ $id = isset($_['id']) ? $_['id'] : '';
@@ -85,7 +85,7 @@ $id = isset($_['id']) ? $_['id'] : '';
@@ -131,7 +130,7 @@ $(document).ready(function(){ Contacts.UI.Card.loadContact(jsondata.data); } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); } diff --git a/apps/contacts/templates/part.edit_address_dialog.php b/apps/contacts/templates/part.edit_address_dialog.php index 0ecdc4e191..507a3acaa0 100644 --- a/apps/contacts/templates/part.edit_address_dialog.php +++ b/apps/contacts/templates/part.edit_address_dialog.php @@ -22,44 +22,44 @@ foreach(isset($adr['parameters']['TYPE'])?array($adr['parameters']['TYPE']):arra
- +
- +
- +
- +
- +
- +
- +
diff --git a/apps/contacts/templates/settings.php b/apps/contacts/templates/settings.php index d77c4d3802..5627a15c50 100644 --- a/apps/contacts/templates/settings.php +++ b/apps/contacts/templates/settings.php @@ -8,6 +8,6 @@
t('iOS/OS X'); ?>
/principals//
+ Powered by geonames.org webservice - Powered by geonames.org webservice diff --git a/core/js/oc-vcategories.js b/core/js/oc-vcategories.js index a6dcccf88e..931ea37edb 100644 --- a/core/js/oc-vcategories.js +++ b/core/js/oc-vcategories.js @@ -19,7 +19,7 @@ OCCategories={ height: 350, minHeight:200, width: 250, minWidth: 200, buttons: { 'Delete':function() { - OCCategories.delete(); + OCCategories.do_delete(); }, 'Rescan':function() { OCCategories.rescan(); @@ -53,7 +53,7 @@ OCCategories={ } }); }, - delete:function(){ + do_delete:function(){ var categories = $('#categorylist').find('input[type="checkbox"]').serialize(); categories += '&app=' + OCCategories.app; console.log('OCCategories.delete: ' + categories); From 23d39f7ef0362d27c6a840585f878bc54c438409 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 7 Apr 2012 16:13:18 +0000 Subject: [PATCH 198/302] Display errors on import --- apps/user_migrate/admin.php | 32 +++++++++++++-------------- apps/user_migrate/templates/admin.php | 4 ++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index 280f7b7196..510c54abe3 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -36,18 +36,18 @@ if (isset($_POST['user_import'])) { $from = $_FILES['owncloud_import']['tmp_name']; $to = get_temp_dir().'/'.$importname.'.zip'; if( !move_uploaded_file( $from, $to ) ){ - $errors[] = array('error'=>'Failed to move the uploaded file','hint'=>'Try checking the permissions of the '.get_temp_dir().' dir.'); + $error = array('error'=>'Failed to move the uploaded file','hint'=>'Try checking the permissions of the '.get_temp_dir().' dir.'); OC_Log::write( 'user_migrate', "Failed to copy the uploaded file", OC_Log::ERROR ); - $t = new OC_Template( '', 'error', 'user' ); - $t->assign('errors',$errors); - $t->fetchPage(); + $tmpl = new OC_Template('user_migrate', 'admin'); + $tmpl->assign('error',$error); + return $tmpl->fetchPage(); } if( !$appsstatus = OC_Migrate::import( $to, 'user' ) ){ - $errors[] = array('error'=>'There was an error while importing the user!','hint'=>'Please check the logs for a more detailed explaination'); - $t = new OC_Template( '', 'error', 'user' ); - $t->assign('errors',$errors); - $t->fetchPage(); + $error = array('error'=>'There was an error while importing the user!','hint'=>'Please check the logs for a more detailed explaination'); + $tmpl = new OC_Template('user_migrate', 'admin'); + $tmpl->assign('error',$error); + return $tmpl->fetchPage(); } else { // Check import status foreach( $appsstatus as $app => $status ){ @@ -63,15 +63,15 @@ if (isset($_POST['user_import'])) { // Any problems? if( isset( $notsupported ) || isset( $failed ) ){ if( count( $failed ) > 0 ){ - $errors[] = array('error'=>'Some app data failed to import','hint'=>'App data for: '.implode(', ', $failed).' failed to import.'); - $t = new OC_Template( '', 'error', 'user' ); - $t->assign('errors',$errors); - $t->fetchPage(); + $error = array('error'=>'Some app data failed to import','hint'=>'App data for: '.implode(', ', $failed).' failed to import.'); + $tmpl = new OC_Template('user_migrate', 'admin'); + $tmpl->assign('error',$error); + return $tmpl->fetchPage(); } else if( count( $notsupported ) > 0 ){ - $errors[] = array('error'=>'Some app data could not be imported, as the apps are not installed on this instance','hint'=>'App data for: '.implode(', ', $notsupported).' failed to import as they were not found. Please install the apps and try again'); - $t = new OC_Template( '', 'error', 'user' ); - $t->assign('errors',$errors); - $t->fetchPage(); + $error = array('error'=>'Some app data could not be imported, as the apps are not installed on this instance','hint'=>'App data for: '.implode(', ', $notsupported).' failed to import as they were not found. Please install the apps and try again'); + $tmpl = new OC_Template('user_migrate', 'admin'); + $tmpl->assign('error',$error); + return $tmpl->fetchPage(); } } else { // Went swimmingly! diff --git a/apps/user_migrate/templates/admin.php b/apps/user_migrate/templates/admin.php index b5a9951841..b01e5c7579 100644 --- a/apps/user_migrate/templates/admin.php +++ b/apps/user_migrate/templates/admin.php @@ -1,5 +1,9 @@
+ +

+

+ t('Import user account');?>

From 00449cdf3781e9cbd1e7ab6e42cbeebf1fdb2f35 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 7 Apr 2012 17:23:25 +0000 Subject: [PATCH 199/302] rename admin_export -> admin_migrate --- apps/{admin_export => admin_migrate}/appinfo/app.php | 10 ++++------ apps/{admin_export => admin_migrate}/appinfo/info.xml | 6 +++--- apps/{admin_export => admin_migrate}/settings.php | 6 +++--- .../templates/settings.php | 0 4 files changed, 10 insertions(+), 12 deletions(-) rename apps/{admin_export => admin_migrate}/appinfo/app.php (76%) rename apps/{admin_export => admin_migrate}/appinfo/info.xml (57%) rename apps/{admin_export => admin_migrate}/settings.php (92%) rename apps/{admin_export => admin_migrate}/templates/settings.php (100%) diff --git a/apps/admin_export/appinfo/app.php b/apps/admin_migrate/appinfo/app.php similarity index 76% rename from apps/admin_export/appinfo/app.php rename to apps/admin_migrate/appinfo/app.php index 4e896abd6a..e45d3f6a52 100644 --- a/apps/admin_export/appinfo/app.php +++ b/apps/admin_migrate/appinfo/app.php @@ -1,10 +1,8 @@ "admin_export_settings", + 'id' => "admin_migrate_settings", 'order'=>1, - 'href' => OC_Helper::linkTo( "admin_export", "settings.php" ), + 'href' => OC_Helper::linkTo( "admin_migrate", "settings.php" ), 'name' => 'Export' ); diff --git a/apps/admin_export/appinfo/info.xml b/apps/admin_migrate/appinfo/info.xml similarity index 57% rename from apps/admin_export/appinfo/info.xml rename to apps/admin_migrate/appinfo/info.xml index e434705c9a..67fc3f9c5a 100644 --- a/apps/admin_export/appinfo/info.xml +++ b/apps/admin_migrate/appinfo/info.xml @@ -1,8 +1,8 @@ - admin_export - Import/Export - Import/Export your owncloud data + admin_migrate + ownCloud Instance Migration + Import/Export your owncloud instance 0.1 AGPL Thomas Schmidt and Tom Needham diff --git a/apps/admin_export/settings.php b/apps/admin_migrate/settings.php similarity index 92% rename from apps/admin_export/settings.php rename to apps/admin_migrate/settings.php index 719bedb66e..981d5f4ca6 100644 --- a/apps/admin_export/settings.php +++ b/apps/admin_migrate/settings.php @@ -1,7 +1,7 @@ fetchPage(); } \ No newline at end of file diff --git a/apps/admin_export/templates/settings.php b/apps/admin_migrate/templates/settings.php similarity index 100% rename from apps/admin_export/templates/settings.php rename to apps/admin_migrate/templates/settings.php From d2886f202024243ae4d92e2eea9d3726037a9601 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 7 Apr 2012 17:27:09 +0000 Subject: [PATCH 200/302] Hide instance import as it eats data --- apps/admin_migrate/templates/settings.php | 9 ++++++++- lib/migrate.php | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/admin_migrate/templates/settings.php b/apps/admin_migrate/templates/settings.php index 36eec84d48..91e305074e 100644 --- a/apps/admin_migrate/templates/settings.php +++ b/apps/admin_migrate/templates/settings.php @@ -6,12 +6,16 @@

What would you like to export?

- ownCloud instance ( suitable for import )
+ ownCloud instance (suitable for import )
ownCloud system files
Just user files

+
t('Import an ownCloud instance. THIS WILL DELETE ALL CURRENT OWNCLOUD DATA');?> @@ -22,3 +26,6 @@
+ diff --git a/lib/migrate.php b/lib/migrate.php index 815333b4d8..22c0560691 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -258,6 +258,7 @@ class OC_Migrate{ break; case 'instance': /* + * EXPERIMENTAL // Check for new data dir and dbexport before doing anything // TODO From c0869887cf91bf17421a9163069584d29ec5ed49 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 7 Apr 2012 21:55:16 +0000 Subject: [PATCH 201/302] Return JSON for import and export methods of OC_Migrate --- apps/bookmarks/appinfo/migrate.php | 1 - apps/user_migrate/admin.php | 6 ++--- apps/user_migrate/ajax/export.php | 35 +++++++++++++------------ lib/migrate.php | 42 +++++++++++++++--------------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/apps/bookmarks/appinfo/migrate.php b/apps/bookmarks/appinfo/migrate.php index 603a8b72d3..c1251e816e 100644 --- a/apps/bookmarks/appinfo/migrate.php +++ b/apps/bookmarks/appinfo/migrate.php @@ -3,7 +3,6 @@ class OC_Migration_Provider_Bookmarks extends OC_Migration_Provider{ // Create the xml for the user supplied function export( ){ - OC_Log::write('migration','starting export for bookmarks',OC_Log::INFO); $options = array( 'table'=>'bookmarks', 'matchcol'=>'user_id', diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php index 510c54abe3..0160753af2 100644 --- a/apps/user_migrate/admin.php +++ b/apps/user_migrate/admin.php @@ -42,15 +42,15 @@ if (isset($_POST['user_import'])) { $tmpl->assign('error',$error); return $tmpl->fetchPage(); } - - if( !$appsstatus = OC_Migrate::import( $to, 'user' ) ){ + $response = json_decode( OC_Migrate::import( $to, 'user' ) ); + if( !$response->success ){ $error = array('error'=>'There was an error while importing the user!','hint'=>'Please check the logs for a more detailed explaination'); $tmpl = new OC_Template('user_migrate', 'admin'); $tmpl->assign('error',$error); return $tmpl->fetchPage(); } else { // Check import status - foreach( $appsstatus as $app => $status ){ + foreach( $response->data as $app => $status ){ if( $status != 'true' ){ // It failed for some reason if( $status == 'notsupported' ){ diff --git a/apps/user_migrate/ajax/export.php b/apps/user_migrate/ajax/export.php index fac96577fa..86745d6b16 100644 --- a/apps/user_migrate/ajax/export.php +++ b/apps/user_migrate/ajax/export.php @@ -28,28 +28,29 @@ OC_JSON::checkLoggedIn(); OC_Util::checkAppEnabled('user_migrate'); // Which operation if( $_GET['operation']=='create' ){ -$uid = !empty( $_POST['uid'] ) ? $_POST['uid'] : OC_User::getUser(); -if( $uid != OC_User::getUser() ){ - // Needs to be admin to export someone elses account - OC_JSON::error(); + $uid = !empty( $_POST['uid'] ) ? $_POST['uid'] : OC_User::getUser(); + if( $uid != OC_User::getUser() ){ + // Needs to be admin to export someone elses account + OC_JSON::error(); + die(); + } + // Create the export zip + $response = json_decode( OC_Migrate::export( $uid ) ); + if( !$response->success ){ + // Error + OC_JSON::error(); + die(); + } else { + // Save path in session + $_SESSION['ocuserexportpath'] = $response->data; + } + OC_JSON::success(); die(); -} -// Create the export zip -if( !$path = OC_Migrate::export( $uid ) ){ - // Error - OC_JSON::error(); - die(); -} else { - // Save path in session - $_SESSION['ocuserexportpath'] = $path; -} -OC_JSON::success(); -die(); } else if( $_GET['operation']=='download' ){ // Download the export $path = isset( $_SESSION['ocuserexportpath'] ) ? $_SESSION['ocuserexportpath'] : false; if( !$path ){ - die(); + OC_JSON::error(); } header("Content-Type: application/zip"); header("Content-Disposition: attachment; filename=" . basename($path)); diff --git a/lib/migrate.php b/lib/migrate.php index 22c0560691..6734c805fc 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -84,7 +84,7 @@ class OC_Migrate{ $types = array( 'user', 'instance', 'system', 'userfiles' ); if( !in_array( $type, $types ) ){ OC_Log::write( 'migration', 'Invalid export type', OC_Log::ERROR ); - return false; + return json_encode( array( array( 'success' => false ) ) ); } self::$exporttype = $type; // Userid? @@ -93,7 +93,7 @@ class OC_Migrate{ if( !is_null($uid) ){ if( !OC_User_Database::userExists( $uid ) ){ OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR); - return false; + return json_encode( array( 'success' => false ) ); } self::$uid = $uid; } else { @@ -114,7 +114,7 @@ class OC_Migrate{ // Validate custom path if( !file_exists( $path ) || !is_writeable( $path ) ){ OC_Log::write( 'migration', 'Path supplied is invalid.', OC_Log::ERROR ); - return false; + return json_encode( array( 'success' => false ) ); } self::$zippath = $path . $zipname; } else { @@ -124,7 +124,7 @@ class OC_Migrate{ } // Create the zip object if( !self::createZip() ){ - return false; + return json_encode( array( 'success' => false ) ); } // Do the export self::findProviders(); @@ -134,7 +134,7 @@ class OC_Migrate{ // Connect to the db self::$dbpath = $datadir . '/' . self::$uid . '/migration.db'; if( !self::connectDB() ){ - return false; + return json_encode( array( 'success' => false ) ); } self::$content = new OC_Migration_Content( self::$zip, self::$MDB2 ); // Export the app info @@ -178,14 +178,14 @@ class OC_Migrate{ break; } if( !$info = self::getExportInfo( $exportdata ) ){ - return false; + return json_encode( array( 'success' => false ) ); } // Add the export info json to the export zip self::$content->addFromString( $info, 'export_info.json' ); if( !self::$content->finish() ){ - return false; + return json_encode( array( 'success' => false ) ); } - return self::$zippath; + return json_encode( array( 'success' => true, 'data' => self::$zippath ) ); } /** @@ -199,19 +199,19 @@ class OC_Migrate{ $datadir = OC_Config::getValue( 'datadirectory' ); // Extract the zip if( !$extractpath = self::extractZip( $path ) ){ - return false; + return json_encode( array( 'success' => false ) ); } // Get export_info.json $scan = scandir( $extractpath ); // Check for export_info.json if( !in_array( 'export_info.json', $scan ) ){ OC_Log::write( 'migration', 'Invalid import file, export_info.json note found', OC_Log::ERROR ); - return false; + return json_encode( array( 'success' => false ) ); } $json = json_decode( file_get_contents( $extractpath . 'export_info.json' ) ); if( $json->exporttype != $type ){ OC_Log::write( 'migration', 'Invalid import file', OC_Log::ERROR ); - return false; + return json_encode( array( 'success' => false ) ); } self::$exporttype = $type; @@ -230,31 +230,31 @@ class OC_Migrate{ // Check user availability if( OC_User::userExists( self::$uid ) ){ OC_Log::write( 'migration', 'User already exists', OC_Log::ERROR ); - return false; + return json_encode( array( 'success' => false ) ); } // Create the user if( !self::createUser( self::$uid, $json->hash ) ){ - return false; + return json_encode( array( 'success' => false ) ); } // Make the new users data dir $path = $datadir . '/' . self::$uid . '/files/'; if( !mkdir( $path, 0755, true ) ){ OC_Log::write( 'migration', 'Failed to create users data dir: '.$path, OC_Log::ERROR ); - return false; + return json_encode( array( 'success' => false ) ); } // Copy data if( !self::copy_r( $extractpath . $json->exporteduser . '/files', $datadir . '/' . self::$uid . '/files' ) ){ - return false; + return json_encode( array( 'success' => false ) ); } // Import user app data if( !$appsimported = self::importAppData( $extractpath . $json->exporteduser . '/migration.db', $json, self::$uid ) ){ - return false; + return json_encode( array( 'success' => false ) ); } // All done! if( !self::unlink_r( $extractpath ) ){ OC_Log::write( 'migration', 'Failed to delete the extracted zip', OC_Log::ERROR ); } - return $appsimported; + return json_encode( array( 'success' => true, 'data' => $appsimported ) ); break; case 'instance': /* @@ -266,21 +266,21 @@ class OC_Migrate{ OC_Log::write( 'migration', "Deleting current data dir", OC_Log::INFO ); if( !self::unlink_r( $datadir, false ) ){ OC_Log::write( 'migration', 'Failed to delete the current data dir', OC_Log::ERROR ); - return false; + return json_encode( array( 'success' => false ) ); } // Copy over data if( !self::copy_r( $extractpath . 'userdata', $datadir ) ){ OC_Log::write( 'migration', 'Failed to copy over data directory', OC_Log::ERROR ); - return false; + return json_encode( array( 'success' => false ) ); } // Import the db if( !OC_DB::replaceDB( $extractpath . 'dbexport.xml' ) ){ - return false; + return json_encode( array( 'success' => false ) ); } // Done - return true; + return json_encode( 'success' => true ); */ break; } From 5e314e8effb42b60d720de40fbf74591ae421534 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sat, 7 Apr 2012 22:00:32 +0000 Subject: [PATCH 202/302] Emit hooks for user creation --- lib/migrate.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/migrate.php b/lib/migrate.php index 6734c805fc..2730d26a74 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -232,10 +232,19 @@ class OC_Migrate{ OC_Log::write( 'migration', 'User already exists', OC_Log::ERROR ); return json_encode( array( 'success' => false ) ); } + $run = true; + OC_Hook::emit( "OC_User", "pre_createUser", array( "run" => &$run, "uid" => self::$uid, "password" => $json->hash )); + if( !$run ){ + // Something stopped the user creation + OC_Log::write( 'migration', 'User creation failed', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } // Create the user if( !self::createUser( self::$uid, $json->hash ) ){ return json_encode( array( 'success' => false ) ); } + // Emit the post_createUser hook (password is already hashed, will cause problems + OC_Hook::emit( "OC_User", "post_createUser", array( "uid" => self::$uid, "password" => $json->hash )); // Make the new users data dir $path = $datadir . '/' . self::$uid . '/files/'; if( !mkdir( $path, 0755, true ) ){ From e5ebbacc9e951d1da82762daf67f993ee20e95cb Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 8 Apr 2012 18:52:31 +0000 Subject: [PATCH 203/302] Fix sqlite version detection --- lib/migrate.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/migrate.php b/lib/migrate.php index 2730d26a74..a8a5e581c3 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -496,10 +496,14 @@ class OC_Migrate{ $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); // DB type - if( !is_callable( 'sqlite_open' ) || !class_exists( 'SQLite3' ) ){ + if( class_exists( 'SQLite3' ) ){ + $dbtype = 'sqlite3'; + } else if( is_callable( 'sqlite_open' ) ){ + $dbtype = 'sqlite'; + } else { OC_Log::write( 'migration', 'SQLite not found', OC_Log::ERROR ); return false; - } + } // Prepare options array $options = array( @@ -510,7 +514,7 @@ class OC_Migrate{ 'quote_identifier' => true ); $dsn = array( - 'phptype' => 'sqlite3', + 'phptype' => $dbtype, 'database' => self::$dbpath, 'mode' => '0644' ); From ecc596534a9d6d6faffc6038a5bce61ebdf59781 Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 8 Apr 2012 19:08:17 +0000 Subject: [PATCH 204/302] Decode json response for admin exports --- apps/admin_migrate/settings.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/admin_migrate/settings.php b/apps/admin_migrate/settings.php index 981d5f4ca6..94bf6052a6 100644 --- a/apps/admin_migrate/settings.php +++ b/apps/admin_migrate/settings.php @@ -28,10 +28,12 @@ OC_Util::checkAppEnabled('admin_migrate'); // Export? if (isset($_POST['admin_export'])) { // Create the export zip - if( !$path = OC_Migrate::export( null, $_POST['export_type'] ) ){ + $response = json_decode( OC_Migrate::export( null, $_POST['export_type'] ) ); + if( !$response->success ){ // Error die('error'); } else { + $path = $response->data; // Download it header("Content-Type: application/zip"); header("Content-Disposition: attachment; filename=" . basename($path)); From 8e188cd96f4c1f19664aa5ee870420a26d230c5c Mon Sep 17 00:00:00 2001 From: Tom Needham Date: Sun, 8 Apr 2012 19:16:03 +0000 Subject: [PATCH 205/302] Copy over all file app data for imported user --- lib/migrate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/migrate.php b/lib/migrate.php index a8a5e581c3..dff3abe9e9 100644 --- a/lib/migrate.php +++ b/lib/migrate.php @@ -246,13 +246,13 @@ class OC_Migrate{ // Emit the post_createUser hook (password is already hashed, will cause problems OC_Hook::emit( "OC_User", "post_createUser", array( "uid" => self::$uid, "password" => $json->hash )); // Make the new users data dir - $path = $datadir . '/' . self::$uid . '/files/'; + $path = $datadir . '/' . self::$uid; if( !mkdir( $path, 0755, true ) ){ OC_Log::write( 'migration', 'Failed to create users data dir: '.$path, OC_Log::ERROR ); return json_encode( array( 'success' => false ) ); } // Copy data - if( !self::copy_r( $extractpath . $json->exporteduser . '/files', $datadir . '/' . self::$uid . '/files' ) ){ + if( !self::copy_r( $extractpath . $json->exporteduser, $datadir . '/' . self::$uid ) ){ return json_encode( array( 'success' => false ) ); } // Import user app data From 53091551d8019fa64dbcf5197a380f60bb2e6827 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 9 Apr 2012 16:02:59 +0200 Subject: [PATCH 206/302] Re-added file I had accidentally removed earlier :-P --- apps/contacts/templates/part.no_contacts.php | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 apps/contacts/templates/part.no_contacts.php diff --git a/apps/contacts/templates/part.no_contacts.php b/apps/contacts/templates/part.no_contacts.php new file mode 100644 index 0000000000..d24f7ae980 --- /dev/null +++ b/apps/contacts/templates/part.no_contacts.php @@ -0,0 +1,8 @@ +
+ You have no contacts in your list. +
+ + + +
+
\ No newline at end of file From 9c247ce874697ed6833c58d21ddecee418ddb525 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 9 Apr 2012 16:29:35 +0200 Subject: [PATCH 207/302] Removed padding. --- apps/contacts/js/contacts.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index 3b264c0197..b2678e8c52 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -264,6 +264,18 @@ Contacts={ }, add:function(n, fn, aid, isnew){ // add a new contact console.log('Add contact: ' + n + ', ' + fn + ' ' + aid); + var card = $('#card')[0]; + if(!card) { + console.log('Loading proper card DOM'); + $.getJSON(OC.filePath('contacts', 'ajax', 'loadcard.php'),{},function(jsondata){ + if(jsondata.status == 'success'){ + $('#rightcontent').html(jsondata.data.page); + Contacts.UI.loadHandlers(); + } else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + } $.post(OC.filePath('contacts', 'ajax', 'addcontact.php'), { n: n, fn: fn, aid: aid }, function(jsondata) { if (jsondata.status == 'success'){ From 4fda6af725ea775750905983255ab3dc9ceeb2b9 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 9 Apr 2012 16:29:56 +0200 Subject: [PATCH 208/302] Contacts: Fix for not being able to add contact to empty/non-existant address book. --- apps/contacts/ajax/addcontact.php | 2 -- apps/contacts/css/contacts.css | 2 +- apps/contacts/lib/addressbook.php | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/contacts/ajax/addcontact.php b/apps/contacts/ajax/addcontact.php index 947b35bab5..68da54655a 100644 --- a/apps/contacts/ajax/addcontact.php +++ b/apps/contacts/ajax/addcontact.php @@ -47,8 +47,6 @@ OC_Contacts_App::getAddressbook( $aid ); // is owner access check $fn = trim($_POST['fn']); $n = trim($_POST['n']); -debug('N: '.$n); -debug('FN: '.$fn); $vcard = new OC_VObject('VCARD'); $vcard->setUID(); diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 5d3ebf65fb..2d20794384 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -56,7 +56,7 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } .contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } #cropbox { margin: auto; } -#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; padding: 0.5em; margin: 0.3em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; display: block; /* clear: right;*/ } +#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; margin: 0.3em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; display: block; /* clear: right;*/ } #contacts_details_photo:hover { background: #fff; } /*#contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; }*/ /* Address editor */ diff --git a/apps/contacts/lib/addressbook.php b/apps/contacts/lib/addressbook.php index 052c19e55f..9061fa1914 100644 --- a/apps/contacts/lib/addressbook.php +++ b/apps/contacts/lib/addressbook.php @@ -169,7 +169,7 @@ class OC_Contacts_Addressbook{ $uid = OC_User::getUser(); } $prefbooks = OC_Preferences::getValue($uid,'contacts','openaddressbooks',null); - if(is_null($prefbooks)){ + if(!$prefbooks){ $addressbooks = OC_Contacts_Addressbook::all($uid); if(count($addressbooks) == 0){ OC_Contacts_Addressbook::add($uid,'default','Default Address Book'); From b0894b314845a52e979539cbf1e6bd94444dca54 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Mon, 9 Apr 2012 22:10:29 +0200 Subject: [PATCH 209/302] Add Craigs granite library to 3rdparty and files_versioning. Still not working and lots ot stuff to do. --- 3rdparty/granite/git/blob.php | 162 +++++ 3rdparty/granite/git/commit.php | 232 +++++++ 3rdparty/granite/git/object/index.php | 210 ++++++ 3rdparty/granite/git/object/loose.php | 81 +++ 3rdparty/granite/git/object/packed.php | 304 ++++++++ 3rdparty/granite/git/object/raw.php | 153 +++++ 3rdparty/granite/git/repository.php | 293 ++++++++ 3rdparty/granite/git/tag.php | 38 + 3rdparty/granite/git/tree.php | 198 ++++++ 3rdparty/granite/git/tree/node.php | 126 ++++ apps/files_versioning/ajax/gethead.php | 12 + apps/files_versioning/ajax/sethead.php | 14 + apps/files_versioning/appinfo/app.php | 20 + apps/files_versioning/appinfo/info.xml | 14 + apps/files_versioning/css/settings.css | 3 + apps/files_versioning/js/settings.js | 25 + apps/files_versioning/lib_granite.php | 12 + apps/files_versioning/settings.php | 34 + apps/files_versioning/templates/settings.php | 12 + apps/files_versioning/versionstorage.php | 386 +++++++++++ apps/files_versioning/versionwrapper.php | 686 +++++++++++++++++++ 21 files changed, 3015 insertions(+) create mode 100644 3rdparty/granite/git/blob.php create mode 100644 3rdparty/granite/git/commit.php create mode 100644 3rdparty/granite/git/object/index.php create mode 100644 3rdparty/granite/git/object/loose.php create mode 100644 3rdparty/granite/git/object/packed.php create mode 100644 3rdparty/granite/git/object/raw.php create mode 100644 3rdparty/granite/git/repository.php create mode 100644 3rdparty/granite/git/tag.php create mode 100644 3rdparty/granite/git/tree.php create mode 100644 3rdparty/granite/git/tree/node.php create mode 100644 apps/files_versioning/ajax/gethead.php create mode 100644 apps/files_versioning/ajax/sethead.php create mode 100644 apps/files_versioning/appinfo/app.php create mode 100644 apps/files_versioning/appinfo/info.xml create mode 100644 apps/files_versioning/css/settings.css create mode 100644 apps/files_versioning/js/settings.js create mode 100644 apps/files_versioning/lib_granite.php create mode 100644 apps/files_versioning/settings.php create mode 100644 apps/files_versioning/templates/settings.php create mode 100644 apps/files_versioning/versionstorage.php create mode 100644 apps/files_versioning/versionwrapper.php diff --git a/3rdparty/granite/git/blob.php b/3rdparty/granite/git/blob.php new file mode 100644 index 0000000000..781a697d56 --- /dev/null +++ b/3rdparty/granite/git/blob.php @@ -0,0 +1,162 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Object\Raw as Raw; +use \InvalidArgumentException as InvalidArgumentException; +use \finfo as finfo; +/** + * **Granite\Git\Blob** represents the raw content of an object in a Git repository, + * typically a **file**. This class provides methods related to the handling of + * blob content, mimetypes, sizes and write support. + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Blob +{ + + /** + * Stores the SHA-1 id of the object requested; accessed through the `sha()` + * method where it is recalculated based on the blob content. + */ + private $sha = null; + /** + * The raw binary string of the file contents. + */ + private $content = ""; + /** + * The path to the repository location. + */ + private $path; + + /** + * Fetches a raw Git object and parses the result. Throws an + * InvalidArgumentException if the object is not of the correct type, + * or cannot be found. + * + * @param string $path The path to the repository root. + * @param string $sha The SHA-1 id of the requested object, or `null` if + * creating a new blob object. + * + * @throws InvalidArgumentException If the SHA-1 id provided is not a blob. + */ + public function __construct($path, $sha = NULL) + { + $this->path = $path; + if ($sha !== NULL) { + $this->sha = $sha; + $object = Raw::factory($path, $sha); + + if ($object->type() !== Raw::OBJ_BLOB) { + throw new InvalidArgumentException( + "The object $sha is not a blob, type is {$object->type()}" + ); + } + + $this->content = $object->content(); + unset($object); + } + } + + /** + * Sets or returns the raw file content, depending whether the parameter is + * provided. + * + * @param string $content The object content to set, or `null` if requesting the + * current content. + * + * @return string The raw binary string of the file contents. + */ + public function content($content = NULL) + { + if ($content == NULL) { + return $this->content; + } + $this->content = $content; + } + + /** + * Returns the size of the file content in bytes, equivalent to + * `strlen($blob->content())`. + * + * @return int The size of the object in bytes. + */ + public function size() + { + return strlen($this->content); + } + + /** + * Updates and returns the SHA-1 id of the object, based on it's contents. + * + * @return int The SHA-1 id of the object. + */ + public function sha() + { + $sha = hash_init('sha1'); + $header = 'blob ' . strlen($this->content) . "\0"; + hash_update($sha, $header); + hash_update($sha, $this->content); + $this->sha = hash_final($sha); + return $this->sha; + } + + /** + * Returns the mimetype of the object, using `finfo()` to determine the mimetype + * of the string. + * + * @return string The object mimetype. + * @see http://php.net/manual/en/function.finfo-open.php + */ + public function mimetype() + { + $finfo = new finfo(FILEINFO_MIME); + return $finfo->buffer($this->content); + } + + /** + * Encode and compress the object content, saving it to a 'loose' file. + * + * @return boolean True on success, false on failure. + */ + public function write() + { + $sha = $this->sha(TRUE); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = 'blob ' . strlen($this->content) . "\0" . $this->content; + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + +} diff --git a/3rdparty/granite/git/commit.php b/3rdparty/granite/git/commit.php new file mode 100644 index 0000000000..51077e89f3 --- /dev/null +++ b/3rdparty/granite/git/commit.php @@ -0,0 +1,232 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Object\Raw as Raw; +use \InvalidArgumentException as InvalidArgumentException; + +/** + * Commit represents a full commit object + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Commit +{ + + /** + * The path to the repository root + */ + private $path; + /** + * The SHA-1 id of the requested commit + */ + private $sha; + /** + * The size of the commit in bytes + */ + private $size; + /** + * The commit message + */ + private $message; + /** + * The full committer string + */ + private $committer; + /** + * The full author string + */ + private $author; + /** + * The SHA-1 ids of the parent commits + */ + private $parents = array(); + + /** + * Fetches a raw Git object and parses the result. Throws an + * InvalidArgumentException if the object is not of the correct type, + * or cannot be found. + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @throws InvalidArgumentException + */ + public function __construct($path, $sha = NULL) + { + $this->path = $path; + if ($sha !== NULL) { + $this->sha = $sha; + $object = Raw::factory($path, $sha); + $this->size = $object->size(); + + if ($object->type() !== Raw::OBJ_COMMIT) { + throw new InvalidArgumentException( + "The object $sha is not a commit, type is " . $object->type() + ); + } + + // Parse headers and commit message (delimited with "\n\n") + list($headers, $this->message) = explode("\n\n", $object->content(), 2); + $headers = explode("\n", $headers); + + foreach ($headers as $header) { + list($header, $value) = explode(' ', $header, 2); + if ($header == 'parent') { + $this->parents[] = $value; + } else { + $this->$header = $value; + } + } + + $this->tree = new Tree($this->path, $this->tree); + } + } + + /** + * Returns the message stored in the commit + * + * @return string The commit message + */ + public function message($message = NULL) + { + if ($message !== NULL) { + $this->message = $message; + return $this; + } + return $this->message; + } + + /** + * Returns the commiter string + * + * @return string The committer string + */ + public function committer($committer = NULL) + { + if ($committer !== NULL) { + $this->committer = $committer; + return $this; + } + return $this->committer; + } + + /** + * Returns the author string + * + * @return string The author string + */ + public function author($author = NULL) + { + if ($author !== NULL) { + $this->author = $author; + return $this; + } + return $this->author; + } + + /** + * Returns the parents of the commit, or an empty array if none + * + * @return array The parents of the commit + */ + public function parents($parents = NULL) + { + if ($parents !== NULL) { + $this->parents = $parents; + return $this; + } + return $this->parents; + } + + /** + * Returns a tree object associated with the commit + * + * @return Tree + */ + public function tree(Tree $tree = NULL) + { + if ($tree !== NULL) { + $this->tree = $tree; + return $this; + } + return $this->tree; + } + + /** + * Returns the size of the commit in bytes (Git header + data) + * + * @return int + */ + public function size() + { + return $this->size; + } + + /** + * Returns the size of the commit in bytes (Git header + data) + * + * @return int + */ + public function sha() + { + $this->sha = hash('sha1', $this->_raw()); + return $this->sha; + } + + public function write() + { + $sha = $this->sha(); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = $this->_raw(); + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + + public function _raw() + { + $data = 'tree ' . $this->tree->sha() . "\n"; + foreach ($this->parents as $parent) + { + $data .= "parent $parent\n"; + } + $data .= 'author ' . $this->author . "\n"; + $data .= 'committer ' . $this->committer . "\n\n"; + $data .= $this->message; + + $data = 'commit ' . strlen($data) . "\0" . $data; + return $data; + } + +} diff --git a/3rdparty/granite/git/object/index.php b/3rdparty/granite/git/object/index.php new file mode 100644 index 0000000000..239706d4ef --- /dev/null +++ b/3rdparty/granite/git/object/index.php @@ -0,0 +1,210 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Index represents a packfile index + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link http://craig0990.github.com/Granite/ + */ +class Index +{ + const INDEX_MAGIC = "\377tOc"; + + /** + * The full path to the packfile index + */ + private $path; + /** + * The offset at which the fanout begins, version 2+ indexes have a 2-byte header + */ + private $offset = 8; + /** + * The size of the SHA-1 entries, version 1 stores 4-byte offsets alongside to + * total 24 bytes, version 2+ stores offsets separately + */ + private $size = 20; + /** + * The version of the index file format, versions 1 and 2 are in use and + * currently supported + */ + private $version; + + /** + * Fetches a raw Git object and parses the result + * + * @param string $path The path to the repository root + * @param string $packname The name of the packfile index to read + */ + public function __construct($path, $packname) + { + $this->path = $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-' . $packname . '.idx'; + + $this->version = $this->_readVersion(); + if ($this->version !== 1 && $this->version !== 2) { + throw new UnexpectedValueException( + "Unsupported index version (version $version)" + ); + } + + if ($this->version == 1) { + $this->offset = 0; // Version 1 index has no header/version + $this->size = 24; // Offsets + SHA-1 ids are stored together + } + } + + /** + * Returns the offset of the object stored in the index + * + * @param string $sha The SHA-1 id of the object being requested + * + * @return int The offset of the object in the packfile + */ + public function find($sha) + { + $index = fopen($this->path, 'rb'); + $offset = false; // Offset for object in packfile not found by default + + // Read the fanout to skip to the start char in the sorted SHA-1 list + list($start, $after) = $this->_readFanout($index, $sha); + + if ($start == $after) { + fclose($index); + return false; // Object is apparently located in a 0-length section + } + + // Seek $offset + 255 4-byte fanout entries and read 256th entry + fseek($index, $this->offset + 4 * 255); + $totalObjects = $this->_uint32($index); + + // Look up the SHA-1 id of the object + // TODO: Binary search + fseek($index, $this->offset + 1024 + $this->size * $start); + for ($i = $start; $i < $after; $i++) { + if ($this->version == 1) { + $offset = $this->_uint32($index); + } + + $name = fread($index, 20); + if ($name == pack('H40', $sha)) { + break; // Found it + } + } + + if ($i == $after) { + fclose($index); + return false; // Scanned entire section, couldn't find it + } + + if ($this->version == 2) { + // Jump to the offset location and read it + fseek($index, 1032 + 24 * $totalObjects + 4 * $i); + $offset = $this->_uint32($index); + if ($offset & 0x80000000) { + // Offset is a 64-bit integer; packfile is larger than 2GB + fclose($index); + throw new UnexpectedValueException( + "Packfile larger than 2GB, currently unsupported" + ); + } + } + + fclose($index); + return $offset; + } + + /** + * Converts a binary string into a 32-bit unsigned integer + * + * @param handle $file Binary string to convert + * + * @return int Integer value + */ + private function _uint32($file) + { + $val = unpack('Nx', fread($file, 4)); + return $val['x']; + } + + /** + * Reads the fanout for a particular SHA-1 id + * + * Largely modified from Glip, with some reference to Grit - largely because I + * can't see how to re-implement this in PHP + * + * @param handle $file File handle to the index file + * @param string $sha The SHA-1 id to search for + * @param int $offset The offset at which the fanout begins + * + * @return array Array containing integer 'start' and + * 'past-the-end' locations + */ + private function _readFanout($file, $sha) + { + $sha = pack('H40', $sha); + fseek($file, $this->offset); + if ($sha{0} == "\00") { + /** + * First character is 0, read first fanout entry to provide + * 'past-the-end' location (since first fanout entry provides start + * point for '1'-prefixed SHA-1 ids) + */ + $start = 0; + fseek($file, $this->offset); // Jump to start of fanout, $offset bytes in + $after = $this->_uint32($file); + } else { + /** + * Take ASCII value of first character, minus one to get the fanout + * position of the offset (minus one because the fanout does not + * contain an entry for "\00"), multiplied by four bytes per entry + */ + fseek($file, $this->offset + (ord($sha{0}) - 1) * 4); + $start = $this->_uint32($file); + $after = $this->_uint32($file); + } + + return array($start, $after); + } + + /** + * Returns the version number of the index file, or 1 if there is no version + * information + * + * @return int + */ + private function _readVersion() + { + $file = fopen($this->path, 'rb'); + $magic = fread($file, 4); + $version = $this->_uint32($file); + + if ($magic !== self::INDEX_MAGIC) { + $version = 1; + } + + fclose($file); + return $version; + } + +} diff --git a/3rdparty/granite/git/object/loose.php b/3rdparty/granite/git/object/loose.php new file mode 100644 index 0000000000..32f894845b --- /dev/null +++ b/3rdparty/granite/git/object/loose.php @@ -0,0 +1,81 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Loose represents a loose object in the Git repository + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Loose extends Raw +{ + + /** + * Reads an object from a loose object file based on the SHA-1 id + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @throws UnexpectedValueException If the type is not 'commit', 'tree', + * 'tag' or 'blob' + */ + public function __construct($path, $sha) + { + $this->sha = $sha; + + $loose_path = $path + . 'objects/' + . substr($sha, 0, 2) + . '/' + . substr($sha, 2); + + if (!file_exists($loose_path)) { + throw new InvalidArgumentException("Cannot open loose object file for $sha"); + } + + $raw = gzuncompress(file_get_contents($loose_path)); + $data = explode("\0", $raw, 2); + + $header = $data[0]; + $this->content = $data[1]; + + list($this->type, $this->size) = explode(' ', $header); + + switch ($this->type) { + case 'commit': + $this->type = Raw::OBJ_COMMIT; + break; + case 'tree': + $this->type = Raw::OBJ_TREE; + break; + case 'blob': + $this->type = Raw::OBJ_BLOB; + break; + case 'tag': + $this->type = Raw::OBJ_TAG; + break; + default: + throw new UnexpectedValueException( + "Unexpected type '{$this->type}'" + ); + break; + } + } + +} diff --git a/3rdparty/granite/git/object/packed.php b/3rdparty/granite/git/object/packed.php new file mode 100644 index 0000000000..7e8d663b32 --- /dev/null +++ b/3rdparty/granite/git/object/packed.php @@ -0,0 +1,304 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Packed represents a packed object in the Git repository + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Packed extends Raw +{ + + /** + * The name of the packfile being read + */ + private $_packfile; + + /** + * Added to the object size to make a 'best-guess' effort at how much compressed + * data to read - should be reimplemented, ideally with streams. + */ + const OBJ_PADDING = 512; + + /** + * Reads the object data from the compressed data at $offset in $packfile + * + * @param string $packfile The path to the packfile + * @param int $offset The offset of the object data + */ + public function __construct($packfile, $offset) + { + $this->_packfile = $packfile; + + list($this->type, $this->size, $this->content) + = $this->_readPackedObject($offset); + } + + /** + * Reads the object data at $this->_offset + * + * @param int $offset Offset of the object header + * + * @return array Containing the type, size and object data + */ + private function _readPackedObject($offset) + { + $file = fopen($this->_packfile, 'rb'); + fseek($file, $offset); + // Read the type and uncompressed size from the object header + list($type, $size) = $this->_readHeader($file, $offset); + $object_offset = ftell($file); + + if ($type == self::OBJ_OFS_DELTA || $type == self::OBJ_REF_DELTA) { + return $this->_unpackDeltified( + $file, $offset, $object_offset, $type, $size + ); + } + + $content = gzuncompress(fread($file, $size + self::OBJ_PADDING), $size); + + return array($type, $size, $content); + } + + /** + * Reads a packed object header, returning the type and the size. For more + * detailed information, refer to the @see tag. + * + * From the @see tag: "Each byte is really 7 bits of data, with the first bit + * being used to say if that hunk is the last one or not before the data starts. + * If the first bit is a 1, you will read another byte, otherwise the data starts + * next. The first 3 bits in the first byte specifies the type of data..." + * + * @param handle $file File handle to read + * @param int $offset Offset of the object header + * + * @return array Containing the type and the size + * @see http://book.git-scm.com/7_the_packfile.html + */ + private function _readHeader($file, $offset) + { + // Read the object header byte-by-byte + fseek($file, $offset); + $byte = ord(fgetc($file)); + /** + * Bit-shift right by four, then ignore the first bit with a bitwise AND + * This gives us the object type in binary: + * 001 commit self::OBJ_COMMIT + * 010 tree self::OBJ_TREE + * 011 blob self::OBJ_BLOB + * 100 tag self::OBJ_TAG + * 110 offset delta self::OBJ_OFS_DELTA + * 111 ref delta self::OBJ_REF_DELTA + * + * (000 is undefined, 101 is not currently in use) + * See http://book.git-scm.com/7_the_packfile.html for details + */ + $type = ($byte >> 4) & 0x07; + + // Read the last four bits of the first byte, used to find the size + $size = $byte & 0x0F; + + /** + * $shift initially set to four, since we use the last four bits of the first + * byte + * + * $byte & 0x80 checks the initial bit is set to 1 (i.e. keep reading data) + * + * Finally, $shift is incremented by seven for each consecutive byte (because + * we ignore the initial bit) + */ + for ($shift = 4; $byte & 0x80; $shift += 7) { + $byte = ord(fgetc($file)); + /** + * The size is ANDed against 0x7F to strip the initial bit, then + * bitshifted by left $shift (4 or 7, depending on whether it's the + * initial byte) and ORed against the existing binary $size. This + * continuously increments the $size variable. + */ + $size |= (($byte & 0x7F) << $shift); + } + + return array($type, $size); + } + + /** + * Unpacks a deltified object located at $offset in $file + * + * @param handle $file File handle to read + * @param int $offset Offset of the object data + * @param int $object_offset Offset of the object data, past the header + * @param int $type The object type, either OBJ_REF_DELTA + or OBJ_OFS_DELTA + * @param int $size The expected size of the uncompressed data + * + * @return array Containing the type, size and object data + */ + private function _unpackDeltified($file, $offset, $object_offset, $type, $size) + { + fseek($file, $object_offset); + + if ($type == self::OBJ_REF_DELTA) { + + $base_sha = bin2hex(fread($file, 20)); + + $path = substr($this->_packfile, 0, strpos($this->_packfile, '.git')+5); + $base = Raw::factory($path, $base_sha); + $type = $base->type(); + $base = $base->content(); + + $delta = gzuncompress( + fread($file, $size + self::OBJ_PADDING), $size + ); + + $content = $this->_applyDelta($base, $delta); + + } elseif ($type == self::OBJ_OFS_DELTA) { + + // 20 = maximum varint size according to Glip + $data = fread($file, $size + self::OBJ_PADDING + 20); + + list($base_offset, $length) = $this->_bigEndianNumber($data); + + $delta = gzuncompress(substr($data, $length), $size); + unset($data); + + $base_offset = $offset - $base_offset; + list($type, $size, $base) = $this->_readPackedObject($base_offset); + + $content = $this->_applyDelta($base, $delta); + + } else { + throw new UnexpectedValueException( + "Unknown type $type for deltified object" + ); + } + + return array($type, strlen($content), $content); + } + + /** + * Applies the $delta byte-sequence to $base and returns the + * resultant binary string. + * + * This code is modified from Grit (see below), the Ruby + * implementation used for GitHub under an MIT license. + * + * @param string $base The base string for the delta to be applied to + * @param string $delta The delta string to apply + * + * @return string The patched binary string + * @see + * https://github.com/mojombo/grit/blob/master/lib/grit/git-ruby/internal/pack.rb + */ + private function _applyDelta($base, $delta) + { + $pos = 0; + $src_size = $this->_varint($delta, $pos); + $dst_size = $this->_varint($delta, $pos); + + if ($src_size !== strlen($base)) { + throw new UnexpectedValueException( + 'Expected base delta size ' . strlen($base) . ' does not match the expected ' + . "value $src_size" + ); + } + + $dest = ""; + while ($pos < strlen($delta)) { + $byte = ord($delta{$pos++}); + + if ($byte & 0x80) { + /* copy a part of $base */ + $offset = 0; + if ($byte & 0x01) $offset = ord($delta{$pos++}); + if ($byte & 0x02) $offset |= ord($delta{$pos++}) << 8; + if ($byte & 0x04) $offset |= ord($delta{$pos++}) << 16; + if ($byte & 0x08) $offset |= ord($delta{$pos++}) << 24; + $length = 0; + if ($byte & 0x10) $length = ord($delta{$pos++}); + if ($byte & 0x20) $length |= ord($delta{$pos++}) << 8; + if ($byte & 0x40) $length |= ord($delta{$pos++}) << 16; + if ($length == 0) $length = 0x10000; + $dest .= substr($base, $offset, $length); + } else { + /* take the next $byte bytes as they are */ + $dest .= substr($delta, $pos, $byte); + $pos += $byte; + } + } + + if (strlen($dest) !== $dst_size) { + throw new UnexpectedValueException( + "Deltified string expected to be $dst_size bytes, but actually " + . strlen($dest) . ' bytes' + ); + } + + return $dest; + } + + /** + * Parse a Git varint (variable-length integer). Used in the `_applyDelta()` + * method to read the delta header. + * + * @param string $string The string to parse + * @param int &$pos The position in the string to read from + * + * @return int The integer value + */ + private function _varint($string, &$pos = 0) + { + $varint = 0; + $bitmask = 0x80; + for ($i = 0; $bitmask & 0x80; $i += 7) { + $bitmask = ord($string{$pos++}); + $varint |= (($bitmask & 0x7F) << $i); + } + return $varint; + } + + /** + * Decodes a big endian modified base 128 number (refer to @see tag); this only + * appears to be used in one place, the offset delta in packfiles. The offset + * is the number of bytes to seek back from the start of the delta object to find + * the base object. + * + * This code has been implemented using the C code given in the @see tag below. + * + * @param string &$data The data to read from and decode the number + * + * @return Array Containing the base offset (number of bytes to seek back) and + * the length to use when reading the delta + * @see http://git.rsbx.net/Documents/Git_Data_Formats.txt + */ + private function _bigEndianNumber(&$data) + { + $i = 0; + $byte = ord($data{$i++}); + $number = $byte & 0x7F; + while ($byte & 0x80) { + $byte = ord($data{$i++}); + $number = (($number + 1) << 7) | ($byte & 0x7F); + } + + return array($number, $i); + } + +} diff --git a/3rdparty/granite/git/object/raw.php b/3rdparty/granite/git/object/raw.php new file mode 100644 index 0000000000..56f363c37b --- /dev/null +++ b/3rdparty/granite/git/object/raw.php @@ -0,0 +1,153 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \InvalidArgumentException as InvalidArgumentException; + +/** + * Raw represents a raw Git object, using Index to locate + * packed objects. + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Raw +{ + /** + * Integer values for Git objects + * @see http://book.git-scm.com/7_the_packfile.html + */ + const OBJ_COMMIT = 1; + const OBJ_TREE = 2; + const OBJ_BLOB = 3; + const OBJ_TAG = 4; + const OBJ_OFS_DELTA = 6; + const OBJ_REF_DELTA = 7; + + /** + * The SHA-1 id of the requested object + */ + protected $sha; + /** + * The type of the requested object (see class constants) + */ + protected $type; + /** + * The binary string content of the requested object + */ + protected $content; + + /** + * Returns an instance of a raw Git object + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @return Packed|Loose + */ + public static function factory($path, $sha) + { + $loose_path = $path + . 'objects/' + . substr($sha, 0, 2) + . '/' + . substr($sha, 2); + if (file_exists($loose_path)) { + return new Loose($path, $sha); + } else { + return self::_findPackedObject($path, $sha); + } + } + + /** + * Returns the raw content of the Git object requested + * + * @return string Raw object content + */ + public function content() + { + return $this->content; + } + + /** + * Returns the size of the Git object + * + * @return int The size of the object in bytes + */ + public function size() + { + return strlen($this->content); + } + + /** + * Returns the type of the object as either commit, tag, blob or tree + * + * @return string The object type + */ + public function type() + { + return $this->type; + } + + /** + * Searches a packfile for the SHA id and reads the object from the packfile + * + * @param string $path The path to the repository + * @param string $sha The SHA-1 id of the object being requested + * + * @throws \InvalidArgumentException + * @return array An array containing the type, size and object data + */ + private static function _findPackedObject($path, $sha) + { + $packfiles = glob( + $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-*.pack' + ); + + $offset = false; + foreach ($packfiles as $packfile) { + $packname = substr(basename($packfile, '.pack'), 5); + $idx = new Index($path, $packname); + $offset = $idx->find($sha); + + if ($offset !== false) { + break; // Found it + } + } + + if ($offset == false) { + throw new InvalidArgumentException("Could not find packed object $sha"); + } + + $packname = $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-' . $packname . '.pack'; + $object = new Packed($packname, $offset); + + return $object; + } + +} + +?> diff --git a/3rdparty/granite/git/repository.php b/3rdparty/granite/git/repository.php new file mode 100644 index 0000000000..30b58a39f5 --- /dev/null +++ b/3rdparty/granite/git/repository.php @@ -0,0 +1,293 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \InvalidArgumentException as InvalidArgumentException; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Repository represents a Git repository, providing a variety of methods for + * fetching objects from SHA-1 ids or the tip of a branch with `head()` + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Repository +{ + + /** + * The path to the repository root + */ + private $_path; + /** + * The indexed version of a commit, ready to write with `commit()` + */ + private $idx_commit; + /** + * The indexed version of a tree, modified to with `add()` and `remove()` + */ + private $idx_tree; + + /** + * Sets the repository path + * + * @param string $path The path to the repository root (i.e. /repo/.git/) + */ + public function __construct($path) + { + if (!is_dir($path)) { + throw new InvalidArgumentException("Unable to find directory $path"); + } elseif (!is_readable($path)) { + throw new InvalidArgumentException("Unable to read directory $path"); + } elseif (!is_dir($path . DIRECTORY_SEPARATOR . 'objects') + || !is_dir($path . DIRECTORY_SEPARATOR . 'refs') + ) { + throw new UnexpectedValueException( + "Invalid directory, could not find 'objects' or 'refs' in $path" + ); + } + + $this->_path = $path; + $this->idx_commit = $this->factory('commit'); + $this->idx_tree = $this->factory('tree'); + } + + /** + * Returns an object from the Repository of the given type, with the given + * SHA-1 id, or false if it cannot be found + * + * @param string $type The type (blob, commit, tag or tree) of object being + * requested + * @param string $sha The SHA-1 id of the object (or the name of a tag) + * + * @return Blob|Commit|Tag|Tree + */ + public function factory($type, $sha = null) + { + if (!in_array($type, array('blob', 'commit', 'tag', 'tree'))) { + throw new InvalidArgumentException("Invalid type: $type"); + } + + if ($type == 'tag') { + $sha = $this->_ref('tags' . DIRECTORY_SEPARATOR . $sha); + } + $type = 'Granite\\Git\\' . ucwords($type); + + return new $type($this->_path, $sha); + } + + /** + * Returns a Commit object representing the HEAD commit + * + * @param string $branch The branch name to lookup, defaults to 'master' + * + * @return Commit An object representing the HEAD commit + */ + public function head($branch = 'master', $value = NULL) + { + if ($value == NULL) + return $this->factory( + 'commit', $this->_ref('heads' . DIRECTORY_SEPARATOR . $branch) + ); + + file_put_contents( + $this->_path . DIRECTORY_SEPARATOR + . 'refs' . DIRECTORY_SEPARATOR + . 'heads' . DIRECTORY_SEPARATOR . 'master', + $value + ); + } + + /** + * Returns a string representing the repository's location, which may or may + * not be initialised + * + * @return string A string representing the repository's location + */ + public function path() + { + return $this->_path; + } + + /** + * Returns an array of the local branches under `refs/heads` + * + * @return array + */ + public function tags() + { + return $this->_refs('tags'); + } + + /** + * Returns an array of the local tags under `refs/tags` + * + * @return array + */ + public function branches() + { + return $this->_refs('heads'); + } + + private function _refs($type) + { + $dir = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $type; + $refs = glob($dir . DIRECTORY_SEPARATOR . '*'); + foreach ($refs as &$ref) { + $ref = basename($ref); + } + return $refs; + } + + /** + * Initialises a Git repository + * + * @return boolean Returns true on success, false on error + */ + public static function init($path) + { + $path .= '/'; + if (!is_dir($path)) { + mkdir($path); + } elseif (is_dir($path . 'objects')) { + return false; + } + + mkdir($path . 'objects'); + mkdir($path . 'objects/info'); + mkdir($path . 'objects/pack'); + mkdir($path . 'refs'); + mkdir($path . 'refs/heads'); + mkdir($path . 'refs/tags'); + + file_put_contents($path . 'HEAD', 'ref: refs/heads/master'); + + return true; + } + + /** + * Writes the indexed commit to disk, with blobs added/removed via `add()` and + * `rm()` + * + * @param string $message The commit message + * @param string $author The author name + * + * @return boolean True on success, or false on failure + */ + public function commit($message, $author) + { + $user_string = $username . ' ' . time() . ' +0000'; + + try { + $parents = array($this->repo->head()->sha()); + } catch (InvalidArgumentException $e) { + $parents = array(); + } + + $this->idx_commit->message($message); + $this->idx_commit->author($user_string); + $this->idx_commit->committer($user_string); + $this->idx_commit->tree($this->idx_tree); + $commit->parents($parents); + + $this->idx_tree->write(); + $this->idx_commit->write(); + + $this->repo->head('master', $this->idx_commit->sha()); + + $this->idx_commit = $this->factory('commit'); + $this->idx_tree = $this->factory('tree'); + } + + /** + * Adds a file to the indexed commit, to be written to disk with `commit()` + * + * @param string $filename The filename to save it under + * @param Granite\Git\Blob $blob The raw blob object to add to the tree + */ + public function add($filename, Granite\Git\Blob $blob) + { + $blob->write(); + $nodes = $this->idx_tree->nodes(); + $nodes[$filename] = new Granite\Git\Tree\Node($filename, '100644', $blob->sha()); + $this->idx_tree->nodes($nodes); + } + + /** + * Removes a file from the indexed commit + */ + public function rm($filename) + { + $nodes = $this->idx_tree->nodes(); + unset($nodes[$filename]); + $this->idx_tree->nodes($nodes); + } + + /** + * Returns an SHA-1 id of the ref resource + * + * @param string $ref The ref name to lookup + * + * @return string An SHA-1 id of the ref resource + */ + private function _ref($ref) + { + // All refs are stored in `.git/refs` + $file = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $ref; + + if (file_exists($file)) { + return trim(file_get_contents($file)); + } + + $sha = $this->_packedRef($ref); + + if ($sha == false) { + throw new InvalidArgumentException("The ref $ref could not be found"); + } + + return $sha; + } + + /** + * Returns an SHA-1 id of the ref resource, or false if it cannot be found + * + * @param string $ref The ref name to lookup + * + * @return string An SHA-1 id of the ref resource + */ + private function _packedRef($ref) + { + $sha = false; + if (file_exists($this->_path . 'packed-refs')) { + $file = fopen($this->_path . 'packed-refs', 'r'); + + while (($line = fgets($file)) !== false) { + $info = explode(' ', $line); + if (count($info) == 2 + && trim($info[1]) == 'refs' . DIRECTORY_SEPARATOR . $ref + ) { + $sha = trim($info[0]); + break; + } + } + + fclose($file); + } + + return $sha; + } + +} diff --git a/3rdparty/granite/git/tag.php b/3rdparty/granite/git/tag.php new file mode 100644 index 0000000000..e26ddaffa6 --- /dev/null +++ b/3rdparty/granite/git/tag.php @@ -0,0 +1,38 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; + +/** + * Tag represents a full tag object + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Tag +{ + + public function __construct($path, $sha) + { + $this->sha = $sha; + } + + public function sha() + { + return $this->sha; + } + +} diff --git a/3rdparty/granite/git/tree.php b/3rdparty/granite/git/tree.php new file mode 100644 index 0000000000..2de7227453 --- /dev/null +++ b/3rdparty/granite/git/tree.php @@ -0,0 +1,198 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Tree\Node as Node; + +/** + * Tree represents a full tree object, with nodes pointing to other tree objects + * and file blobs + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Tree +{ + + /** + * The SHA-1 id of the requested tree + */ + private $sha; + /** + * The nodes/entries for the requested tree + */ + private $nodes = array(); + /** + * The path to the repository + */ + private $path; + + /** + * Reads a tree object by fetching the raw object + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + */ + public function __construct($path, $sha = NULL, $dbg = FALSE) + { + $this->path = $path; + if ($sha !== NULL) { + $object = Object\Raw::factory($path, $sha); + $this->sha = $sha; + + if ($object->type() !== Object\Raw::OBJ_TREE) { + throw new \InvalidArgumentException( + "The object $sha is not a tree, type is " . $object->type() + ); + } + + $content = $object->content(); + file_put_contents('/tmp/tree_from_real_repo'.time(), $content); + $nodes = array(); + + for ($i = 0; $i < strlen($content); $i = $data_start + 21) { + $data_start = strpos($content, "\0", $i); + $info = substr($content, $i, $data_start-$i); + list($mode, $name) = explode(' ', $info, 2); + // Read the object SHA-1 id + $sha = bin2hex(substr($content, $data_start + 1, 20)); + + $this->nodes[$name] = new Node($name, $mode, $sha); + } + } + } + + /** + * Returns an array of Tree and Granite\Git\Blob objects, + * representing subdirectories and files + * + * @return array Array of Tree and Granite\Git\Blob objects + */ + public function nodes($nodes = null) + { + if ($nodes == null) { + return $this->nodes; + } + $this->nodes = $nodes; + } + + /** + * Adds a blob or a tree to the list of nodes + * + * @param string $name The basename (filename) of the blob or tree + * @param string $mode The mode of the blob or tree (see above) + * @param string $sha The SHA-1 id of the blob or tree to add + */ + public function add($name, $mode, $sha) + { + $this->nodes[$name] = new Node($name, $mode, $sha); + uasort($this->nodes, array($this, '_sort')); + } + + public function write() + { + $sha = $this->sha(); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = $this->_raw(); + $data = 'tree ' . strlen($data) . "\0" . $data; + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + + /** + * Returns the SHA-1 id of the Tree + * + * @return string SHA-1 id of the Tree + */ + public function sha() + { + $data = $this->_raw(); + $raw = 'tree ' . strlen($data) . "\0" . $data; + $this->sha = hash('sha1', $raw); + return $this->sha; + } + + /** + * Generates the raw object content to be saved to disk + */ + public function _raw() + { + uasort($this->nodes, array($this, '_sort')); + $data = ''; + foreach ($this->nodes as $node) + { + $data .= base_convert($node->mode(), 10, 8) . ' ' . $node->name() . "\0"; + $data .= pack('H40', $node->sha()); + } + file_put_contents('/tmp/tree_made'.time(), $data); + return $data; + } + + /** + * Sorts the node entries in a tree, general sort method adapted from original + * Git C code (see @see tag below). + * + * @return 1, 0 or -1 if the first entry is greater than, the same as, or less + * than the second, respectively. + * @see https://github.com/gitster/git/blob/master/read-cache.c Around line 352, + * the `base_name_compare` function + */ + public function _sort(&$a, &$b) + { + $length = strlen($a->name()) < strlen($b->name()) ? strlen($a->name()) : strlen($b->name()); + + $cmp = strncmp($a->name(), $b->name(), $length); + if ($cmp) { + return $cmp; + } + + $suffix1 = $a->name(); + $suffix1 = (strlen($suffix1) > $length) ? $suffix1{$length} : FALSE; + $suffix2 = $b->name(); + $suffix2 = (strlen($suffix2) > $length) ? $suffix2{$length} : FALSE; + if (!$suffix1 && $a->isDirectory()) { + $suffix1 = '/'; + } + if (!$suffix2 && $b->isDirectory()) { + $suffix2 = '/'; + } + if ($suffix1 < $suffix2) { + return -1; + } elseif ($suffix1 > $suffix2) { + return 1; + } + + return 0; + } + +} diff --git a/3rdparty/granite/git/tree/node.php b/3rdparty/granite/git/tree/node.php new file mode 100644 index 0000000000..f99eb1ae28 --- /dev/null +++ b/3rdparty/granite/git/tree/node.php @@ -0,0 +1,126 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Tree; + +/** + * Node represents an entry in a Tree + * + * @category Git + * @package Granite + * @author Craig Roberts + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Node +{ + + /** + * Name of the file, directory or submodule + */ + private $_name; + /** + * Mode of the object, in octal + */ + private $_mode; + /** + * SHA-1 id of the tree + */ + private $_sha; + /** + * Boolean value for whether the entry represents a directory + */ + private $_is_dir; + /** + * Boolean value for whether the entry represents a submodule + */ + private $_is_submodule; + + /** + * Sets up a Node class with properties corresponding to the $mode parameter + * + * @param string $name The name of the object (file, directory or submodule name) + * @param int $mode The mode of the object, retrieved from the repository + * @param string $sha The SHA-1 id of the object + */ + public function __construct($name, $mode, $sha) + { + $this->_name = $name; + $this->_mode = intval($mode, 8); + $this->_sha = $sha; + + $this->_is_dir = (bool) ($this->_mode & 0x4000); + $this->_is_submodule = ($this->_mode == 0xE000); + } + + /** + * Returns a boolean value indicating whether the node is a directory + * + * @return boolean + */ + public function isDirectory() + { + return $this->_is_dir; + } + + /** + * Returns a boolean value indicating whether the node is a submodule + * + * @return boolean + */ + public function isSubmodule() + { + return $this->_is_submodule; + } + + /** + * Returns the object name + * + * @return string + */ + public function name() + { + return $this->_name; + } + + /** + * Returns the object's SHA-1 id + * + * @return string + */ + public function sha() + { + return $this->_sha; + } + + /** + * Returns the octal value of the file mode + * + * @return int + */ + public function mode() + { + return $this->_mode; + } + + public function type() + { + if ($this->isDirectory()) { + return 'tree'; + } elseif ($this->isSubmodule()) { + return 'commit'; + } else { + return 'blob'; + } + } +} diff --git a/apps/files_versioning/ajax/gethead.php b/apps/files_versioning/ajax/gethead.php new file mode 100644 index 0000000000..cc93b7a1d1 --- /dev/null +++ b/apps/files_versioning/ajax/gethead.php @@ -0,0 +1,12 @@ + $head)); diff --git a/apps/files_versioning/ajax/sethead.php b/apps/files_versioning/ajax/sethead.php new file mode 100644 index 0000000000..d1b2df9b00 --- /dev/null +++ b/apps/files_versioning/ajax/sethead.php @@ -0,0 +1,14 @@ + 10, + 'id' => 'files_versioning', + 'name' => 'Versioning and Backup' )); + +// Include stylesheets for the settings page +OC_Util::addStyle( 'files_versioning', 'settings' ); +OC_Util::addScript('files_versioning','settings'); + +// Register a settings section in the Admin > Personal page +OC_APP::registerPersonal('files_versioning','settings'); diff --git a/apps/files_versioning/appinfo/info.xml b/apps/files_versioning/appinfo/info.xml new file mode 100644 index 0000000000..d5546be54a --- /dev/null +++ b/apps/files_versioning/appinfo/info.xml @@ -0,0 +1,14 @@ + + + files_versioning + Versioning and Backup + 1.0.0 + GPLv2 + Craig Roberts + 3 + Versions files using Git repositories, providing a simple backup facility. Currently in *beta* and explicitly without warranty of any kind. + + + + + diff --git a/apps/files_versioning/css/settings.css b/apps/files_versioning/css/settings.css new file mode 100644 index 0000000000..afe2cd5508 --- /dev/null +++ b/apps/files_versioning/css/settings.css @@ -0,0 +1,3 @@ +#file_versioning_commit_chzn { + width: 15em; +} diff --git a/apps/files_versioning/js/settings.js b/apps/files_versioning/js/settings.js new file mode 100644 index 0000000000..8dd13bac03 --- /dev/null +++ b/apps/files_versioning/js/settings.js @@ -0,0 +1,25 @@ +$(document).ready(function(){ + $('#file_versioning_head').chosen(); + + $.getJSON(OC.filePath('files_versioning', 'ajax', 'gethead.php'), function(jsondata, status) { + + if (jsondata.head == 'HEAD') { + // Most recent commit, do nothing + } else { + $("#file_versioning_head").val(jsondata.head); + // Trigger the chosen update call + // See http://harvesthq.github.com/chosen/ + $("#file_versioning_head").trigger("liszt:updated"); + } + }); + + $('#file_versioning_head').change(function() { + + var data = $(this).serialize(); + $.post( OC.filePath('files_versioning', 'ajax', 'sethead.php'), data, function(data){ + if(data == 'error'){ + console.log('Saving new HEAD failed'); + } + }); + }); +}); diff --git a/apps/files_versioning/lib_granite.php b/apps/files_versioning/lib_granite.php new file mode 100644 index 0000000000..c69c62d9c4 --- /dev/null +++ b/apps/files_versioning/lib_granite.php @@ -0,0 +1,12 @@ +head(); +for ($i = 0; $i < 50; $i++) { + $commits[] = $commit; + $parents = $commit->parents(); + if (count($parents) > 0) { + $parent = $parents[0]; + } else { + break; + } + + $commit = $repository->factory('commit', $parent); +} + +$tmpl = new OC_Template( 'files_versioning', 'settings'); +$tmpl->assign('commits', $commits); +return $tmpl->fetchPage(); diff --git a/apps/files_versioning/templates/settings.php b/apps/files_versioning/templates/settings.php new file mode 100644 index 0000000000..17f4cc7f77 --- /dev/null +++ b/apps/files_versioning/templates/settings.php @@ -0,0 +1,12 @@ +
+ Versioning and Backup
+

Please note: Backing up large files (around 16MB+) will cause your backup history to grow very large, very quickly.

+ + +
diff --git a/apps/files_versioning/versionstorage.php b/apps/files_versioning/versionstorage.php new file mode 100644 index 0000000000..d083e623df --- /dev/null +++ b/apps/files_versioning/versionstorage.php @@ -0,0 +1,386 @@ +. + */ + +// Include Granite +require_once('lib_granite.php'); + +// Create a top-level 'Backup' directory if it does not already exist +$user = OC_User::getUser(); +if (OC_Filesystem::$loaded and !OC_Filesystem::is_dir('/Backup')) { + OC_Filesystem::mkdir('/Backup'); + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); +} + +// Generate the repository path (currently using 'full' repositories, as opposed to bare ones) +$repo_path = DIRECTORY_SEPARATOR + . OC_User::getUser() + . DIRECTORY_SEPARATOR + . 'files' + . DIRECTORY_SEPARATOR + . 'Backup'; + +// Mount the 'Backup' folder using the versioned storage provider below +OC_Filesystem::mount('OC_Filestorage_Versioned', array('repo'=>$repo_path), $repo_path . DIRECTORY_SEPARATOR); + +class OC_Filestorage_Versioned extends OC_Filestorage { + + /** + * Holds an instance of Granite\Git\Repository + */ + protected $repo; + + /** + * Constructs a new OC_Filestorage_Versioned instance, expects an associative + * array with a `repo` key set to the path of the repository's `.git` folder + * + * @param array $parameters An array containing the key `repo` pointing to the + * repository path. + */ + public function __construct($parameters) { + // Get the full path to the repository folder + $path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data') + . $parameters['repo'] + . DIRECTORY_SEPARATOR + . '.git' + . DIRECTORY_SEPARATOR; + + try { + // Attempt to load the repository + $this->repo = new Granite\Git\Repository($path); + } catch (InvalidArgumentException $e) { + // $path is not a valid Git repository, we must create one + Granite\Git\Repository::init($path); + + // Load the newly-initialised repository + $this->repo = new Granite\Git\Repository($path); + + /** + * Create an initial commit with a README file + * FIXME: This functionality should be transferred to the Granite library + */ + $blob = new Granite\Git\Blob($this->repo->path()); + $blob->content('Your Backup directory is now ready for use.'); + + // Create a new tree to hold the README file + $tree = $this->repo->factory('tree'); + // Create a tree node to represent the README blob + $tree_node = new Granite\Git\Tree\Node('README', '100644', $blob->sha()); + $tree->nodes(array($tree_node->name() => $tree_node)); + + // Create an initial commit + $commit = new Granite\Git\Commit($this->repo->path()); + $user_string = OC_User::getUser() . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message('Initial commit'); + $commit->tree($tree); + + // Write it all to disk + $blob->write(); + $tree->write(); + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + } + + // Update the class pointer to the HEAD + $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); + + // Load the most recent commit if the preference is not set + if ($head == 'HEAD') { + $this->head = $this->repo->head()->sha(); + } else { + $this->head = $head; + } + } + + public function mkdir($path) { + if (mkdir("versioned:/{$this->repo->path()}$path#{$this->head}")) { + $this->head = $this->repo->head()->sha(); + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $head); + return true; + } + + return false; + } + + public function rmdir($path) { + + } + + /** + * Returns a directory handle to the requested path, or FALSE on failure + * + * @param string $path The directory path to open + * + * @return boolean|resource A directory handle, or FALSE on failure + */ + public function opendir($path) { + return opendir("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns TRUE if $path is a directory, or FALSE if not + * + * @param string $path The path to check + * + * @return boolean + */ + public function is_dir($path) { + return $this->filetype($path) == 'dir'; + } + + /** + * Returns TRUE if $path is a file, or FALSE if not + * + * @param string $path The path to check + * + * @return boolean + */ + public function is_file($path) { + return $this->filetype($path) == 'file'; + } + + public function stat($path) + { + return stat("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns the strings 'dir' or 'file', depending on the type of $path + * + * @param string $path The path to check + * + * @return string Returns 'dir' if a directory, 'file' otherwise + */ + public function filetype($path) { + if ($path == "" || $path == "/") { + return 'dir'; + } else { + if (substr($path, -1) == '/') { + $path = substr($path, 0, -1); + } + + $node = $this->tree_search($this->repo, $this->repo->factory('commit', $this->head)->tree(), $path); + + // Does it exist, or is it new? + if ($node == null) { + // New file + return 'file'; + } else { + // Is it a tree? + try { + $this->repo->factory('tree', $node); + return 'dir'; + } catch (InvalidArgumentException $e) { + // Nope, must be a blob + return 'file'; + } + } + } + } + + public function filesize($path) { + return filesize("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns a boolean value representing whether $path is readable + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path is readable + */ + public function is_readable($path) { + return true; + } + + /** + * Returns a boolean value representing whether $path is writable + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path is writable + */ + public function is_writable($path) { + + $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); + if ($head !== 'HEAD' && $head !== $this->repo->head()->sha()) { + // Cannot modify previous commits + return false; + } + return true; + } + + /** + * Returns a boolean value representing whether $path exists + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path exists + */ + public function file_exists($path) { + return file_exists("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns an integer value representing the inode change time + * (NOT IMPLEMENTED) + * + * @param string $path The path to check + *( + * @return int Timestamp of the last inode change + */ + public function filectime($path) { + return -1; + } + + /** + * Returns an integer value representing the file modification time + * + * @param string $path The path to check + *( + * @return int Timestamp of the last file modification + */ + public function filemtime($path) { + return filemtime("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + public function file_get_contents($path) { + return file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + public function file_put_contents($path, $data) { + $success = file_put_contents("versioned:/{$this->repo->path()}$path#{$this->head}", $data); + if ($success !== false) { + // Update the HEAD in the preferences + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $this->repo->head()->sha()); + return $success; + } + + return false; + } + + public function unlink($path) { + + } + + public function rename($path1, $path2) { + + } + + public function copy($path1, $path2) { + + } + + public function fopen($path, $mode) { + return fopen("versioned:/{$this->repo->path()}$path#{$this->head}", $mode); + } + + public function getMimeType($path) { + if ($this->filetype($path) == 'dir') { + return 'httpd/unix-directory'; + } elseif ($this->filesize($path) == 0) { + // File's empty, returning text/plain allows opening in the web editor + return 'text/plain'; + } else { + $finfo = new finfo(FILEINFO_MIME_TYPE); + /** + * We need to represent the repository path, the file path, and the + * revision, which can be simply achieved with a convention of using + * `.git` in the repository directory (bare or not) and the '#part' + * segment of a URL to specify the revision. For example + * + * versioned://var/www/myrepo.git/docs/README.md#HEAD ('bare' repo) + * versioned://var/www/myrepo/.git/docs/README.md#HEAD ('full' repo) + * versioned://var/www/myrepo/.git/docs/README.md#6a8f...8a54 ('full' repo and SHA-1 commit ID) + */ + $mime = $finfo->buffer(file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}")); + return $mime; + } + } + + /** + * Generates a hash based on the file contents + * + * @param string $type The hashing algorithm to use (e.g. 'md5', 'sha256', etc.) + * @param string $path The file to be hashed + * @param boolean $raw Outputs binary data if true, lowercase hex digits otherwise + * + * @return string Hashed string representing the file contents + */ + public function hash($type, $path, $raw) { + return hash($type, file_get_contents($path), $raw); + } + + public function free_space($path) { + } + + public function search($query) { + + } + + public function touch($path, $mtime=null) { + + } + + + public function getLocalFile($path) { + } + + /** + * Recursively searches a tree for a path, returning FALSE if is not found + * or an SHA-1 id if it is found. + * + * @param string $repo The repository containing the tree object + * @param string $tree The tree object to search + * @param string $path The path to search for (relative to the tree) + * @param int $depth The depth of the current search (for recursion) + * + * @return string|boolean The SHA-1 id of the sub-tree + */ + private function tree_search($repo, $tree, $path, $depth = 0) + { + $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); + + $current_path = $paths[$depth]; + + $nodes = $tree->nodes(); + foreach ($nodes as $node) { + if ($node->name() == $current_path) { + + if (count($paths)-1 == $depth) { + // Stop, found it + return $node->sha(); + } + + // Recurse if necessary + if ($node->isDirectory()) { + $tree = $this->repo->factory('tree', $node->sha()); + return $this->tree_search($repo, $tree, $path, $depth + 1); + } + } + } + + return false; + } + +} diff --git a/apps/files_versioning/versionwrapper.php b/apps/files_versioning/versionwrapper.php new file mode 100644 index 0000000000..b83a4fd3b2 --- /dev/null +++ b/apps/files_versioning/versionwrapper.php @@ -0,0 +1,686 @@ +tree); + return true; + } + + /** + * Open directory handle + */ + public function dir_opendir($path, $options) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + if ($repo_file == '' || $repo_file == '/') { + // Set the tree property for the future `readdir()` etc. calls + $this->tree = array_values($this->commit->tree()->nodes()); + return true; + } elseif ($this->tree_search($this->repo, $this->commit->tree(), $repo_file) !== false) { + // Something exists at this path, is it a directory though? + try { + $tree = $this->repo->factory( + 'tree', + $this->tree_search($this->repo, $this->commit->tree(), $repo_file) + ); + $this->tree = array_values($tree->nodes()); + return true; + } catch (InvalidArgumentException $e) { + // Trying to call `opendir()` on a file, return false below + } + } + + // Unable to find the directory, return false + return false; + } + + /** + * Read entry from directory handle + */ + public function dir_readdir() { + return isset($this->tree[$this->dir_position]) + ? $this->tree[$this->dir_position++]->name() + : false; + } + + /** + * Rewind directory handle + */ + public function dir_rewinddir() { + $this->dir_position = 0; + } + + /** + * Create a directory + * Git doesn't track empty directories, so a ".empty" file is added instead + */ + public function mkdir($path, $mode, $options) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + // Create an empty file for Git + $empty = new Granite\Git\Blob($this->repo->path()); + $empty->content(''); + $empty->write(); + + if (dirname($repo_file) == '.') { + // Adding a new directory to the root tree + $tree = $this->repo->head()->tree(); + } else { + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, $this->repo->head()->tree(), dirname($repo_file) + ) + ); + } + + // Create our new tree, with our empty file + $dir = $this->repo->factory('tree'); + $nodes = array(); + $nodes[self::EMPTYFILE] = new Granite\Git\Tree\Node(self::EMPTYFILE, '100644', $empty->sha()); + $dir->nodes($nodes); + $dir->write(); + + // Add our new tree to its parent + $nodes = $tree->nodes(); + $nodes[basename($repo_file)] = new Granite\Git\Tree\Node(basename($repo_file), '040000', $dir->sha()); + $tree->nodes($nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + + $tree = $previous_tree; + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username created the `$repo_file` directory, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($tree); + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + return true; + } + + /** + * Renames a file or directory + */ + public function rename($path_from, $path_to) { + + } + + /** + * Removes a directory + */ + public function rmdir($path, $options) { + + } + + /** + * Retrieve the underlaying resource (NOT IMPLEMENTED) + */ + public function stream_cast($cast_as) { + return false; + } + + /** + * Close a resource + */ + public function stream_close() { + unset($this->blob); + return true; + } + + /** + * Tests for end-of-file on a file pointer + */ + public function stream_eof() { + return !($this->file_position < strlen($this->blob)); + } + + /** + * Flushes the output (NOT IMPLEMENTED) + */ + public function stream_flush() { + return false; + } + + /** + * Advisory file locking (NOT IMPLEMENTED) + */ + public function stream_lock($operation) { + return false; + } + + /** + * Change stream options (NOT IMPLEMENTED) + * Called in response to `chgrp()`, `chown()`, `chmod()` and `touch()` + */ + public function stream_metadata($path, $option, $var) { + return false; + } + + /** + * Opens file or URL + */ + public function stream_open($path, $mode, $options, &$opened_path) { + // Store the path, so we can use it later in `stream_write()` if necessary + $this->path = $path; + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + $file = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + if ($file !== false) { + try { + $this->blob = $this->repo->factory('blob', $file)->content(); + return true; + } catch (InvalidArgumentException $e) { + // Trying to open a directory, return false below + } + } elseif ($mode !== 'r') { + // All other modes allow opening for reading and writing, clearly + // some 'write' files may not exist yet... + return true; + } + + // File could not be found or is not actually a file + return false; + } + + /** + * Read from stream + */ + public function stream_read($count) { + // Fetch the remaining set of bytes + $bytes = substr($this->blob, $this->file_position, $count); + + // If EOF or empty string, return false + if ($bytes == '' || $bytes == false) { + return false; + } + + // If $count does not extend past EOF, add $count to stream offset + if ($this->file_position + $count < strlen($this->blob)) { + $this->file_position += $count; + } else { + // Otherwise return all remaining bytes + $this->file_position = strlen($this->blob); + } + + return $bytes; + } + + /** + * Seeks to specific location in a stream + */ + public function stream_seek($offset, $whence = SEEK_SET) { + $new_offset = false; + + switch ($whence) + { + case SEEK_SET: + $new_offset = $offset; + break; + case SEEK_CUR: + $new_offset = $this->file_position += $offset; + break; + case SEEK_END: + $new_offset = strlen($this->blob) + $offset; + break; + } + + $this->file_position = $offset; + + return ($new_offset !== false); + } + + /** + * Change stream options (NOT IMPLEMENTED) + */ + public function stream_set_option($option, $arg1, $arg2) { + return false; + } + + /** + * Retrieve information about a file resource (NOT IMPLEMENTED) + */ + public function stream_stat() { + + } + + /** + * Retrieve the current position of a stream + */ + public function stream_tell() { + return $this->file_position; + } + + /** + * Truncate stream + */ + public function stream_truncate($new_size) { + + } + + /** + * Write to stream + * FIXME: Could use heavy refactoring + */ + public function stream_write($data) { + /** + * FIXME: This also needs to be added to Granite, in the form of `add()`, + * `rm()` and `commit()` calls + */ + + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($this->path); + + $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + + if ($node !== false) { + // File already exists, attempting modification of existing tree + try { + $this->repo->factory('blob', $node); + + // Create our new blob with the provided $data + $blob = $this->repo->factory('blob'); + $blob->content($data); + $blob->write(); + + // We know the tree exists, so strip the filename from the path and + // find it... + + if (dirname($repo_file) == '.' || dirname($repo_file) == '') { + // Root directory + $tree = $this->repo->head()->tree(); + } else { + // Sub-directory + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + dirname($repo_file) + ) + ); + } + + // Replace the old blob with our newly modified one + $tree_nodes = $tree->nodes(); + $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($tree_nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username modified the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($previous_tree); + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + // If we made it this far, write was successful - update the stream + // position and return the number of bytes written + $this->file_position += strlen($data); + return strlen($data); + + } catch (InvalidArgumentException $e) { + // Attempting to write to a directory or other error, fail + return 0; + } + } else { + // File does not exist, needs to be created + + // Create our new blob with the provided $data + $blob = $this->repo->factory('blob'); + $blob->content($data); + $blob->write(); + + if (dirname($repo_file) == '.') { + // Trying to add a new file to the root tree, nice and easy + $tree = $this->repo->head()->tree(); + $tree_nodes = $tree->nodes(); + $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($tree_nodes); + $tree->write(); + } else { + // Trying to add a new file to a subdirectory, try and find it + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, $this->repo->head()->tree(), dirname($repo_file) + ) + ); + + // Add the blob to the tree + $nodes = $tree->nodes(); + $nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + + $tree = $previous_tree; + } + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username created the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($tree); // Top-level tree (NOT the newly modified tree) + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + // If we made it this far, write was successful - update the stream + // position and return the number of bytes written + $this->file_position += strlen($data); + return strlen($data); + } + + // Write failed + return 0; + } + + /** + * Delete a file + */ + public function unlink($path) { + + } + + /** + * Retrieve information about a file + */ + public function url_stat($path, $flags) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + + if ($node == false && $this->commit->sha() == $this->repo->head()->sha()) { + // A new file - no information available + $size = 0; + $mtime = -1; + } else { + + // Is it a directory? + try { + $this->repo->factory('tree', $node); + $size = 4096; // FIXME + } catch (InvalidArgumentException $e) { + // Must be a file + $size = strlen(file_get_contents($path)); + } + + // Parse the timestamp from the commit message + preg_match('/[0-9]{10}+/', $this->commit->committer(), $matches); + $mtime = $matches[0]; + } + + $stat["dev"] = ""; + $stat["ino"] = ""; + $stat["mode"] = ""; + $stat["nlink"] = ""; + $stat["uid"] = ""; + $stat["gid"] = ""; + $stat["rdev"] = ""; + $stat["size"] = $size; + $stat["atime"] = $mtime; + $stat["mtime"] = $mtime; + $stat["ctime"] = $mtime; + $stat["blksize"] = ""; + $stat["blocks"] = ""; + + return $stat; + } + + /** + * Debug function for development purposes + */ + private function debug($message, $level = OC_Log::DEBUG) + { + if ($this->debug) { + OC_Log::write('files_versioning', $message, $level); + } + } + + /** + * Parses a URL of the form: + * `versioned://path/to/git/repository/.git/path/to/file#SHA-1-commit-id` + * FIXME: Will throw an InvalidArgumentException if $path is invaid + * + * @param string $path The path to parse + * + * @return array An array containing an instance of Granite\Git\Repository, + * the file path, and an instance of Granite\Git\Commit + * @throws InvalidArgumentException If the repository cannot be loaded + */ + private function parse_url($path) + { + preg_match('/\/([A-Za-z0-9\/]+\.git\/)([A-Za-z0-9\/\.\/]*)(#([A-Fa-f0-9]+))*/', $path, $matches); + + // Load up the repo + $repo = new \Granite\Git\Repository($matches[1]); + // Parse the filename (stripping any trailing slashes) + $repo_file = $matches[2]; + if (substr($repo_file, -1) == '/') { + $repo_file = substr($repo_file, 0, -1); + } + + // Default to HEAD if no commit is provided + $repo_commit = isset($matches[4]) + ? $matches[4] + : $repo->head()->sha(); + + // Load the relevant commit + $commit = $repo->factory('commit', $repo_commit); + + return array($repo, $repo_file, $commit); + } + + /** + * Recursively searches a tree for a path, returning FALSE if is not found + * or an SHA-1 id if it is found. + * + * @param string $repo The repository containing the tree object + * @param string $tree The tree object to search + * @param string $path The path to search for (relative to the tree) + * @param int $depth The depth of the current search (for recursion) + * + * @return string|boolean The SHA-1 id of the sub-tree + */ + private function tree_search($repo, $tree, $path, $depth = 0) + { + $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); + + $current_path = $paths[$depth]; + + $nodes = $tree->nodes(); + foreach ($nodes as $node) { + if ($node->name() == $current_path) { + + if (count($paths)-1 == $depth) { + // Stop, found it + return $node->sha(); + } + + // Recurse if necessary + if ($node->isDirectory()) { + $tree = $this->repo->factory('tree', $node->sha()); + return $this->tree_search($repo, $tree, $path, $depth + 1); + } + } + } + + return false; + } + +} From 6cf3f10d3546eda09ceb823af5c2cf1190f18743 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Mon, 9 Apr 2012 22:51:53 +0200 Subject: [PATCH 210/302] fix includes --- apps/files_versioning/lib_granite.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/files_versioning/lib_granite.php b/apps/files_versioning/lib_granite.php index c69c62d9c4..62aebdd99d 100644 --- a/apps/files_versioning/lib_granite.php +++ b/apps/files_versioning/lib_granite.php @@ -1,12 +1,12 @@ Date: Tue, 10 Apr 2012 13:53:27 +0200 Subject: [PATCH 211/302] fix: drag'n'drop actions with files containing special chars, fixes oc-420 --- files/js/files.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/js/files.js b/files/js/files.js index df9f45a7af..1c0a40c236 100644 --- a/files/js/files.js +++ b/files/js/files.js @@ -417,7 +417,7 @@ var folderDropOptions={ var dir=$('#dir').val(); $.ajax({ url: 'ajax/move.php', - data: "dir="+dir+"&file="+file+'&target='+dir+'/'+target, + data: "dir="+encodeURIComponent(dir)+"&file="+encodeURIComponent(file)+'&target='+encodeURIComponent(dir)+'/'+encodeURIComponent(target), complete: function(data){boolOperationFinished(data, function(){ var el = $('#fileList tr').filterAttr('data-file',file).find('td.filename'); el.draggable('destroy'); @@ -443,7 +443,7 @@ var crumbDropOptions={ } $.ajax({ url: 'ajax/move.php', - data: "dir="+dir+"&file="+file+'&target='+target, + data: "dir="+encodeURIComponent(dir)+"&file="+encodeURIComponent(file)+'&target='+encodeURIComponent(target), complete: function(data){boolOperationFinished(data, function(){ FileList.remove(file); });} From 47f94548ce4c015782ebfe8566f009f5d4091fb3 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Tue, 10 Apr 2012 15:05:33 +0200 Subject: [PATCH 212/302] =?UTF-8?q?don=C2=B4t=20enable=20by=20default.=20s?= =?UTF-8?q?till=20broken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/files_versioning/appinfo/info.xml | 1 - apps/files_versioning/lib_granite.php | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/files_versioning/appinfo/info.xml b/apps/files_versioning/appinfo/info.xml index d5546be54a..4c67894f9f 100644 --- a/apps/files_versioning/appinfo/info.xml +++ b/apps/files_versioning/appinfo/info.xml @@ -7,7 +7,6 @@ Craig Roberts 3 Versions files using Git repositories, providing a simple backup facility. Currently in *beta* and explicitly without warranty of any kind. - diff --git a/apps/files_versioning/lib_granite.php b/apps/files_versioning/lib_granite.php index 62aebdd99d..571e5cea63 100644 --- a/apps/files_versioning/lib_granite.php +++ b/apps/files_versioning/lib_granite.php @@ -1,12 +1,12 @@ Date: Sat, 31 Mar 2012 00:03:21 +0200 Subject: [PATCH 213/302] Silence error for missing appinfo info file --- lib/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app.php b/lib/app.php index 7d5e8fa91c..1c81fbd424 100755 --- a/lib/app.php +++ b/lib/app.php @@ -319,7 +319,7 @@ class OC_App{ $file=OC::$APPSROOT.'/apps/'.$appid.'/appinfo/info.xml'; } $data=array(); - $content=file_get_contents($file); + $content=@file_get_contents($file); if(!$content){ return; } From fd72556fd16260088ec4a4340b6ea5d9e6a126a3 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Tue, 10 Apr 2012 21:15:38 +0200 Subject: [PATCH 214/302] Contacts: Make part.no_contacts.php translatable --- apps/contacts/templates/part.no_contacts.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/contacts/templates/part.no_contacts.php b/apps/contacts/templates/part.no_contacts.php index d24f7ae980..89b7575293 100644 --- a/apps/contacts/templates/part.no_contacts.php +++ b/apps/contacts/templates/part.no_contacts.php @@ -1,8 +1,8 @@
- You have no contacts in your list. + t('You have no contacts in your list.') ?>
- - - + + +
-
\ No newline at end of file + From 3f6e971571825dd66650348ef84b4e57d4cd15b8 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Tue, 10 Apr 2012 21:19:03 +0200 Subject: [PATCH 215/302] Contacts: Don't lowercase categories value when saving --- apps/contacts/ajax/saveproperty.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php index 4cef4d1e7a..99d55e7927 100644 --- a/apps/contacts/ajax/saveproperty.php +++ b/apps/contacts/ajax/saveproperty.php @@ -96,7 +96,7 @@ switch($element) { //$value = getOtherValue(); } break; - case 'CATEGORIES': + //case 'CATEGORIES': /* multi autocomplete triggers an save with empty value if (!$value) { $value = $vcard->getAsString('CATEGORIES'); From 2dfa02a34647010d26efe61598ff800c94b9c0d3 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Tue, 10 Apr 2012 21:53:36 +0200 Subject: [PATCH 216/302] Contacts: fix function names to coding standard --- apps/contacts/js/contacts.js | 10 +++++----- apps/contacts/templates/part.contact.php | 2 +- apps/contacts/templates/part.no_contacts.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index b2678e8c52..6f34a42a73 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -254,12 +254,12 @@ Contacts={ }); } }, - do_export:function() { + doExport:function() { document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + this.id; //$.get(OC.linkTo('contacts', 'export.php'),{'contactid':this.id},function(jsondata){ //}); }, - do_import:function(){ + doImport:function(){ Contacts.UI.notImplemented(); }, add:function(n, fn, aid, isnew){ // add a new contact @@ -323,7 +323,7 @@ Contacts={ } }); }, - do_delete:function() { + doDelete:function() { $('#contacts_deletecard').tipsy('hide'); OC.dialogs.confirm(t('contacts', 'Are you sure you want to delete this contact?'), t('contacts', 'Warning'), function(answer) { if(answer == true) { @@ -1264,7 +1264,7 @@ Contacts={ }); } }, - do_import:function(){ + doImport:function(){ Contacts.UI.notImplemented(); }, submit:function(button, bookid){ @@ -1364,7 +1364,7 @@ $(document).ready(function(){ }); $('#contacts_deletecard').live('click',function(){ - Contacts.UI.Card.do_delete(); + Contacts.UI.Card.doDelete(); }); $('#contacts li').bind('inview', function(event, isInView, visiblePartX, visiblePartY) { diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index 03d2fad853..ff1f081c4d 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -17,7 +17,7 @@ $id = isset($_['id']) ? $_['id'] : '';
  • t('Categories'); ?>
  • - + diff --git a/apps/contacts/templates/part.no_contacts.php b/apps/contacts/templates/part.no_contacts.php index 89b7575293..7024a142ae 100644 --- a/apps/contacts/templates/part.no_contacts.php +++ b/apps/contacts/templates/part.no_contacts.php @@ -1,7 +1,7 @@
    t('You have no contacts in your list.') ?>
    - +
    From 9f547a1b3987838fda8db238a8999c9f474bb542 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Tue, 10 Apr 2012 21:53:51 +0200 Subject: [PATCH 217/302] VCategories: fix function names to coding standard --- core/js/oc-vcategories.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/js/oc-vcategories.js b/core/js/oc-vcategories.js index 931ea37edb..e3b1abba08 100644 --- a/core/js/oc-vcategories.js +++ b/core/js/oc-vcategories.js @@ -19,7 +19,7 @@ OCCategories={ height: 350, minHeight:200, width: 250, minWidth: 200, buttons: { 'Delete':function() { - OCCategories.do_delete(); + OCCategories.doDelete(); }, 'Rescan':function() { OCCategories.rescan(); @@ -53,7 +53,7 @@ OCCategories={ } }); }, - do_delete:function(){ + doDelete:function(){ var categories = $('#categorylist').find('input[type="checkbox"]').serialize(); categories += '&app=' + OCCategories.app; console.log('OCCategories.delete: ' + categories); From 637db92e60bfcaa625ec2baf346a11de8981311b Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Wed, 11 Apr 2012 09:20:28 +0200 Subject: [PATCH 218/302] increase version to show that we are not the same as stable --- lib/util.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util.php b/lib/util.php index 722b7404d0..34f535d2d5 100644 --- a/lib/util.php +++ b/lib/util.php @@ -66,7 +66,7 @@ class OC_Util { * @return array */ public static function getVersion(){ - return array(3,00,4); + return array(3,80,0); } /** @@ -74,7 +74,7 @@ class OC_Util { * @return string */ public static function getVersionString(){ - return '3'; + return '4 alpha'; } /** From 8cea656ad741e471bd740170b38d6f789831a5df Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 2 Apr 2012 23:33:18 +0200 Subject: [PATCH 219/302] upgrade getid3 --- .../getid3/extension.cache.dbm.php | 45 +- .../getid3/extension.cache.mysql.php | 54 +- .../getID3 => 3rdparty}/getid3/getid3.lib.php | 558 ++-- .../getID3 => 3rdparty}/getid3/getid3.php | 1019 ++++--- .../getID3 => 3rdparty/getid3}/license.txt | 0 .../getid3/module.archive.gzip.php | 123 +- .../getid3/module.archive.rar.php | 19 +- .../getid3/module.archive.szip.php | 33 +- .../getid3/module.archive.tar.php | 59 +- .../getid3/module.archive.zip.php | 206 +- .../getid3/module.audio-video.asf.php | 946 +++++-- 3rdparty/getid3/module.audio-video.bink.php | 73 + 3rdparty/getid3/module.audio-video.flv.php | 731 +++++ .../getid3/module.audio-video.matroska.php | 1720 ++++++------ .../getid3/module.audio-video.mpeg.php | 131 +- 3rdparty/getid3/module.audio-video.nsv.php | 226 ++ .../getid3/module.audio-video.quicktime.php | 2134 +++++++++++++++ .../getid3/module.audio-video.real.php | 142 +- .../getid3/module.audio-video.riff.php | 1071 +++++--- .../getid3/module.audio-video.swf.php | 75 +- 3rdparty/getid3/module.audio.aa.php | 59 + 3rdparty/getid3/module.audio.aac.php | 515 ++++ 3rdparty/getid3/module.audio.ac3.php | 473 ++++ .../getid3/module.audio.au.php | 42 +- 3rdparty/getid3/module.audio.avr.php | 127 + .../getid3/module.audio.bonk.php | 135 +- .../getid3/module.audio.dss.php | 39 +- 3rdparty/getid3/module.audio.dts.php | 246 ++ 3rdparty/getid3/module.audio.flac.php | 480 ++++ 3rdparty/getid3/module.audio.la.php | 229 ++ 3rdparty/getid3/module.audio.lpac.php | 130 + .../getid3/module.audio.midi.php | 62 +- 3rdparty/getid3/module.audio.mod.php | 101 + .../getid3/module.audio.monkey.php | 73 +- .../getid3/module.audio.mp3.php | 697 +++-- .../getid3/module.audio.mpc.php | 169 +- 3rdparty/getid3/module.audio.ogg.php | 705 +++++ .../getid3/module.audio.optimfrog.php | 171 +- 3rdparty/getid3/module.audio.rkau.php | 94 + .../getid3/module.audio.shorten.php | 97 +- 3rdparty/getid3/module.audio.tta.php | 109 + .../getid3/module.audio.voc.php | 62 +- .../getid3/module.audio.vqf.php | 75 +- 3rdparty/getid3/module.audio.wavpack.php | 400 +++ .../getid3/module.graphic.bmp.php | 93 +- 3rdparty/getid3/module.graphic.efax.php | 53 + 3rdparty/getid3/module.graphic.gif.php | 184 ++ 3rdparty/getid3/module.graphic.jpg.php | 338 +++ .../getid3/module.graphic.pcd.php | 52 +- .../getid3/module.graphic.png.php | 43 +- 3rdparty/getid3/module.graphic.svg.php | 104 + .../getid3/module.graphic.tiff.php | 86 +- 3rdparty/getid3/module.misc.cue.php | 312 +++ 3rdparty/getid3/module.misc.exe.php | 61 + .../getid3/module.misc.iso.php | 125 +- .../getid3/module.misc.msoffice.php | 18 +- .../getid3/module.misc.par2.php | 9 +- .../getid3/module.misc.pdf.php | 9 +- .../getid3/module.tag.apetag.php | 166 +- .../getid3/module.tag.id3v1.php | 57 +- .../getid3/module.tag.id3v2.php | 631 +++-- .../getid3/module.tag.lyrics3.php | 125 +- 3rdparty/getid3/module.tag.xmp.php | 766 ++++++ .../getid3/write.apetag.php | 15 +- .../getid3/write.id3v1.php | 77 +- .../getid3/write.id3v2.php | 118 +- .../getid3/write.lyrics3.php | 9 +- 3rdparty/getid3/write.metaflac.php | 163 ++ .../getID3 => 3rdparty}/getid3/write.php | 85 +- 3rdparty/getid3/write.real.php | 275 ++ 3rdparty/getid3/write.vorbiscomment.php | 121 + apps/media/getID3/changelog.txt | 2435 ----------------- apps/media/getID3/dependencies.txt | 24 - .../getID3/getid3/module.audio-video.bink.php | 70 - .../getID3/getid3/module.audio-video.flv.php | 505 ---- .../getID3/getid3/module.audio-video.nsv.php | 224 -- .../getid3/module.audio-video.quicktime.php | 1382 ---------- apps/media/getID3/getid3/module.audio.aac.php | 542 ---- apps/media/getID3/getid3/module.audio.ac3.php | 497 ---- apps/media/getID3/getid3/module.audio.avr.php | 125 - apps/media/getID3/getid3/module.audio.dts.php | 239 -- .../media/getID3/getid3/module.audio.flac.php | 397 --- apps/media/getID3/getid3/module.audio.la.php | 228 -- .../media/getID3/getid3/module.audio.lpac.php | 126 - apps/media/getID3/getid3/module.audio.mod.php | 101 - apps/media/getID3/getid3/module.audio.ogg.php | 556 ---- .../media/getID3/getid3/module.audio.rkau.php | 92 - apps/media/getID3/getid3/module.audio.tta.php | 107 - .../getID3/getid3/module.audio.wavpack.php | 372 --- .../getID3/getid3/module.graphic.gif.php | 183 -- .../getID3/getid3/module.graphic.jpg.php | 249 -- .../getID3/getid3/module.graphic.svg.php | 52 - apps/media/getID3/getid3/module.misc.doc.php | 32 - apps/media/getID3/getid3/module.misc.exe.php | 59 - apps/media/getID3/getid3/write.metaflac.php | 167 -- apps/media/getID3/getid3/write.real.php | 295 -- .../getID3/getid3/write.vorbiscomment.php | 124 - apps/media/getID3/helperapps/readme.txt | 55 - apps/media/getID3/license.commercial.txt | 27 - apps/media/getID3/readme.txt | 549 ---- apps/media/getID3/structure.txt | 2251 --------------- apps/media/lib_scanner.php | 2 +- 102 files changed, 14650 insertions(+), 16167 deletions(-) rename {apps/media/getID3 => 3rdparty}/getid3/extension.cache.dbm.php (78%) rename {apps/media/getID3 => 3rdparty}/getid3/extension.cache.mysql.php (64%) rename {apps/media/getID3 => 3rdparty}/getid3/getid3.lib.php (70%) rename {apps/media/getID3 => 3rdparty}/getid3/getid3.php (52%) rename {apps/media/getID3 => 3rdparty/getid3}/license.txt (100%) rename {apps/media/getID3 => 3rdparty}/getid3/module.archive.gzip.php (62%) rename {apps/media/getID3 => 3rdparty}/getid3/module.archive.rar.php (62%) rename {apps/media/getID3 => 3rdparty}/getid3/module.archive.szip.php (75%) rename {apps/media/getID3 => 3rdparty}/getid3/module.archive.tar.php (71%) rename {apps/media/getID3 => 3rdparty}/getid3/module.archive.zip.php (66%) rename {apps/media/getID3 => 3rdparty}/getid3/module.audio-video.asf.php (69%) create mode 100644 3rdparty/getid3/module.audio-video.bink.php create mode 100644 3rdparty/getid3/module.audio-video.flv.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio-video.matroska.php (53%) rename {apps/media/getID3 => 3rdparty}/getid3/module.audio-video.mpeg.php (60%) create mode 100644 3rdparty/getid3/module.audio-video.nsv.php create mode 100644 3rdparty/getid3/module.audio-video.quicktime.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio-video.real.php (80%) rename {apps/media/getID3 => 3rdparty}/getid3/module.audio-video.riff.php (61%) rename {apps/media/getID3 => 3rdparty}/getid3/module.audio-video.swf.php (53%) create mode 100644 3rdparty/getid3/module.audio.aa.php create mode 100644 3rdparty/getid3/module.audio.aac.php create mode 100644 3rdparty/getid3/module.audio.ac3.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.au.php (72%) create mode 100644 3rdparty/getid3/module.audio.avr.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.bonk.php (53%) rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.dss.php (57%) create mode 100644 3rdparty/getid3/module.audio.dts.php create mode 100644 3rdparty/getid3/module.audio.flac.php create mode 100644 3rdparty/getid3/module.audio.la.php create mode 100644 3rdparty/getid3/module.audio.lpac.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.midi.php (87%) create mode 100644 3rdparty/getid3/module.audio.mod.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.monkey.php (78%) rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.mp3.php (72%) rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.mpc.php (72%) create mode 100644 3rdparty/getid3/module.audio.ogg.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.optimfrog.php (65%) create mode 100644 3rdparty/getid3/module.audio.rkau.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.shorten.php (54%) create mode 100644 3rdparty/getid3/module.audio.tta.php rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.voc.php (77%) rename {apps/media/getID3 => 3rdparty}/getid3/module.audio.vqf.php (55%) create mode 100644 3rdparty/getid3/module.audio.wavpack.php rename {apps/media/getID3 => 3rdparty}/getid3/module.graphic.bmp.php (89%) create mode 100644 3rdparty/getid3/module.graphic.efax.php create mode 100644 3rdparty/getid3/module.graphic.gif.php create mode 100644 3rdparty/getid3/module.graphic.jpg.php rename {apps/media/getID3 => 3rdparty}/getid3/module.graphic.pcd.php (67%) rename {apps/media/getID3 => 3rdparty}/getid3/module.graphic.png.php (94%) create mode 100644 3rdparty/getid3/module.graphic.svg.php rename {apps/media/getID3 => 3rdparty}/getid3/module.graphic.tiff.php (60%) create mode 100644 3rdparty/getid3/module.misc.cue.php create mode 100644 3rdparty/getid3/module.misc.exe.php rename {apps/media/getID3 => 3rdparty}/getid3/module.misc.iso.php (79%) rename {apps/media/getID3 => 3rdparty}/getid3/module.misc.msoffice.php (56%) rename {apps/media/getID3 => 3rdparty}/getid3/module.misc.par2.php (81%) rename {apps/media/getID3 => 3rdparty}/getid3/module.misc.pdf.php (79%) rename {apps/media/getID3 => 3rdparty}/getid3/module.tag.apetag.php (54%) rename {apps/media/getID3 => 3rdparty}/getid3/module.tag.id3v1.php (86%) rename {apps/media/getID3 => 3rdparty}/getid3/module.tag.id3v2.php (79%) rename {apps/media/getID3 => 3rdparty}/getid3/module.tag.lyrics3.php (61%) create mode 100644 3rdparty/getid3/module.tag.xmp.php rename {apps/media/getID3 => 3rdparty}/getid3/write.apetag.php (92%) rename {apps/media/getID3 => 3rdparty}/getid3/write.id3v1.php (53%) rename {apps/media/getID3 => 3rdparty}/getid3/write.id3v2.php (95%) rename {apps/media/getID3 => 3rdparty}/getid3/write.lyrics3.php (91%) create mode 100644 3rdparty/getid3/write.metaflac.php rename {apps/media/getID3 => 3rdparty}/getid3/write.php (79%) create mode 100644 3rdparty/getid3/write.real.php create mode 100644 3rdparty/getid3/write.vorbiscomment.php delete mode 100644 apps/media/getID3/changelog.txt delete mode 100644 apps/media/getID3/dependencies.txt delete mode 100644 apps/media/getID3/getid3/module.audio-video.bink.php delete mode 100644 apps/media/getID3/getid3/module.audio-video.flv.php delete mode 100644 apps/media/getID3/getid3/module.audio-video.nsv.php delete mode 100644 apps/media/getID3/getid3/module.audio-video.quicktime.php delete mode 100644 apps/media/getID3/getid3/module.audio.aac.php delete mode 100644 apps/media/getID3/getid3/module.audio.ac3.php delete mode 100644 apps/media/getID3/getid3/module.audio.avr.php delete mode 100644 apps/media/getID3/getid3/module.audio.dts.php delete mode 100644 apps/media/getID3/getid3/module.audio.flac.php delete mode 100644 apps/media/getID3/getid3/module.audio.la.php delete mode 100644 apps/media/getID3/getid3/module.audio.lpac.php delete mode 100644 apps/media/getID3/getid3/module.audio.mod.php delete mode 100644 apps/media/getID3/getid3/module.audio.ogg.php delete mode 100644 apps/media/getID3/getid3/module.audio.rkau.php delete mode 100644 apps/media/getID3/getid3/module.audio.tta.php delete mode 100644 apps/media/getID3/getid3/module.audio.wavpack.php delete mode 100644 apps/media/getID3/getid3/module.graphic.gif.php delete mode 100644 apps/media/getID3/getid3/module.graphic.jpg.php delete mode 100644 apps/media/getID3/getid3/module.graphic.svg.php delete mode 100644 apps/media/getID3/getid3/module.misc.doc.php delete mode 100644 apps/media/getID3/getid3/module.misc.exe.php delete mode 100644 apps/media/getID3/getid3/write.metaflac.php delete mode 100644 apps/media/getID3/getid3/write.real.php delete mode 100644 apps/media/getID3/getid3/write.vorbiscomment.php delete mode 100644 apps/media/getID3/helperapps/readme.txt delete mode 100644 apps/media/getID3/license.commercial.txt delete mode 100644 apps/media/getID3/readme.txt delete mode 100644 apps/media/getID3/structure.txt diff --git a/apps/media/getID3/getid3/extension.cache.dbm.php b/3rdparty/getid3/extension.cache.dbm.php similarity index 78% rename from apps/media/getID3/getid3/extension.cache.dbm.php rename to 3rdparty/getid3/extension.cache.dbm.php index c18b52d5dc..9a88c22b24 100644 --- a/apps/media/getID3/getid3/extension.cache.dbm.php +++ b/3rdparty/getid3/extension.cache.dbm.php @@ -77,35 +77,24 @@ class getID3_cached_dbm extends getID3 // Check for dba extension if (!extension_loaded('dba')) { - die('PHP is not compiled with dba support, required to use DBM style cache.'); + throw new Exception('PHP is not compiled with dba support, required to use DBM style cache.'); } // Check for specific dba driver - if (function_exists('dba_handlers')) { // PHP 4.3.0+ - if (!in_array('db3', dba_handlers())) { - die('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.'); - } - } - else { // PHP <= 4.2.3 - ob_start(); // nasty, buy the only way to check... - phpinfo(); - $contents = ob_get_contents(); - @ob_end_clean(); - if (!strstr($contents, $cache_type)) { - die('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.'); - } + if (!function_exists('dba_handlers') || !in_array($cache_type, dba_handlers())) { + throw new Exception('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.'); } // Create lock file if needed if (!file_exists($lock_filename)) { if (!touch($lock_filename)) { - die('failed to create lock file: ' . $lock_filename); + throw new Exception('failed to create lock file: '.$lock_filename); } } // Open lock file for writing if (!is_writeable($lock_filename)) { - die('lock file: ' . $lock_filename . ' is not writable'); + throw new Exception('lock file: '.$lock_filename.' is not writable'); } $this->lock = fopen($lock_filename, 'w'); @@ -115,23 +104,23 @@ class getID3_cached_dbm extends getID3 // Create dbm-file if needed if (!file_exists($dbm_filename)) { if (!touch($dbm_filename)) { - die('failed to create dbm file: ' . $dbm_filename); + throw new Exception('failed to create dbm file: '.$dbm_filename); } } // Try to open dbm file for writing - $this->dba = @dba_open($dbm_filename, 'w', $cache_type); + $this->dba = dba_open($dbm_filename, 'w', $cache_type); if (!$this->dba) { // Failed - create new dbm file $this->dba = dba_open($dbm_filename, 'n', $cache_type); if (!$this->dba) { - die('failed to create dbm file: ' . $dbm_filename); + throw new Exception('failed to create dbm file: '.$dbm_filename); } // Insert getID3 version number - dba_insert(GETID3_VERSION, GETID3_VERSION, $this->dba); + dba_insert(getID3::VERSION, getID3::VERSION, $this->dba); } // Init misc values @@ -142,7 +131,7 @@ class getID3_cached_dbm extends getID3 register_shutdown_function(array($this, '__destruct')); // Check version number and clear cache if changed - if (dba_fetch(GETID3_VERSION, $this->dba) != GETID3_VERSION) { + if (dba_fetch(getID3::VERSION, $this->dba) != getID3::VERSION) { $this->clear_cache(); } @@ -155,13 +144,13 @@ class getID3_cached_dbm extends getID3 function __destruct() { // Close dbm file - @dba_close($this->dba); + dba_close($this->dba); // Release exclusive lock - @flock($this->lock, LOCK_UN); + flock($this->lock, LOCK_UN); // Close lock file - @fclose($this->lock); + fclose($this->lock); } @@ -176,13 +165,13 @@ class getID3_cached_dbm extends getID3 $this->dba = dba_open($this->dbm_filename, 'n', $this->cache_type); if (!$this->dba) { - die('failed to clear cache/recreate dbm file: ' . $this->dbm_filename); + throw new Exception('failed to clear cache/recreate dbm file: '.$this->dbm_filename); } // Insert getID3 version number - dba_insert(GETID3_VERSION, GETID3_VERSION, $this->dba); + dba_insert(getID3::VERSION, getID3::VERSION, $this->dba); - // Reregister shutdown function + // Re-register shutdown function register_shutdown_function(array($this, '__destruct')); } @@ -194,7 +183,7 @@ class getID3_cached_dbm extends getID3 if (file_exists($filename)) { // Calc key filename::mod_time::size - should be unique - $key = $filename . '::' . filemtime($filename) . '::' . filesize($filename); + $key = $filename.'::'.filemtime($filename).'::'.filesize($filename); // Loopup key $result = dba_fetch($key, $this->dba); diff --git a/apps/media/getID3/getid3/extension.cache.mysql.php b/3rdparty/getid3/extension.cache.mysql.php similarity index 64% rename from apps/media/getID3/getid3/extension.cache.mysql.php rename to 3rdparty/getid3/extension.cache.mysql.php index 40ea6883ea..1e1f91fa14 100644 --- a/apps/media/getID3/getid3/extension.cache.mysql.php +++ b/3rdparty/getid3/extension.cache.mysql.php @@ -11,6 +11,7 @@ ///////////////////////////////////////////////////////////////// // // // This extension written by Allan Hansen // +// Table name mod by Carlo Capocasa // // /// ///////////////////////////////////////////////////////////////// @@ -33,8 +34,8 @@ * * require_once 'getid3/getid3.php'; * require_once 'getid3/getid3/extension.cache.mysql.php'; -* $getID3 = new getID3_cached_mysql('localhost', 'database', -* 'username', 'password'); +* // 5th parameter (tablename) is optional, default is 'getid3_cache' +* $getID3 = new getID3_cached_mysql('localhost', 'database', 'username', 'password', 'tablename'); * $getID3->encoding = 'UTF-8'; * $info1 = $getID3->analyze('file1.flac'); * $info2 = $getID3->analyze('file2.wv'); @@ -78,31 +79,36 @@ class getID3_cached_mysql extends getID3 // public: constructor - see top of this file for cache type and cache_options - function getID3_cached_mysql($host, $database, $username, $password) { + function getID3_cached_mysql($host, $database, $username, $password, $table='getid3_cache') { // Check for mysql support if (!function_exists('mysql_pconnect')) { - die('PHP not compiled with mysql support.'); + throw new Exception('PHP not compiled with mysql support.'); } // Connect to database $this->connection = mysql_pconnect($host, $username, $password); if (!$this->connection) { - die('mysql_pconnect() failed - check permissions and spelling.'); + throw new Exception('mysql_pconnect() failed - check permissions and spelling.'); } // Select database if (!mysql_select_db($database, $this->connection)) { - die('Cannot use database '.$database); + throw new Exception('Cannot use database '.$database); } + // Set table + $this->table = $table; + // Create cache table if not exists $this->create_table(); // Check version number and clear cache if changed - $this->cursor = mysql_query("SELECT `value` FROM `getid3_cache` WHERE (`filename` = '".GETID3_VERSION."') AND (`filesize` = '-1') AND (`filetime` = '-1') AND (`analyzetime` = '-1')", $this->connection); - list($version) = @mysql_fetch_array($this->cursor); - if ($version != GETID3_VERSION) { + $version = ''; + if ($this->cursor = mysql_query("SELECT `value` FROM `".mysql_real_escape_string($this->table)."` WHERE (`filename` = '".mysql_real_escape_string(getID3::VERSION)."') AND (`filesize` = '-1') AND (`filetime` = '-1') AND (`analyzetime` = '-1')", $this->connection)) { + list($version) = mysql_fetch_array($this->cursor); + } + if ($version != getID3::VERSION) { $this->clear_cache(); } @@ -114,8 +120,8 @@ class getID3_cached_mysql extends getID3 // public: clear cache function clear_cache() { - $this->cursor = mysql_query("DELETE FROM `getid3_cache`", $this->connection); - $this->cursor = mysql_query("INSERT INTO `getid3_cache` VALUES ('".GETID3_VERSION."', -1, -1, -1, '".GETID3_VERSION."')", $this->connection); + $this->cursor = mysql_query("DELETE FROM `".mysql_real_escape_string($this->table)."`", $this->connection); + $this->cursor = mysql_query("INSERT INTO `".mysql_real_escape_string($this->table)."` VALUES ('".getID3::VERSION."', -1, -1, -1, '".getID3::VERSION."')", $this->connection); } @@ -128,35 +134,32 @@ class getID3_cached_mysql extends getID3 // Short-hands $filetime = filemtime($filename); $filesize = filesize($filename); - $filenam2 = mysql_escape_string($filename); - // Loopup file - $this->cursor = mysql_query("SELECT `value` FROM `getid3_cache` WHERE (`filename`='".$filenam2."') AND (`filesize`='".$filesize."') AND (`filetime`='".$filetime."')", $this->connection); - list($result) = @mysql_fetch_array($this->cursor); - - // Hit - if ($result) { - return unserialize($result); + // Lookup file + $this->cursor = mysql_query("SELECT `value` FROM `".mysql_real_escape_string($this->table)."` WHERE (`filename` = '".mysql_real_escape_string($filename)."') AND (`filesize` = '".mysql_real_escape_string($filesize)."') AND (`filetime` = '".mysql_real_escape_string($filetime)."')", $this->connection); + if (mysql_num_rows($this->cursor) > 0) { + // Hit + list($result) = mysql_fetch_array($this->cursor); + return unserialize(base64_decode($result)); } } // Miss - $result = parent::analyze($filename); + $analysis = parent::analyze($filename); // Save result if (file_exists($filename)) { - $res2 = mysql_escape_string(serialize($result)); - $this->cursor = mysql_query("INSERT INTO `getid3_cache` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES ('".$filenam2."', '".$filesize."', '".$filetime."', '".time()."', '".$res2."')", $this->connection); + $this->cursor = mysql_query("INSERT INTO `".mysql_real_escape_string($this->table)."` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES ('".mysql_real_escape_string($filename)."', '".mysql_real_escape_string($filesize)."', '".mysql_real_escape_string($filetime)."', '".mysql_real_escape_string(time())."', '".mysql_real_escape_string(base64_encode(serialize($analysis)))."')", $this->connection); } - return $result; + return $analysis; } // private: (re)create sql table - function create_table($drop = false) { + function create_table($drop=false) { - $this->cursor = mysql_query("CREATE TABLE IF NOT EXISTS `getid3_cache` ( + $this->cursor = mysql_query("CREATE TABLE IF NOT EXISTS `".mysql_real_escape_string($this->table)."` ( `filename` VARCHAR(255) NOT NULL DEFAULT '', `filesize` INT(11) NOT NULL DEFAULT '0', `filetime` INT(11) NOT NULL DEFAULT '0', @@ -167,5 +170,4 @@ class getID3_cached_mysql extends getID3 } } - ?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/getid3.lib.php b/3rdparty/getid3/getid3.lib.php similarity index 70% rename from apps/media/getID3/getid3/getid3.lib.php rename to 3rdparty/getid3/getid3.lib.php index 9322cae4dd..723e2e24c2 100644 --- a/apps/media/getID3/getid3/getid3.lib.php +++ b/3rdparty/getid3/getid3.lib.php @@ -14,33 +14,28 @@ class getid3_lib { - function PrintHexBytes($string, $hex=true, $spaces=true, $htmlsafe=true) { + static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') { $returnstring = ''; for ($i = 0; $i < strlen($string); $i++) { if ($hex) { $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT); } else { - $returnstring .= ' '.(ereg("[\x20-\x7E]", $string{$i}) ? $string{$i} : '¤'); + $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '¤'); } if ($spaces) { $returnstring .= ' '; } } - if ($htmlsafe) { - $returnstring = htmlentities($returnstring); + if (!empty($htmlencoding)) { + if ($htmlencoding === true) { + $htmlencoding = 'UTF-8'; // prior to getID3 v1.9.0 the function's 4th parameter was boolean + } + $returnstring = htmlentities($returnstring, ENT_QUOTES, $htmlencoding); } return $returnstring; } - function SafeStripSlashes($text) { - if (get_magic_quotes_gpc()) { - return stripslashes($text); - } - return $text; - } - - - function trunc($floatnumber) { + static function trunc($floatnumber) { // truncates a floating-point number at the decimal point // returns int (if possible, otherwise float) if ($floatnumber >= 1) { @@ -50,21 +45,30 @@ class getid3_lib } else { $truncatednumber = 0; } - if ($truncatednumber <= 1073741824) { // 2^30 + if (getid3_lib::intValueSupported($truncatednumber)) { $truncatednumber = (int) $truncatednumber; } return $truncatednumber; } - function CastAsInt($floatnum) { + static function safe_inc(&$variable, $increment=1) { + if (isset($variable)) { + $variable += $increment; + } else { + $variable = $increment; + } + return true; + } + + static function CastAsInt($floatnum) { // convert to float if not already $floatnum = (float) $floatnum; // convert a float to type int, only if possible if (getid3_lib::trunc($floatnum) == $floatnum) { // it's not floating point - if ($floatnum <= 2147483647) { // 2^31 + if (getid3_lib::intValueSupported($floatnum)) { // it's within int range $floatnum = (int) $floatnum; } @@ -72,15 +76,36 @@ class getid3_lib return $floatnum; } + public static function intValueSupported($num) { + // check if integers are 64-bit + static $hasINT64 = null; + if ($hasINT64 === null) { // 10x faster than is_null() + $hasINT64 = is_int(pow(2, 31)); // 32-bit int are limited to (2^31)-1 + if (!$hasINT64 && !defined('PHP_INT_MIN')) { + define('PHP_INT_MIN', ~PHP_INT_MAX); + } + } + // if integers are 64-bit - no other check required + if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) { + return true; + } + return false; + } - function DecimalBinary2Float($binarynumerator) { + static function DecimalizeFraction($fraction) { + list($numerator, $denominator) = explode('/', $fraction); + return $numerator / ($denominator ? $denominator : 1); + } + + + static function DecimalBinary2Float($binarynumerator) { $numerator = getid3_lib::Bin2Dec($binarynumerator); $denominator = getid3_lib::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); return ($numerator / $denominator); } - function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { + static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html if (strpos($binarypointnumber, '.') === false) { $binarypointnumber = '0.'.$binarypointnumber; @@ -104,7 +129,7 @@ class getid3_lib } - function Float2BinaryDecimal($floatvalue) { + static function Float2BinaryDecimal($floatvalue) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html $maxbits = 128; // to how many bits of precision should the calculations be taken? $intpart = getid3_lib::trunc($floatvalue); @@ -120,7 +145,7 @@ class getid3_lib } - function Float2String($floatvalue, $bits) { + static function Float2String($floatvalue, $bits) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html switch ($bits) { case 32: @@ -151,20 +176,20 @@ class getid3_lib } - function LittleEndian2Float($byteword) { + static function LittleEndian2Float($byteword) { return getid3_lib::BigEndian2Float(strrev($byteword)); } - function BigEndian2Float($byteword) { + static function BigEndian2Float($byteword) { // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic // http://www.psc.edu/general/software/packages/ieee/ieee.html // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html $bitword = getid3_lib::BigEndian2Bin($byteword); if (!$bitword) { - return 0; - } + return 0; + } $signbit = $bitword{0}; switch (strlen($byteword) * 8) { @@ -234,44 +259,42 @@ class getid3_lib } - function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { + static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { $intvalue = 0; $bytewordlen = strlen($byteword); + if ($bytewordlen == 0) { + return false; + } for ($i = 0; $i < $bytewordlen; $i++) { if ($synchsafe) { // disregard MSB, effectively 7-bit bytes - $intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); + //$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems + $intvalue += (ord($byteword{$i}) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7); } else { $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i)); } } if ($signed && !$synchsafe) { // synchsafe ints are not allowed to be signed - switch ($bytewordlen) { - case 1: - case 2: - case 3: - case 4: - $signmaskbit = 0x80 << (8 * ($bytewordlen - 1)); - if ($intvalue & $signmaskbit) { - $intvalue = 0 - ($intvalue & ($signmaskbit - 1)); - } - break; - - default: - die('ERROR: Cannot have signed integers larger than 32-bits in getid3_lib::BigEndian2Int()'); - break; + if ($bytewordlen <= PHP_INT_SIZE) { + $signMaskBit = 0x80 << (8 * ($bytewordlen - 1)); + if ($intvalue & $signMaskBit) { + $intvalue = 0 - ($intvalue & ($signMaskBit - 1)); + } + } else { + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in getid3_lib::BigEndian2Int()'); + break; } } return getid3_lib::CastAsInt($intvalue); } - function LittleEndian2Int($byteword, $signed=false) { + static function LittleEndian2Int($byteword, $signed=false) { return getid3_lib::BigEndian2Int(strrev($byteword), false, $signed); } - function BigEndian2Bin($byteword) { + static function BigEndian2Bin($byteword) { $binvalue = ''; $bytewordlen = strlen($byteword); for ($i = 0; $i < $bytewordlen; $i++) { @@ -281,15 +304,15 @@ class getid3_lib } - function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { + static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { if ($number < 0) { - return false; + throw new Exception('ERROR: getid3_lib::BigEndian2String() does not support negative numbers'); } $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); $intstring = ''; if ($signed) { - if ($minbytes > 4) { - die('ERROR: Cannot have signed integers larger than 32-bits in getid3_lib::BigEndian2String()'); + if ($minbytes > PHP_INT_SIZE) { + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in getid3_lib::BigEndian2String()'); } $number = $number & (0x80 << (8 * ($minbytes - 1))); } @@ -302,7 +325,7 @@ class getid3_lib } - function Dec2Bin($number) { + static function Dec2Bin($number) { while ($number >= 256) { $bytes[] = (($number / 256) - (floor($number / 256))) * 256; $number = floor($number / 256); @@ -316,7 +339,7 @@ class getid3_lib } - function Bin2Dec($binstring, $signed=false) { + static function Bin2Dec($binstring, $signed=false) { $signmult = 1; if ($signed) { if ($binstring{0} == '1') { @@ -332,7 +355,7 @@ class getid3_lib } - function Bin2String($binstring) { + static function Bin2String($binstring) { // return 'hi' for input of '0110100001101001' $string = ''; $binstringreversed = strrev($binstring); @@ -343,7 +366,7 @@ class getid3_lib } - function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { + static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { $intstring = ''; while ($number > 0) { if ($synchsafe) { @@ -358,7 +381,7 @@ class getid3_lib } - function array_merge_clobber($array1, $array2) { + static function array_merge_clobber($array1, $array2) { // written by kcØhireability*com // taken from http://www.php.net/manual/en/function.array-merge-recursive.php if (!is_array($array1) || !is_array($array2)) { @@ -376,7 +399,7 @@ class getid3_lib } - function array_merge_noclobber($array1, $array2) { + static function array_merge_noclobber($array1, $array2) { if (!is_array($array1) || !is_array($array2)) { return false; } @@ -392,7 +415,17 @@ class getid3_lib } - function fileextension($filename, $numextensions=1) { + static function ksort_recursive(&$theArray) { + ksort($theArray); + foreach ($theArray as $key => $value) { + if (is_array($value)) { + self::ksort_recursive($theArray[$key]); + } + } + return true; + } + + static function fileextension($filename, $numextensions=1) { if (strstr($filename, '.')) { $reversedfilename = strrev($filename); $offset = 0; @@ -408,66 +441,40 @@ class getid3_lib } - function PlaytimeString($playtimeseconds) { - $sign = (($playtimeseconds < 0) ? '-' : ''); - $playtimeseconds = abs($playtimeseconds); - $contentseconds = round((($playtimeseconds / 60) - floor($playtimeseconds / 60)) * 60); - $contentminutes = floor($playtimeseconds / 60); - if ($contentseconds >= 60) { - $contentseconds -= 60; - $contentminutes++; - } - return $sign.intval($contentminutes).':'.str_pad($contentseconds, 2, 0, STR_PAD_LEFT); + static function PlaytimeString($seconds) { + $sign = (($seconds < 0) ? '-' : ''); + $seconds = abs($seconds); + $H = floor( $seconds / 3600); + $M = floor(($seconds - (3600 * $H) ) / 60); + $S = round( $seconds - (3600 * $H) - (60 * $M) ); + return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT); } - function image_type_to_mime_type($imagetypeid) { - // only available in PHP v4.3.0+ - static $image_type_to_mime_type = array(); - if (empty($image_type_to_mime_type)) { - $image_type_to_mime_type[1] = 'image/gif'; // GIF - $image_type_to_mime_type[2] = 'image/jpeg'; // JPEG - $image_type_to_mime_type[3] = 'image/png'; // PNG - $image_type_to_mime_type[4] = 'application/x-shockwave-flash'; // Flash - $image_type_to_mime_type[5] = 'image/psd'; // PSD - $image_type_to_mime_type[6] = 'image/bmp'; // BMP - $image_type_to_mime_type[7] = 'image/tiff'; // TIFF: little-endian (Intel) - $image_type_to_mime_type[8] = 'image/tiff'; // TIFF: big-endian (Motorola) - //$image_type_to_mime_type[9] = 'image/jpc'; // JPC - //$image_type_to_mime_type[10] = 'image/jp2'; // JPC - //$image_type_to_mime_type[11] = 'image/jpx'; // JPC - //$image_type_to_mime_type[12] = 'image/jb2'; // JPC - $image_type_to_mime_type[13] = 'application/x-shockwave-flash'; // Shockwave - $image_type_to_mime_type[14] = 'image/iff'; // IFF - } - return (isset($image_type_to_mime_type[$imagetypeid]) ? $image_type_to_mime_type[$imagetypeid] : 'application/octet-stream'); - } - - - function DateMac2Unix($macdate) { + static function DateMac2Unix($macdate) { // Macintosh timestamp: seconds since 00:00h January 1, 1904 // UNIX timestamp: seconds since 00:00h January 1, 1970 return getid3_lib::CastAsInt($macdate - 2082844800); } - function FixedPoint8_8($rawdata) { + static function FixedPoint8_8($rawdata) { return getid3_lib::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); } - function FixedPoint16_16($rawdata) { + static function FixedPoint16_16($rawdata) { return getid3_lib::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); } - function FixedPoint2_30($rawdata) { + static function FixedPoint2_30($rawdata) { $binarystring = getid3_lib::BigEndian2Bin($rawdata); - return getid3_lib::Bin2Dec(substr($binarystring, 0, 2)) + (float) (getid3_lib::Bin2Dec(substr($binarystring, 2, 30)) / 1073741824); + return getid3_lib::Bin2Dec(substr($binarystring, 0, 2)) + (float) (getid3_lib::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); } - function CreateDeepArray($ArrayPath, $Separator, $Value) { + static function CreateDeepArray($ArrayPath, $Separator, $Value) { // assigns $Value to a nested array path: // $foo = getid3_lib::CreateDeepArray('/path/to/my', '/', 'file.txt') // is the same as: @@ -485,7 +492,7 @@ class getid3_lib return $ReturnedArray; } - function array_max($arraydata, $returnkey=false) { + static function array_max($arraydata, $returnkey=false) { $maxvalue = false; $maxkey = false; foreach ($arraydata as $key => $value) { @@ -499,7 +506,7 @@ class getid3_lib return ($returnkey ? $maxkey : $maxvalue); } - function array_min($arraydata, $returnkey=false) { + static function array_min($arraydata, $returnkey=false) { $minvalue = false; $minkey = false; foreach ($arraydata as $key => $value) { @@ -513,82 +520,35 @@ class getid3_lib return ($returnkey ? $minkey : $minvalue); } - - function md5_file($file) { - - // md5_file() exists in PHP 4.2.0+. - if (function_exists('md5_file')) { - return md5_file($file); - } - - if (GETID3_OS_ISWINDOWS) { - - $RequiredFiles = array('cygwin1.dll', 'md5sum.exe'); - foreach ($RequiredFiles as $required_file) { - if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { - die(implode(' and ', $RequiredFiles).' are required in '.GETID3_HELPERAPPSDIR.' for getid3_lib::md5_file() to function under Windows in PHP < v4.2.0'); - } + static function XML2array($XMLstring) { + if (function_exists('simplexml_load_string')) { + if (function_exists('get_object_vars')) { + $XMLobject = simplexml_load_string($XMLstring); + return self::SimpleXMLelement2array($XMLobject); } - $commandline = GETID3_HELPERAPPSDIR.'md5sum.exe "'.str_replace('/', DIRECTORY_SEPARATOR, $file).'"'; - if (ereg("^[\\]?([0-9a-f]{32})", strtolower(`$commandline`), $r)) { - return $r[1]; - } - - } else { - - // The following works under UNIX only - $file = str_replace('`', '\\`', $file); - if (ereg("^([0-9a-f]{32})[ \t\n\r]", `md5sum "$file"`, $r)) { - return $r[1]; - } - } return false; } - - function sha1_file($file) { - - // sha1_file() exists in PHP 4.3.0+. - if (function_exists('sha1_file')) { - return sha1_file($file); + static function SimpleXMLelement2array($XMLobject) { + if (!is_object($XMLobject) && !is_array($XMLobject)) { + return $XMLobject; } - - $file = str_replace('`', '\\`', $file); - - if (GETID3_OS_ISWINDOWS) { - - $RequiredFiles = array('cygwin1.dll', 'sha1sum.exe'); - foreach ($RequiredFiles as $required_file) { - if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { - die(implode(' and ', $RequiredFiles).' are required in '.GETID3_HELPERAPPSDIR.' for getid3_lib::sha1_file() to function under Windows in PHP < v4.3.0'); - } - } - $commandline = GETID3_HELPERAPPSDIR.'sha1sum.exe "'.str_replace('/', DIRECTORY_SEPARATOR, $file).'"'; - if (ereg("^sha1=([0-9a-f]{40})", strtolower(`$commandline`), $r)) { - return $r[1]; - } - - } else { - - $commandline = 'sha1sum '.escapeshellarg($file).''; - if (ereg("^([0-9a-f]{40})[ \t\n\r]", strtolower(`$commandline`), $r)) { - return $r[1]; - } - + $XMLarray = (is_object($XMLobject) ? get_object_vars($XMLobject) : $XMLobject); + foreach ($XMLarray as $key => $value) { + $XMLarray[$key] = self::SimpleXMLelement2array($value); } - - return false; + return $XMLarray; } // Allan Hansen // getid3_lib::md5_data() - returns md5sum for a file from startuing position to absolute end position - function hash_data($file, $offset, $end, $algorithm) { - if ($end >= pow(2, 31)) { + static function hash_data($file, $offset, $end, $algorithm) { + static $tempdir = ''; + if (!getid3_lib::intValueSupported($end)) { return false; } - switch ($algorithm) { case 'md5': $hash_function = 'md5_file'; @@ -605,7 +565,7 @@ class getid3_lib break; default: - die('Invalid algorithm ('.$algorithm.') in getid3_lib::hash_data()'); + throw new Exception('Invalid algorithm ('.$algorithm.') in getid3_lib::hash_data()'); break; } $size = $end - $offset; @@ -636,16 +596,23 @@ class getid3_lib $commandline .= $unix_call; } - if ((bool) ini_get('safe_mode')) { - $ThisFileInfo['warning'][] = 'PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'; + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + //throw new Exception('PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'); break; } return substr(`$commandline`, 0, $hash_length); } + if (empty($tempdir)) { + // yes this is ugly, feel free to suggest a better way + require_once(dirname(__FILE__).'/getid3.php'); + $getid3_temp = new getID3(); + $tempdir = $getid3_temp->tempdir; + unset($getid3_temp); + } // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir - if (($data_filename = tempnam('*', 'getID3')) === false) { - // can't find anywhere to create a temp file, just die + if (($data_filename = tempnam($tempdir, 'gI3')) === false) { + // can't find anywhere to create a temp file, just fail return false; } @@ -653,52 +620,68 @@ class getid3_lib $result = false; // copy parts of file - if ($fp = @fopen($file, 'rb')) { - - if ($fp_data = @fopen($data_filename, 'wb')) { - - fseek($fp, $offset, SEEK_SET); - $byteslefttowrite = $end - $offset; - while (($byteslefttowrite > 0) && ($buffer = fread($fp, GETID3_FREAD_BUFFER_SIZE))) { - $byteswritten = fwrite($fp_data, $buffer, $byteslefttowrite); - $byteslefttowrite -= $byteswritten; - } - fclose($fp_data); - $result = getid3_lib::$hash_function($data_filename); - - } - fclose($fp); + try { + getid3_lib::CopyFileParts($file, $data_filename, $offset, $end - $offset); + $result = $hash_function($data_filename); + } catch (Exception $e) { + throw new Exception('getid3_lib::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage()); } unlink($data_filename); return $result; } + static function CopyFileParts($filename_source, $filename_dest, $offset, $length) { + if (!getid3_lib::intValueSupported($offset + $length)) { + throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); + } + if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) { + if (($fp_dest = fopen($filename_dest, 'wb'))) { + if (fseek($fp_src, $offset, SEEK_SET) == 0) { + $byteslefttowrite = $length; + while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) { + $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite); + $byteslefttowrite -= $byteswritten; + } + return true; + } else { + throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source); + } + fclose($fp_dest); + } else { + throw new Exception('failed to create file for writing '.$filename_dest); + } + fclose($fp_src); + } else { + throw new Exception('failed to open file for reading '.$filename_source); + } + return false; + } - function iconv_fallback_int_utf8($charval) { + static function iconv_fallback_int_utf8($charval) { if ($charval < 128) { // 0bbbbbbb $newcharstring = chr($charval); } elseif ($charval < 2048) { // 110bbbbb 10bbbbbb - $newcharstring = chr(($charval >> 6) | 0xC0); + $newcharstring = chr(($charval >> 6) | 0xC0); $newcharstring .= chr(($charval & 0x3F) | 0x80); } elseif ($charval < 65536) { // 1110bbbb 10bbbbbb 10bbbbbb - $newcharstring = chr(($charval >> 12) | 0xE0); - $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring = chr(($charval >> 12) | 0xE0); + $newcharstring .= chr(($charval >> 6) | 0xC0); $newcharstring .= chr(($charval & 0x3F) | 0x80); } else { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb - $newcharstring = chr(($charval >> 18) | 0xF0); - $newcharstring .= chr(($charval >> 12) | 0xC0); - $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring = chr(($charval >> 18) | 0xF0); + $newcharstring .= chr(($charval >> 12) | 0xC0); + $newcharstring .= chr(($charval >> 6) | 0xC0); $newcharstring .= chr(($charval & 0x3F) | 0x80); } return $newcharstring; } // ISO-8859-1 => UTF-8 - function iconv_fallback_iso88591_utf8($string, $bom=false) { + static function iconv_fallback_iso88591_utf8($string, $bom=false) { if (function_exists('utf8_encode')) { return utf8_encode($string); } @@ -715,7 +698,7 @@ class getid3_lib } // ISO-8859-1 => UTF-16BE - function iconv_fallback_iso88591_utf16be($string, $bom=false) { + static function iconv_fallback_iso88591_utf16be($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFE\xFF"; @@ -727,7 +710,7 @@ class getid3_lib } // ISO-8859-1 => UTF-16LE - function iconv_fallback_iso88591_utf16le($string, $bom=false) { + static function iconv_fallback_iso88591_utf16le($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFF\xFE"; @@ -739,12 +722,12 @@ class getid3_lib } // ISO-8859-1 => UTF-16LE (BOM) - function iconv_fallback_iso88591_utf16($string) { + static function iconv_fallback_iso88591_utf16($string) { return getid3_lib::iconv_fallback_iso88591_utf16le($string, true); } // UTF-8 => ISO-8859-1 - function iconv_fallback_utf8_iso88591($string) { + static function iconv_fallback_utf8_iso88591($string) { if (function_exists('utf8_decode')) { return utf8_decode($string); } @@ -756,20 +739,20 @@ class getid3_lib if ((ord($string{$offset}) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); $offset += 4; } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); $offset += 3; } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + (ord($string{($offset + 1)}) & 0x3F); $offset += 2; } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { // 0bbbbbbb @@ -788,7 +771,7 @@ class getid3_lib } // UTF-8 => UTF-16BE - function iconv_fallback_utf8_utf16be($string, $bom=false) { + static function iconv_fallback_utf8_utf16be($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFE\xFF"; @@ -799,20 +782,20 @@ class getid3_lib if ((ord($string{$offset}) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); $offset += 4; } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); $offset += 3; } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + (ord($string{($offset + 1)}) & 0x3F); $offset += 2; } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { // 0bbbbbbb @@ -831,7 +814,7 @@ class getid3_lib } // UTF-8 => UTF-16LE - function iconv_fallback_utf8_utf16le($string, $bom=false) { + static function iconv_fallback_utf8_utf16le($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFF\xFE"; @@ -842,20 +825,20 @@ class getid3_lib if ((ord($string{$offset}) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); $offset += 4; } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); $offset += 3; } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + (ord($string{($offset + 1)}) & 0x3F); $offset += 2; } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { // 0bbbbbbb @@ -874,12 +857,12 @@ class getid3_lib } // UTF-8 => UTF-16LE (BOM) - function iconv_fallback_utf8_utf16($string) { + static function iconv_fallback_utf8_utf16($string) { return getid3_lib::iconv_fallback_utf8_utf16le($string, true); } // UTF-16BE => UTF-8 - function iconv_fallback_utf16be_utf8($string) { + static function iconv_fallback_utf16be_utf8($string) { if (substr($string, 0, 2) == "\xFE\xFF") { // strip BOM $string = substr($string, 2); @@ -893,7 +876,7 @@ class getid3_lib } // UTF-16LE => UTF-8 - function iconv_fallback_utf16le_utf8($string) { + static function iconv_fallback_utf16le_utf8($string) { if (substr($string, 0, 2) == "\xFF\xFE") { // strip BOM $string = substr($string, 2); @@ -907,7 +890,7 @@ class getid3_lib } // UTF-16BE => ISO-8859-1 - function iconv_fallback_utf16be_iso88591($string) { + static function iconv_fallback_utf16be_iso88591($string) { if (substr($string, 0, 2) == "\xFE\xFF") { // strip BOM $string = substr($string, 2); @@ -921,7 +904,7 @@ class getid3_lib } // UTF-16LE => ISO-8859-1 - function iconv_fallback_utf16le_iso88591($string) { + static function iconv_fallback_utf16le_iso88591($string) { if (substr($string, 0, 2) == "\xFF\xFE") { // strip BOM $string = substr($string, 2); @@ -935,7 +918,7 @@ class getid3_lib } // UTF-16 (BOM) => ISO-8859-1 - function iconv_fallback_utf16_iso88591($string) { + static function iconv_fallback_utf16_iso88591($string) { $bom = substr($string, 0, 2); if ($bom == "\xFE\xFF") { return getid3_lib::iconv_fallback_utf16be_iso88591(substr($string, 2)); @@ -946,7 +929,7 @@ class getid3_lib } // UTF-16 (BOM) => UTF-8 - function iconv_fallback_utf16_utf8($string) { + static function iconv_fallback_utf16_utf8($string) { $bom = substr($string, 0, 2); if ($bom == "\xFE\xFF") { return getid3_lib::iconv_fallback_utf16be_utf8(substr($string, 2)); @@ -956,7 +939,7 @@ class getid3_lib return $string; } - function iconv_fallback($in_charset, $out_charset, $string) { + static function iconv_fallback($in_charset, $out_charset, $string) { if ($in_charset == $out_charset) { return $string; @@ -964,23 +947,22 @@ class getid3_lib // iconv() availble if (function_exists('iconv')) { + if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { + switch ($out_charset) { + case 'ISO-8859-1': + $converted_string = rtrim($converted_string, "\x00"); + break; + } + return $converted_string; + } - if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { - switch ($out_charset) { - case 'ISO-8859-1': - $converted_string = rtrim($converted_string, "\x00"); - break; - } - return $converted_string; - } - - // iconv() may sometimes fail with "illegal character in input string" error message - // and return an empty string, but returning the unconverted string is more useful - return $string; - } + // iconv() may sometimes fail with "illegal character in input string" error message + // and return an empty string, but returning the unconverted string is more useful + return $string; + } - // iconv() not available + // iconv() not available static $ConversionFunctionList = array(); if (empty($ConversionFunctionList)) { $ConversionFunctionList['ISO-8859-1']['UTF-8'] = 'iconv_fallback_iso88591_utf8'; @@ -1002,41 +984,42 @@ class getid3_lib $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)]; return getid3_lib::$ConversionFunction($string); } - die('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); + throw new Exception('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); } static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { + $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string $HTMLstring = ''; switch ($charset) { - case 'ISO-8859-1': - case 'ISO8859-1': - case 'ISO-8859-15': - case 'ISO8859-15': - case 'cp866': - case 'ibm866': - case '866': - case 'cp1251': - case 'Windows-1251': - case 'win-1251': case '1251': - case 'cp1252': - case 'Windows-1252': case '1252': + case '866': + case '932': + case '936': + case '950': + case 'BIG5': + case 'BIG5-HKSCS': + case 'cp1251': + case 'cp1252': + case 'cp866': + case 'EUC-JP': + case 'EUCJP': + case 'GB2312': + case 'ibm866': + case 'ISO-8859-1': + case 'ISO-8859-15': + case 'ISO8859-1': + case 'ISO8859-15': case 'KOI8-R': case 'koi8-ru': case 'koi8r': - case 'BIG5': - case '950': - case 'GB2312': - case '936': - case 'BIG5-HKSCS': case 'Shift_JIS': case 'SJIS': - case '932': - case 'EUC-JP': - case 'EUCJP': + case 'win-1251': + case 'Windows-1251': + case 'Windows-1252': $HTMLstring = htmlentities($string, ENT_COMPAT, $charset); break; @@ -1099,7 +1082,7 @@ class getid3_lib - function RGADnameLookup($namecode) { + static function RGADnameLookup($namecode) { static $RGADname = array(); if (empty($RGADname)) { $RGADname[0] = 'not set'; @@ -1111,7 +1094,7 @@ class getid3_lib } - function RGADoriginatorLookup($originatorcode) { + static function RGADoriginatorLookup($originatorcode) { static $RGADoriginator = array(); if (empty($RGADoriginator)) { $RGADoriginator[0] = 'unspecified'; @@ -1124,7 +1107,7 @@ class getid3_lib } - function RGADadjustmentLookup($rawadjustment, $signbit) { + static function RGADadjustmentLookup($rawadjustment, $signbit) { $adjustment = $rawadjustment / 10; if ($signbit == 1) { $adjustment *= -1; @@ -1133,7 +1116,7 @@ class getid3_lib } - function RGADgainString($namecode, $originatorcode, $replaygain) { + static function RGADgainString($namecode, $originatorcode, $replaygain) { if ($replaygain < 0) { $signbit = '1'; } else { @@ -1148,15 +1131,23 @@ class getid3_lib return $gainstring; } - function RGADamplitude2dB($amplitude) { + static function RGADamplitude2dB($amplitude) { return 20 * log10($amplitude); } - function GetDataImageSize($imgData, &$imageinfo) { + static function GetDataImageSize($imgData, &$imageinfo) { + static $tempdir = ''; + if (empty($tempdir)) { + // yes this is ugly, feel free to suggest a better way + require_once(dirname(__FILE__).'/getid3.php'); + $getid3_temp = new getID3(); + $tempdir = $getid3_temp->tempdir; + unset($getid3_temp); + } $GetDataImageSize = false; - if ($tempfilename = tempnam('*', 'getID3')) { - if ($tmp = @fopen($tempfilename, 'wb')) { + if ($tempfilename = tempnam($tempdir, 'gI3')) { + if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) { fwrite($tmp, $imgData); fclose($tmp); $GetDataImageSize = @GetImageSize($tempfilename, $imageinfo); @@ -1166,7 +1157,7 @@ class getid3_lib return $GetDataImageSize; } - function ImageTypesLookup($imagetypeid) { + static function ImageTypesLookup($imagetypeid) { static $ImageTypesLookup = array(); if (empty($ImageTypesLookup)) { $ImageTypesLookup[1] = 'gif'; @@ -1210,7 +1201,7 @@ class getid3_lib } } - } else { + } elseif (!is_array($value)) { $newvaluelength = strlen(trim($value)); foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { @@ -1222,8 +1213,9 @@ class getid3_lib } } - if (empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { - $ThisFileInfo['comments'][$tagname][] = trim($value); + if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { + $value = (is_string($value) ? trim($value) : $value); + $ThisFileInfo['comments'][$tagname][] = $value; } } } @@ -1231,21 +1223,31 @@ class getid3_lib } // Copy to ['comments_html'] - foreach ($ThisFileInfo['comments'] as $field => $values) { - foreach ($values as $index => $value) { - $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); - } - } + foreach ($ThisFileInfo['comments'] as $field => $values) { + if ($field == 'picture') { + // pictures can take up a lot of space, and we don't need multiple copies of them + // let there be a single copy in [comments][picture], and not elsewhere + continue; + } + foreach ($values as $index => $value) { + if (is_array($value)) { + $ThisFileInfo['comments_html'][$field][$index] = $value; + } else { + $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); + } + } + } } + return true; } - function EmbeddedLookup($key, $begin, $end, $file, $name) { + static function EmbeddedLookup($key, $begin, $end, $file, $name) { // Cached static $cache; if (isset($cache[$file][$name])) { - return @$cache[$file][$name][$key]; + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); } // Init @@ -1275,20 +1277,22 @@ class getid3_lib // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1)); - @list($ThisKey, $ThisValue) = explode("\t", $line, 2); + $explodedLine = explode("\t", $line, 2); + $ThisKey = (isset($explodedLine[0]) ? $explodedLine[0] : ''); + $ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : ''); $cache[$file][$name][$ThisKey] = trim($ThisValue); } // Close and return fclose($fp); - return @$cache[$file][$name][$key]; + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); } - function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { + static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { global $GETID3_ERRORARRAY; if (file_exists($filename)) { - if (@include_once($filename)) { + if (include_once($filename)) { return true; } else { $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors'; @@ -1297,13 +1301,17 @@ class getid3_lib $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing'; } if ($DieOnFailure) { - die($diemessage); + throw new Exception($diemessage); } else { $GETID3_ERRORARRAY[] = $diemessage; } return false; } + public static function trimNullByte($string) { + return trim($string, "\x00"); + } + } ?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/getid3.php b/3rdparty/getid3/getid3.php similarity index 52% rename from apps/media/getID3/getid3/getid3.php rename to 3rdparty/getid3/getid3.php index f9dcf706d0..e8a3f7e2de 100644 --- a/apps/media/getID3/getid3/getid3.php +++ b/3rdparty/getid3/getid3.php @@ -9,91 +9,164 @@ // /// ///////////////////////////////////////////////////////////////// -// Defines -define('GETID3_VERSION', '1.7.9-20090308'); -define('GETID3_FREAD_BUFFER_SIZE', 16384); // read buffer size in bytes +// attempt to define temp dir as something flexible but reliable +$temp_dir = ini_get('upload_tmp_dir'); +if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { + $temp_dir = ''; +} +if (!$temp_dir && function_exists('sys_get_temp_dir')) { + // PHP v5.2.1+ + // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts + $temp_dir = sys_get_temp_dir(); +} +$temp_dir = realpath($temp_dir); +$open_basedir = ini_get('open_basedir'); +if ($open_basedir) { + // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/" + $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir); + $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir); + if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) { + $temp_dir .= DIRECTORY_SEPARATOR; + } + $found_valid_tempdir = false; + $open_basedirs = explode(':', $open_basedir); + foreach ($open_basedirs as $basedir) { + if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { + $basedir .= DIRECTORY_SEPARATOR; + } + if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) { + $found_valid_tempdir = true; + break; + } + } + if (!$found_valid_tempdir) { + $temp_dir = ''; + } + unset($open_basedirs, $found_valid_tempdir, $basedir); +} +if (!$temp_dir) { + $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir +} +// $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system +define('GETID3_TEMP_DIR', $temp_dir); +unset($open_basedir, $temp_dir); +// define a constant rather than looking up every time it is needed +if (!defined('GETID3_OS_ISWINDOWS')) { + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { + define('GETID3_OS_ISWINDOWS', true); + } else { + define('GETID3_OS_ISWINDOWS', false); + } +} + +// Get base path of getID3() - ONCE +if (!defined('GETID3_INCLUDEPATH')) { + foreach (get_included_files() as $key => $val) { + if (basename($val) == 'getid3.php') { + define('GETID3_INCLUDEPATH', dirname($val).DIRECTORY_SEPARATOR); + break; + } + } +} + +// End: Defines + class getID3 { // public: Settings - var $encoding = 'ISO-8859-1'; // CASE SENSITIVE! - i.e. (must be supported by iconv()) - // Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE - - var $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' - - var $tempdir = '*'; // default '*' should use system temp dir + public $encoding = 'UTF-8'; // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE + public $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252' // public: Optional tag checks - disable for speed. - var $option_tag_id3v1 = true; // Read and process ID3v1 tags - var $option_tag_id3v2 = true; // Read and process ID3v2 tags - var $option_tag_lyrics3 = true; // Read and process Lyrics3 tags - var $option_tag_apetag = true; // Read and process APE tags - var $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding - var $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities + public $option_tag_id3v1 = true; // Read and process ID3v1 tags + public $option_tag_id3v2 = true; // Read and process ID3v2 tags + public $option_tag_lyrics3 = true; // Read and process Lyrics3 tags + public $option_tag_apetag = true; // Read and process APE tags + public $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding + public $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities // public: Optional tag/comment calucations - var $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc + public $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc + + // public: Optional handling of embedded attachments (e.g. images) + public $option_save_attachments = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility // public: Optional calculations - var $option_md5_data = false; // Get MD5 sum of data part - slow - var $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG - var $option_sha1_data = false; // Get SHA1 sum of data part - slow - var $option_max_2gb_check = true; // Check whether file is larger than 2 Gb and thus not supported by PHP + public $option_md5_data = false; // Get MD5 sum of data part - slow + public $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG + public $option_sha1_data = false; // Get SHA1 sum of data part - slow + public $option_max_2gb_check = null; // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX) - // private - var $filename; + // public: Read buffer size in bytes + public $option_fread_buffer_size = 32768; + // Public variables + public $filename; // Filename of file being analysed. + public $fp; // Filepointer to file being analysed. + public $info; // Result array. + + // Protected variables + protected $startup_error = ''; + protected $startup_warning = ''; + protected $memory_limit = 0; + + const VERSION = '1.9.3-20111213'; + const FREAD_BUFFER_SIZE = 32768; + var $tempdir = GETID3_TEMP_DIR; + + const ATTACHMENTS_NONE = false; + const ATTACHMENTS_INLINE = true; // public: constructor - function getID3() - { + public function __construct() { - $this->startup_error = ''; - $this->startup_warning = ''; - - // Check for PHP version >= 4.2.0 - if (phpversion() < '4.2.0') { - $this->startup_error .= 'getID3() requires PHP v4.2.0 or higher - you are running v'.phpversion(); + // Check for PHP version + $required_php_version = '5.0.5'; + if (version_compare(PHP_VERSION, $required_php_version, '<')) { + $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION; + return false; } // Check memory - $memory_limit = ini_get('memory_limit'); - if (eregi('([0-9]+)M', $memory_limit, $matches)) { + $this->memory_limit = ini_get('memory_limit'); + if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) { // could be stored as "16M" rather than 16777216 for example - $memory_limit = $matches[1] * 1048576; + $this->memory_limit = $matches[1] * 1048576; + } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 + // could be stored as "2G" rather than 2147483648 for example + $this->memory_limit = $matches[1] * 1073741824; } - if ($memory_limit <= 0) { + if ($this->memory_limit <= 0) { // memory limits probably disabled - } elseif ($memory_limit <= 3145728) { - $this->startup_error .= 'PHP has less than 3MB available memory and will very likely run out. Increase memory_limit in php.ini'; - } elseif ($memory_limit <= 12582912) { - $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'; + } elseif ($this->memory_limit <= 4194304) { + $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'; + } elseif ($this->memory_limit <= 12582912) { + $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'; } // Check safe_mode off - if ((bool) ini_get('safe_mode')) { - $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); } + if (intval(ini_get('mbstring.func_overload')) > 0) { + $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.'); + } - // define a constant rather than looking up every time it is needed - if (!defined('GETID3_OS_ISWINDOWS')) { - if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { - define('GETID3_OS_ISWINDOWS', true); - } else { - define('GETID3_OS_ISWINDOWS', false); + // Check for magic_quotes_runtime + if (function_exists('get_magic_quotes_runtime')) { + if (get_magic_quotes_runtime()) { + return $this->startup_error('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'); } } - // Get base path of getID3() - ONCE - if (!defined('GETID3_INCLUDEPATH')) { - foreach (get_included_files() as $key => $val) { - if (basename($val) == 'getid3.php') { - define('GETID3_INCLUDEPATH', dirname($val).DIRECTORY_SEPARATOR); - break; - } + // Check for magic_quotes_gpc + if (function_exists('magic_quotes_gpc')) { + if (get_magic_quotes_gpc()) { + return $this->startup_error('magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'); } } @@ -102,36 +175,61 @@ class getID3 $this->startup_error .= 'getid3.lib.php is missing or corrupt'; } + if ($this->option_max_2gb_check === null) { + $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647); + } + // Needed for Windows only: // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC // as well as other helper functions such as head, tail, md5sum, etc - // IMPORTANT: This path cannot have spaces in it. If neccesary, use the 8dot3 equivalent - // ie for "C:/Program Files/Apache/" put "C:/PROGRA~1/APACHE/" + // This path cannot contain spaces, but the below code will attempt to get the + // 8.3-equivalent path automatically // IMPORTANT: This path must include the trailing slash if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path if (!is_dir($helperappsdir)) { - $this->startup_error .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; + $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; } elseif (strpos(realpath($helperappsdir), ' ') !== false) { $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); + $path_so_far = array(); foreach ($DirPieces as $key => $value) { - if ((strpos($value, '.') !== false) && (strpos($value, ' ') === false)) { - if (strpos($value, '.') > 8) { - $value = substr($value, 0, 6).'~1'; + if (strpos($value, ' ') !== false) { + if (!empty($path_so_far)) { + $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); + $dir_listing = `$commandline`; + $lines = explode("\n", $dir_listing); + foreach ($lines as $line) { + $line = trim($line); + if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) { + list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; + if ((strtoupper($filesize) == '') && (strtolower($filename) == strtolower($value))) { + $value = $shortname; + } + } + } + } else { + $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'; } - } elseif ((strpos($value, ' ') !== false) || strlen($value) > 8) { - $value = substr($value, 0, 6).'~1'; } - $DirPieces[$key] = strtoupper($value); + $path_so_far[] = $value; } - $this->startup_error .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary (on this server that would be something like "'.implode(DIRECTORY_SEPARATOR, $DirPieces).'" - NOTE: this may or may not be the actual 8.3 equivalent of "'.$helperappsdir.'", please double-check). You can run "dir /x" from the commandline to see the correct 8.3-style names.'; + $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); } - define('GETID3_HELPERAPPSDIR', realpath($helperappsdir).DIRECTORY_SEPARATOR); + define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); } + return true; + } + + public function version() { + return self::VERSION; + } + + public function fread_buffer_size() { + return $this->option_fread_buffer_size; } @@ -141,7 +239,6 @@ class getID3 return false; } foreach ($optArray as $opt => $val) { - //if (isset($this, $opt) === false) { if (isset($this->$opt) === false) { continue; } @@ -151,266 +248,256 @@ class getID3 } - // public: analyze file - replaces GetAllFileInfo() and GetTagOnly() - function analyze($filename) { + public function openfile($filename) { + try { + if (!empty($this->startup_error)) { + throw new getid3_exception($this->startup_error); + } + if (!empty($this->startup_warning)) { + $this->warning($this->startup_warning); + } - if (!empty($this->startup_error)) { - return $this->error($this->startup_error); - } - if (!empty($this->startup_warning)) { - $this->warning($this->startup_warning); - } + // init result array and set parameters + $this->filename = $filename; + $this->info = array(); + $this->info['GETID3_VERSION'] = $this->version(); + $this->info['php_memory_limit'] = $this->memory_limit; - // init result array and set parameters - $this->info = array(); - $this->info['GETID3_VERSION'] = GETID3_VERSION; + // remote files not supported + if (preg_match('/^(ht|f)tp:\/\//', $filename)) { + throw new getid3_exception('Remote files are not supported - please copy the file locally first'); + } - // Check encoding/iconv support - if (!function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { - $errormessage = 'iconv() support is needed for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; - if (GETID3_OS_ISWINDOWS) { - $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; + $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); + $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename); + + // open local file + if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { + // great } else { - $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; + throw new getid3_exception('Could not open "'.$filename.'" (does not exist, or is not a file)'); } - return $this->error($errormessage); - } - // Disable magic_quotes_runtime, if neccesary - $old_magic_quotes_runtime = get_magic_quotes_runtime(); // store current setting of magic_quotes_runtime - if ($old_magic_quotes_runtime) { - set_magic_quotes_runtime(0); // turn off magic_quotes_runtime - if (get_magic_quotes_runtime()) { - return $this->error('Could not disable magic_quotes_runtime - getID3() cannot work properly with this setting enabled'); - } - } + $this->info['filesize'] = filesize($filename); + // set redundant parameters - might be needed in some include file + $this->info['filename'] = basename($filename); + $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); + $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; - // remote files not supported - if (preg_match('/^(ht|f)tp:\/\//', $filename)) { - return $this->error('Remote files are not supported in this version of getID3() - please copy the file locally first'); - } - $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); - $filename = preg_replace('#'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#', DIRECTORY_SEPARATOR, $filename); - - // open local file - if (file_exists($filename) && ($fp = @fopen($filename, 'rb'))) { - // great - } else { - return $this->error('Could not open file "'.$filename.'"'); - } - - // set parameters - $this->info['filesize'] = filesize($filename); - - // option_max_2gb_check - if ($this->option_max_2gb_check) { - // PHP doesn't support integers larger than 31-bit (~2GB) - // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize - // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer - fseek($fp, 0, SEEK_END); - if ((($this->info['filesize'] != 0) && (ftell($fp) == 0)) || - ($this->info['filesize'] < 0) || - (ftell($fp) < 0)) { - $real_filesize = false; - if (GETID3_OS_ISWINDOWS) { - $commandline = 'dir /-C "'.str_replace('/', DIRECTORY_SEPARATOR, $filename).'"'; - $dir_output = `$commandline`; - if (eregi('1 File\(s\)[ ]+([0-9]+) bytes', $dir_output, $matches)) { - $real_filesize = (float) $matches[1]; + // option_max_2gb_check + if ($this->option_max_2gb_check) { + // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB) + // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize + // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer + $fseek = fseek($this->fp, 0, SEEK_END); + if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || + ($this->info['filesize'] < 0) || + (ftell($this->fp) < 0)) { + $real_filesize = false; + if (GETID3_OS_ISWINDOWS) { + $commandline = 'dir /-C "'.str_replace('/', DIRECTORY_SEPARATOR, $filename).'"'; + $dir_output = `$commandline`; + if (preg_match('#1 File\(s\)[ ]+([0-9]+) bytes#i', $dir_output, $matches)) { + $real_filesize = (float) $matches[1]; + } + } else { + $commandline = 'ls -o -g -G --time-style=long-iso '.escapeshellarg($filename); + $dir_output = `$commandline`; + if (preg_match('#([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) '.str_replace('#', '\\#', preg_quote($filename)).'$#', $dir_output, $matches)) { + $real_filesize = (float) $matches[1]; + } } - } else { - $commandline = 'ls -o -g -G --time-style=long-iso '.escapeshellarg($filename); - $dir_output = `$commandline`; - if (eregi('([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) '.preg_quote($filename).'$', $dir_output, $matches)) { - $real_filesize = (float) $matches[1]; + if ($real_filesize === false) { + unset($this->info['filesize']); + fclose($this->fp); + throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.'); + } elseif (getid3_lib::intValueSupported($real_filesize)) { + unset($this->info['filesize']); + fclose($this->fp); + throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org'); } + $this->info['filesize'] = $real_filesize; + $this->error('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); + } + } + + // set more parameters + $this->info['avdataoffset'] = 0; + $this->info['avdataend'] = $this->info['filesize']; + $this->info['fileformat'] = ''; // filled in later + $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used + $this->info['video']['dataformat'] = ''; // filled in later, unset if not used + $this->info['tags'] = array(); // filled in later, unset if not used + $this->info['error'] = array(); // filled in later, unset if not used + $this->info['warning'] = array(); // filled in later, unset if not used + $this->info['comments'] = array(); // filled in later, unset if not used + $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired + + return true; + + } catch (Exception $e) { + $this->error($e->getMessage()); + } + return false; + } + + // public: analyze file + function analyze($filename) { + try { + if (!$this->openfile($filename)) { + return $this->info; + } + + // Handle tags + foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { + $option_tag = 'option_tag_'.$tag_name; + if ($this->$option_tag) { + $this->include_module('tag.'.$tag_name); + try { + $tag_class = 'getid3_'.$tag_name; + $tag = new $tag_class($this); + $tag->Analyze(); } - if ($real_filesize === false) { - unset($this->info['filesize']); - fclose($fp); - return $this->error('File is most likely larger than 2GB and is not supported by PHP'); - } elseif ($real_filesize < pow(2, 31)) { - unset($this->info['filesize']); - fclose($fp); - return $this->error('PHP seems to think the file is larger than 2GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org'); + catch (getid3_exception $e) { + throw $e; } - $this->info['filesize'] = $real_filesize; - $this->error('File is larger than 2GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); + } } - } - - // set more parameters - $this->info['avdataoffset'] = 0; - $this->info['avdataend'] = $this->info['filesize']; - $this->info['fileformat'] = ''; // filled in later - $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used - $this->info['video']['dataformat'] = ''; // filled in later, unset if not used - $this->info['tags'] = array(); // filled in later, unset if not used - $this->info['error'] = array(); // filled in later, unset if not used - $this->info['warning'] = array(); // filled in later, unset if not used - $this->info['comments'] = array(); // filled in later, unset if not used - $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired - - // set redundant parameters - might be needed in some include file - $this->info['filename'] = basename($filename); - $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); - $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; - - - // handle ID3v2 tag - done first - already at beginning of file - // ID3v2 detection (even if not parsing) is always done otherwise fileformat is much harder to detect - if ($this->option_tag_id3v2) { - - $GETID3_ERRORARRAY = &$this->info['warning']; - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, false)) { - $tag = new getid3_id3v2($fp, $this->info); - unset($tag); + if (isset($this->info['id3v2']['tag_offset_start'])) { + $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); + } + foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { + if (isset($this->info[$tag_key]['tag_offset_start'])) { + $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); + } } - } else { - - fseek($fp, 0, SEEK_SET); - $header = fread($fp, 10); - if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { - $this->info['id3v2']['header'] = true; - $this->info['id3v2']['majorversion'] = ord($header{3}); - $this->info['id3v2']['minorversion'] = ord($header{4}); - $this->info['id3v2']['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length - - $this->info['id3v2']['tag_offset_start'] = 0; - $this->info['id3v2']['tag_offset_end'] = $this->info['id3v2']['tag_offset_start'] + $this->info['id3v2']['headerlength']; - $this->info['avdataoffset'] = $this->info['id3v2']['tag_offset_end']; + // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier + if (!$this->option_tag_id3v2) { + fseek($this->fp, 0, SEEK_SET); + $header = fread($this->fp, 10); + if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { + $this->info['id3v2']['header'] = true; + $this->info['id3v2']['majorversion'] = ord($header{3}); + $this->info['id3v2']['minorversion'] = ord($header{4}); + $this->info['avdataoffset'] += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length + } } - } + // read 32 kb file data + fseek($this->fp, $this->info['avdataoffset'], SEEK_SET); + $formattest = fread($this->fp, 32774); + // determine format + $determined_format = $this->GetFileFormat($formattest, $filename); - // handle ID3v1 tag - if ($this->option_tag_id3v1) { - if (!@include_once(GETID3_INCLUDEPATH.'module.tag.id3v1.php')) { - return $this->error('module.tag.id3v1.php is missing - you may disable option_tag_id3v1.'); + // unable to determine file format + if (!$determined_format) { + fclose($this->fp); + return $this->error('unable to determine file format'); } - $tag = new getid3_id3v1($fp, $this->info); - unset($tag); - } - // handle APE tag - if ($this->option_tag_apetag) { - if (!@include_once(GETID3_INCLUDEPATH.'module.tag.apetag.php')) { - return $this->error('module.tag.apetag.php is missing - you may disable option_tag_apetag.'); + // check for illegal ID3 tags + if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { + if ($determined_format['fail_id3'] === 'ERROR') { + fclose($this->fp); + return $this->error('ID3 tags not allowed on this file type.'); + } elseif ($determined_format['fail_id3'] === 'WARNING') { + $this->warning('ID3 tags not allowed on this file type.'); + } } - $tag = new getid3_apetag($fp, $this->info); - unset($tag); - } - // handle lyrics3 tag - if ($this->option_tag_lyrics3) { - if (!@include_once(GETID3_INCLUDEPATH.'module.tag.lyrics3.php')) { - return $this->error('module.tag.lyrics3.php is missing - you may disable option_tag_lyrics3.'); + // check for illegal APE tags + if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { + if ($determined_format['fail_ape'] === 'ERROR') { + fclose($this->fp); + return $this->error('APE tags not allowed on this file type.'); + } elseif ($determined_format['fail_ape'] === 'WARNING') { + $this->warning('APE tags not allowed on this file type.'); + } } - $tag = new getid3_lyrics3($fp, $this->info); - unset($tag); - } - // read 32 kb file data - fseek($fp, $this->info['avdataoffset'], SEEK_SET); - $formattest = fread($fp, 32774); + // set mime type + $this->info['mime_type'] = $determined_format['mime_type']; - // determine format - $determined_format = $this->GetFileFormat($formattest, $filename); - - // unable to determine file format - if (!$determined_format) { - fclose($fp); - return $this->error('unable to determine file format'); - } - - // check for illegal ID3 tags - if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { - if ($determined_format['fail_id3'] === 'ERROR') { - fclose($fp); - return $this->error('ID3 tags not allowed on this file type.'); - } elseif ($determined_format['fail_id3'] === 'WARNING') { - $this->info['warning'][] = 'ID3 tags not allowed on this file type.'; + // supported format signature pattern detected, but module deleted + if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { + fclose($this->fp); + return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); } - } - // check for illegal APE tags - if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { - if ($determined_format['fail_ape'] === 'ERROR') { - fclose($fp); - return $this->error('APE tags not allowed on this file type.'); - } elseif ($determined_format['fail_ape'] === 'WARNING') { - $this->info['warning'][] = 'APE tags not allowed on this file type.'; + // module requires iconv support + // Check encoding/iconv support + if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { + $errormessage = 'iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; + if (GETID3_OS_ISWINDOWS) { + $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; + } else { + $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; + } + return $this->error($errormessage); } - } - // set mime type - $this->info['mime_type'] = $determined_format['mime_type']; + // include module + include_once(GETID3_INCLUDEPATH.$determined_format['include']); - // supported format signature pattern detected, but module deleted - if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { - fclose($fp); - return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); - } - - // module requires iconv support - if (!function_exists('iconv') && @$determined_format['iconv_req']) { - return $this->error('iconv support is required for this module ('.$determined_format['include'].').'); - } - - // include module - include_once(GETID3_INCLUDEPATH.$determined_format['include']); - - // instantiate module class - $class_name = 'getid3_'.$determined_format['module']; - if (!class_exists($class_name)) { - return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); - } - if (isset($determined_format['option'])) { - $class = new $class_name($fp, $this->info, $determined_format['option']); - } else { - $class = new $class_name($fp, $this->info); - } - unset($class); - - // close file - fclose($fp); - - // process all tags - copy to 'tags' and convert charsets - if ($this->option_tags_process) { - $this->HandleAllTags(); - } - - // perform more calculations - if ($this->option_extra_info) { - $this->ChannelsBitratePlaytimeCalculations(); - $this->CalculateCompressionRatioVideo(); - $this->CalculateCompressionRatioAudio(); - $this->CalculateReplayGain(); - $this->ProcessAudioStreams(); - } - - // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags - if ($this->option_md5_data) { - // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too - if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { - $this->getHashdata('md5'); + // instantiate module class + $class_name = 'getid3_'.$determined_format['module']; + if (!class_exists($class_name)) { + return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); } + //if (isset($determined_format['option'])) { + // //$class = new $class_name($this->fp, $this->info, $determined_format['option']); + //} else { + //$class = new $class_name($this->fp, $this->info); + $class = new $class_name($this); + //} + + if (!empty($determined_format['set_inline_attachments'])) { + $class->inline_attachments = $this->option_save_attachments; + } + $class->Analyze(); + + unset($class); + + // close file + fclose($this->fp); + + // process all tags - copy to 'tags' and convert charsets + if ($this->option_tags_process) { + $this->HandleAllTags(); + } + + // perform more calculations + if ($this->option_extra_info) { + $this->ChannelsBitratePlaytimeCalculations(); + $this->CalculateCompressionRatioVideo(); + $this->CalculateCompressionRatioAudio(); + $this->CalculateReplayGain(); + $this->ProcessAudioStreams(); + } + + // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_md5_data) { + // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too + if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { + $this->getHashdata('md5'); + } + } + + // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_sha1_data) { + $this->getHashdata('sha1'); + } + + // remove undesired keys + $this->CleanUp(); + + } catch (Exception $e) { + $this->error('Caught exception: '.$e->getMessage()); } - // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags - if ($this->option_sha1_data) { - $this->getHashdata('sha1'); - } - - // remove undesired keys - $this->CleanUp(); - - // restore magic_quotes_runtime setting - set_magic_quotes_runtime($old_magic_quotes_runtime); - // return info array return $this->info; } @@ -418,9 +505,10 @@ class getID3 // private: error handling function error($message) { - $this->CleanUp(); - + if (!isset($this->info['error'])) { + $this->info['error'] = array(); + } $this->info['error'][] = $message; return $this->info; } @@ -465,6 +553,19 @@ class getID3 unset($this->info['avdataend']); } } + + // remove possible duplicated identical entries + if (!empty($this->info['error'])) { + $this->info['error'] = array_values(array_unique($this->info['error'])); + } + if (!empty($this->info['warning'])) { + $this->info['warning'] = array_values(array_unique($this->info['warning'])); + } + + // remove "global variable" type keys + unset($this->info['php_memory_limit']); + + return true; } @@ -489,18 +590,24 @@ class getID3 'pattern' => '^ADIF', 'group' => 'audio', 'module' => 'aac', - 'option' => 'adif', 'mime_type' => 'application/octet-stream', 'fail_ape' => 'WARNING', ), + // AA - audio - Audible Audiobook + 'adts' => array( + 'pattern' => '^.{4}\x57\x90\x75\x36', + 'group' => 'audio', + 'module' => 'aa', + 'mime_type' => 'audio/audible ', + ), + // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) 'adts' => array( 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]', 'group' => 'audio', 'module' => 'aac', - 'option' => 'adts', 'mime_type' => 'application/octet-stream', 'fail_ape' => 'WARNING', ), @@ -532,7 +639,7 @@ class getID3 // DSS - audio - Digital Speech Standard 'dss' => array( - 'pattern' => '^[\x02]dss', + 'pattern' => '^[\x02-\x03]dss', 'group' => 'audio', 'module' => 'dss', 'mime_type' => 'application/octet-stream', @@ -552,6 +659,7 @@ class getID3 'group' => 'audio', 'module' => 'flac', 'mime_type' => 'audio/x-flac', + 'set_inline_attachments' => true, ), // LA - audio - Lossless Audio (LA) @@ -601,7 +709,7 @@ class getID3 'pattern' => '^IMPM', 'group' => 'audio', 'module' => 'mod', - 'option' => 'it', + //'option' => 'it', 'mime_type' => 'audio/it', ), @@ -610,7 +718,7 @@ class getID3 'pattern' => '^Extended Module', 'group' => 'audio', 'module' => 'mod', - 'option' => 'xm', + //'option' => 'xm', 'mime_type' => 'audio/xm', ), @@ -619,7 +727,7 @@ class getID3 'pattern' => '^.{44}SCRM', 'group' => 'audio', 'module' => 'mod', - 'option' => 's3m', + //'option' => 's3m', 'mime_type' => 'audio/s3m', ), @@ -633,7 +741,7 @@ class getID3 // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) 'mp3' => array( - 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]', + 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\x0B\x10-\x1B\x20-\x2B\x30-\x3B\x40-\x4B\x50-\x5B\x60-\x6B\x70-\x7B\x80-\x8B\x90-\x9B\xA0-\xAB\xB0-\xBB\xC0-\xCB\xD0-\xDB\xE0-\xEB\xF0-\xFB]', 'group' => 'audio', 'module' => 'mp3', 'mime_type' => 'audio/mpeg', @@ -731,6 +839,7 @@ class getID3 'group' => 'audio-video', 'module' => 'matroska', 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska + 'set_inline_attachments' => true, ), // MPEG - audio/video - MPEG (Moving Pictures Experts Group) @@ -757,6 +866,7 @@ class getID3 'mime_type' => 'application/ogg', 'fail_id3' => 'WARNING', 'fail_ape' => 'WARNING', + 'set_inline_attachments' => true, ), // QT - audio/video - Quicktime @@ -847,9 +957,9 @@ class getID3 ), - // SVG - still image - Scalable Vector Graphics (SVG) + // SVG - still image - Scalable Vector Graphics (SVG) 'svg' => array( - 'pattern' => ' '( 'graphic', 'module' => 'svg', 'mime_type' => 'image/svg+xml', @@ -858,7 +968,7 @@ class getID3 ), - // TIFF - still image - Tagged Information File Format (TIFF) + // TIFF - still image - Tagged Information File Format (TIFF) 'tiff' => array( 'pattern' => '^(II\x2A\x00|MM\x00\x2A)', 'group' => 'graphic', @@ -869,6 +979,17 @@ class getID3 ), + // EFAX - still image - eFax (TIFF derivative) + 'bmp' => array( + 'pattern' => '^\xDC\xFE', + 'group' => 'graphic', + 'module' => 'efax', + 'mime_type' => 'image/efax', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // Data formats // ISO - data - International Standards Organization (ISO) CD-ROM Image @@ -935,10 +1056,10 @@ class getID3 // Misc other formats - // PAR2 - data - Parity Volume Set Specification 2.0 - 'par2' => array ( - 'pattern' => '^PAR2\x00PKT', - 'group' => 'misc', + // PAR2 - data - Parity Volume Set Specification 2.0 + 'par2' => array ( + 'pattern' => '^PAR2\x00PKT', + 'group' => 'misc', 'module' => 'par2', 'mime_type' => 'application/octet-stream', 'fail_id3' => 'ERROR', @@ -957,13 +1078,22 @@ class getID3 // MSOFFICE - data - ZIP compressed data 'msoffice' => array( - 'pattern' => '^\xD0\xCF\x11\xE0', // D0CF11E == DOCFILE == Microsoft Office Document + 'pattern' => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document 'group' => 'misc', 'module' => 'msoffice', 'mime_type' => 'application/octet-stream', 'fail_id3' => 'ERROR', 'fail_ape' => 'ERROR', ), + + // CUE - data - CUEsheet (index to single-file disc images) + 'cue' => array( + 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents + 'group' => 'misc', + 'module' => 'cue', + 'mime_type' => 'application/octet-stream', + ), + ); } @@ -980,23 +1110,30 @@ class getID3 // Identify file format - loop through $format_info and detect with reg expr foreach ($this->GetFileFormatArray() as $format_name => $info) { - // Using preg_match() instead of ereg() - much faster // The /s switch on preg_match() forces preg_match() NOT to treat // newline (0x0A) characters as special chars but do a binary match - if (preg_match('/'.$info['pattern'].'/s', $filedata)) { + if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; return $info; } } - if (preg_match('/\.mp[123a]$/i', $filename)) { + if (preg_match('#\.mp[123a]$#i', $filename)) { // Too many mp3 encoders on the market put gabage in front of mpeg files // use assume format on these if format detection failed $GetFileFormatArray = $this->GetFileFormatArray(); $info = $GetFileFormatArray['mp3']; $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; return $info; + } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { + // there's not really a useful consistent "magic" at the beginning of .cue files to identify them + // so until I think of something better, just go by filename if all other format checks fail + // and verify there's at least one instance of "TRACK xx AUDIO" in the file + $GetFileFormatArray = $this->GetFileFormatArray(); + $info = $GetFileFormatArray['cue']; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; } return false; @@ -1039,7 +1176,7 @@ class getID3 'ogg' => array('vorbiscomment' , 'UTF-8'), 'png' => array('png' , 'UTF-8'), 'tiff' => array('tiff' , 'ISO-8859-1'), - 'quicktime' => array('quicktime' , 'ISO-8859-1'), + 'quicktime' => array('quicktime' , 'UTF-8'), 'real' => array('real' , 'ISO-8859-1'), 'vqf' => array('vqf' , 'ISO-8859-1'), 'zip' => array('zip' , 'ISO-8859-1'), @@ -1047,11 +1184,13 @@ class getID3 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), 'id3v1' => array('id3v1' , $this->encoding_id3v1), 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8 - 'ape' => array('ape' , 'UTF-8') + 'ape' => array('ape' , 'UTF-8'), + 'cue' => array('cue' , 'ISO-8859-1'), + 'matroska' => array('matroska' , 'UTF-8'), ); } - // loop thru comments array + // loop through comments array foreach ($tags as $comment_name => $tagname_encoding_array) { list($tag_name, $encoding) = $tagname_encoding_array; @@ -1065,8 +1204,11 @@ class getID3 foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { foreach ($valuearray as $key => $value) { - if (strlen(trim($value)) > 0) { - $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; // do not trim!! Unicode characters will get mangled if trailing nulls are removed! + if (is_string($value)) { + $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! + } + if ($value) { + $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; } } } @@ -1081,7 +1223,7 @@ class getID3 foreach ($valuearray as $key => $value) { if (is_string($value)) { //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding); - $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $encoding)); + $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', trim(getid3_lib::MultiByteCharString2HTML($value, $encoding))); } else { $this->info['tags_html'][$tag_name][$tag_key][$key] = $value; } @@ -1093,6 +1235,51 @@ class getID3 } } + + // pictures can take up a lot of space, and we don't need multiple copies of them + // let there be a single copy in [comments][picture], and not elsewhere + if (!empty($this->info['tags'])) { + $unset_keys = array('tags', 'tags_html'); + foreach ($this->info['tags'] as $tagtype => $tagarray) { + foreach ($tagarray as $tagname => $tagdata) { + if ($tagname == 'picture') { + foreach ($tagdata as $key => $tagarray) { + $this->info['comments']['picture'][] = $tagarray; + if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { + if (isset($this->info['tags'][$tagtype][$tagname][$key])) { + unset($this->info['tags'][$tagtype][$tagname][$key]); + } + if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { + unset($this->info['tags_html'][$tagtype][$tagname][$key]); + } + } + } + } + } + foreach ($unset_keys as $unset_key) { + // remove possible empty keys from (e.g. [tags][id3v2][picture]) + if (empty($this->info[$unset_key][$tagtype]['picture'])) { + unset($this->info[$unset_key][$tagtype]['picture']); + } + if (empty($this->info[$unset_key][$tagtype])) { + unset($this->info[$unset_key][$tagtype]); + } + if (empty($this->info[$unset_key])) { + unset($this->info[$unset_key]); + } + } + // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) + if (isset($this->info[$tagtype]['comments']['picture'])) { + unset($this->info[$tagtype]['comments']['picture']); + } + if (empty($this->info[$tagtype]['comments'])) { + unset($this->info[$tagtype]['comments']); + } + if (empty($this->info[$tagtype])) { + unset($this->info[$tagtype]); + } + } + } return true; } @@ -1108,7 +1295,7 @@ class getID3 break; } - if ((@$this->info['fileformat'] == 'ogg') && (@$this->info['audio']['dataformat'] == 'vorbis')) { + if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { // We cannot get an identical md5_data value for Ogg files where the comments // span more than 1 Ogg page (compared to the same audio data with smaller @@ -1128,10 +1315,10 @@ class getID3 // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but // currently vorbiscomment only works on OggVorbis files. - if ((bool) ini_get('safe_mode')) { + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { - $this->info['warning'][] = 'Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'; - $this->info[$algorithm.'_data'] = false; + $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); + $this->info[$algorithm.'_data'] = false; } else { @@ -1139,12 +1326,11 @@ class getID3 $old_abort = ignore_user_abort(true); // Create empty file - $empty = tempnam('*', 'getID3'); + $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); touch($empty); - // Use vorbiscomment to make temp file without comments - $temp = tempnam('*', 'getID3'); + $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); $file = $this->info['filenamepath']; if (GETID3_OS_ISWINDOWS) { @@ -1178,11 +1364,11 @@ class getID3 // Get hash of newly created file switch ($algorithm) { case 'md5': - $this->info[$algorithm.'_data'] = getid3_lib::md5_file($temp); + $this->info[$algorithm.'_data'] = md5_file($temp); break; case 'sha1': - $this->info[$algorithm.'_data'] = getid3_lib::sha1_file($temp); + $this->info[$algorithm.'_data'] = sha1_file($temp); break; } } @@ -1208,11 +1394,11 @@ class getID3 // get hash from whole file switch ($algorithm) { case 'md5': - $this->info[$algorithm.'_data'] = getid3_lib::md5_file($this->info['filenamepath']); + $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); break; case 'sha1': - $this->info[$algorithm.'_data'] = getid3_lib::sha1_file($this->info['filenamepath']); + $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); break; } } @@ -1225,9 +1411,11 @@ class getID3 function ChannelsBitratePlaytimeCalculations() { // set channelmode on audio - if (@$this->info['audio']['channels'] == '1') { + if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { + // ignore + } elseif ($this->info['audio']['channels'] == 1) { $this->info['audio']['channelmode'] = 'mono'; - } elseif (@$this->info['audio']['channels'] == '2') { + } elseif ($this->info['audio']['channels'] == 2) { $this->info['audio']['channelmode'] = 'stereo'; } @@ -1268,11 +1456,6 @@ class getID3 if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; } -//echo '
    ';
    -//var_dump($this->info['bitrate']);
    -//var_dump($this->info['audio']['bitrate']);
    -//var_dump($this->info['video']['bitrate']);
    -//echo '
    '; if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { // audio only @@ -1357,7 +1540,9 @@ class getID3 function CalculateReplayGain() { if (isset($this->info['replay_gain'])) { - $this->info['replay_gain']['reference_volume'] = 89; + if (!isset($this->info['replay_gain']['reference_volume'])) { + $this->info['replay_gain']['reference_volume'] = (double) 89.0; + } if (isset($this->info['replay_gain']['track']['adjustment'])) { $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; } @@ -1392,6 +1577,168 @@ class getID3 return tempnam($this->tempdir, 'gI3'); } + + public function saveAttachment(&$ThisFileInfoIndex, $filename, $offset, $length) { + try { + if (!getid3_lib::intValueSupported($offset + $length)) { + throw new Exception('cannot extract attachment, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); + } + + // do not extract at all + if ($this->option_save_attachments === getID3::ATTACHMENTS_NONE) { + + unset($ThisFileInfoIndex); // do not set any + + // extract to return array + } elseif ($this->option_save_attachments === getID3::ATTACHMENTS_INLINE) { + + // get whole data in one pass, till it is anyway stored in memory + $ThisFileInfoIndex = file_get_contents($this->info['filenamepath'], false, null, $offset, $length); + if (($ThisFileInfoIndex === false) || (strlen($ThisFileInfoIndex) != $length)) { // verify + throw new Exception('failed to read attachment data'); + } + + // assume directory path is given + } else { + + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->option_save_attachments), DIRECTORY_SEPARATOR); + // check supplied directory + if (!is_dir($dir) || !is_writable($dir)) { + throw new Exception('getID3::saveAttachment() -- supplied path ('.$dir.') does not exist, or is not writable'); + } + + // set up destination path + $dest = $dir.DIRECTORY_SEPARATOR.$filename; + + // optimize speed if read buffer size is configured to be large enough + // here stream_copy_to_stream() may also be used. need to do speed-compare tests + if ($length <= $this->fread_buffer_size()) { + $data = file_get_contents($this->info['filenamepath'], false, null, $offset, $length); + if (($data === false) || (strlen($data) != $length)) { // verify + throw new Exception('failed to read attachment data'); + } + if (!file_put_contents($dest, $data)) { + throw new Exception('failed to create file '.$dest); + } + } else { + // optimization not available - copy data in loop + // here stream_copy_to_stream() shouldn't be used because it's internal read buffer may be larger than ours! + getid3_lib::CopyFileParts($this->info['filenamepath'], $dest, $offset, $length); + } + $ThisFileInfoIndex = $dest; + } + + } catch (Exception $e) { + + unset($ThisFileInfoIndex); // do not set any is case of error + $this->warning('Failed to extract attachment '.$filename.': '.$e->getMessage()); + return false; + + } + return true; + } + + + public function include_module($name) { + //if (!file_exists($this->include_path.'module.'.$name.'.php')) { + if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { + throw new getid3_exception('Required module.'.$name.'.php is missing.'); + } + include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); + return true; + } + +} + + +abstract class getid3_handler +{ + protected $getid3; // pointer + + protected $data_string_flag = false; // analyzing filepointer or string + protected $data_string; // string to analyze + protected $data_string_position = 0; // seek position in string + + + public function __construct(getID3 $getid3) { + $this->getid3 = $getid3; + } + + + // Analyze from file pointer + abstract public function Analyze(); + + + // Analyze from string instead + public function AnalyzeString(&$string) { + // Enter string mode + $this->data_string_flag = true; + $this->data_string = $string; + + // Save info + $saved_avdataoffset = $this->getid3->info['avdataoffset']; + $saved_avdataend = $this->getid3->info['avdataend']; + $saved_filesize = $this->getid3->info['filesize']; + + // Reset some info + $this->getid3->info['avdataoffset'] = 0; + $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = strlen($string); + + // Analyze + $this->Analyze(); + + // Restore some info + $this->getid3->info['avdataoffset'] = $saved_avdataoffset; + $this->getid3->info['avdataend'] = $saved_avdataend; + $this->getid3->info['filesize'] = $saved_filesize; + + // Exit string mode + $this->data_string_flag = false; + } + + + protected function ftell() { + if ($this->data_string_flag) { + return $this->data_string_position; + } + return ftell($this->getid3->fp); + } + + + protected function fread($bytes) { + if ($this->data_string_flag) { + $this->data_string_position += $bytes; + return substr($this->data_string, $this->data_string_position - $bytes, $bytes); + } + return fread($this->getid3->fp, $bytes); + } + + + protected function fseek($bytes, $whence = SEEK_SET) { + if ($this->data_string_flag) { + switch ($whence) { + case SEEK_SET: + $this->data_string_position = $bytes; + return; + + case SEEK_CUR: + $this->data_string_position += $bytes; + return; + + case SEEK_END: + $this->data_string_position = strlen($this->data_string) + $bytes; + return; + } + } + return fseek($this->getid3->fp, $bytes, $whence); + } + +} + + +class getid3_exception extends Exception +{ + public $message; } ?> \ No newline at end of file diff --git a/apps/media/getID3/license.txt b/3rdparty/getid3/license.txt similarity index 100% rename from apps/media/getID3/license.txt rename to 3rdparty/getid3/license.txt diff --git a/apps/media/getID3/getid3/module.archive.gzip.php b/3rdparty/getid3/module.archive.gzip.php similarity index 62% rename from apps/media/getID3/getid3/module.archive.gzip.php rename to 3rdparty/getid3/module.archive.gzip.php index 7e9376f37b..c30052eda4 100644 --- a/apps/media/getID3/getid3/module.archive.gzip.php +++ b/3rdparty/getid3/module.archive.gzip.php @@ -19,21 +19,28 @@ ///////////////////////////////////////////////////////////////// -class getid3_gzip { +class getid3_gzip extends getid3_handler { // public: Optional file list - disable for speed. var $option_gzip_parse_contents = false; // decode gzipped files, if possible, and parse recursively (.tar.gz for example) - function getid3_gzip(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'gzip'; + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'gzip'; $start_length = 10; $unpack_header = 'a1id1/a1id2/a1cmethod/a1flags/a4mtime/a1xflags/a1os'; //+---+---+---+---+---+---+---+---+---+---+ //|ID1|ID2|CM |FLG| MTIME |XFL|OS | //+---+---+---+---+---+---+---+---+---+---+ - @fseek($fd, 0); - $buffer = @fread($fd, $ThisFileInfo['filesize']); + + if ($info['filesize'] > $info['php_memory_limit']) { + $info['error'][] = 'File is too large ('.number_format($info['filesize']).' bytes) to read into memory (limit: '.number_format($info['php_memory_limit'] / 1048576).'MB)'; + return false; + } + fseek($this->getid3->fp, 0); + $buffer = fread($this->getid3->fp, $info['filesize']); $arr_members = explode("\x1F\x8B\x08", $buffer); while (true) { @@ -59,7 +66,7 @@ class getid3_gzip { } } - $ThisFileInfo['gzip']['files'] = array(); + $info['gzip']['files'] = array(); $fpointer = 0; $idx = 0; @@ -67,29 +74,29 @@ class getid3_gzip { if (strlen($arr_members[$i]) == 0) { continue; } - $thisThisFileInfo = &$ThisFileInfo['gzip']['member_header'][++$idx]; + $thisInfo = &$info['gzip']['member_header'][++$idx]; $buff = "\x1F\x8B\x08".$arr_members[$i]; $attr = unpack($unpack_header, substr($buff, 0, $start_length)); - $thisThisFileInfo['filemtime'] = getid3_lib::LittleEndian2Int($attr['mtime']); - $thisThisFileInfo['raw']['id1'] = ord($attr['cmethod']); - $thisThisFileInfo['raw']['id2'] = ord($attr['cmethod']); - $thisThisFileInfo['raw']['cmethod'] = ord($attr['cmethod']); - $thisThisFileInfo['raw']['os'] = ord($attr['os']); - $thisThisFileInfo['raw']['xflags'] = ord($attr['xflags']); - $thisThisFileInfo['raw']['flags'] = ord($attr['flags']); + $thisInfo['filemtime'] = getid3_lib::LittleEndian2Int($attr['mtime']); + $thisInfo['raw']['id1'] = ord($attr['cmethod']); + $thisInfo['raw']['id2'] = ord($attr['cmethod']); + $thisInfo['raw']['cmethod'] = ord($attr['cmethod']); + $thisInfo['raw']['os'] = ord($attr['os']); + $thisInfo['raw']['xflags'] = ord($attr['xflags']); + $thisInfo['raw']['flags'] = ord($attr['flags']); - $thisThisFileInfo['flags']['crc16'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x02); - $thisThisFileInfo['flags']['extra'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x04); - $thisThisFileInfo['flags']['filename'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x08); - $thisThisFileInfo['flags']['comment'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x10); + $thisInfo['flags']['crc16'] = (bool) ($thisInfo['raw']['flags'] & 0x02); + $thisInfo['flags']['extra'] = (bool) ($thisInfo['raw']['flags'] & 0x04); + $thisInfo['flags']['filename'] = (bool) ($thisInfo['raw']['flags'] & 0x08); + $thisInfo['flags']['comment'] = (bool) ($thisInfo['raw']['flags'] & 0x10); - $thisThisFileInfo['compression'] = $this->get_xflag_type($thisThisFileInfo['raw']['xflags']); + $thisInfo['compression'] = $this->get_xflag_type($thisInfo['raw']['xflags']); - $thisThisFileInfo['os'] = $this->get_os_type($thisThisFileInfo['raw']['os']); - if (!$thisThisFileInfo['os']) { - $ThisFileInfo['error'][] = 'Read error on gzip file'; + $thisInfo['os'] = $this->get_os_type($thisInfo['raw']['os']); + if (!$thisInfo['os']) { + $info['error'][] = 'Read error on gzip file'; return false; } @@ -99,12 +106,12 @@ class getid3_gzip { //+---+---+=================================+ //| XLEN |...XLEN bytes of "extra field"...| //+---+---+=================================+ - if ($thisThisFileInfo['flags']['extra']) { + if ($thisInfo['flags']['extra']) { $w_xlen = substr($buff, $fpointer, 2); $xlen = getid3_lib::LittleEndian2Int($w_xlen); $fpointer += 2; - $thisThisFileInfo['raw']['xfield'] = substr($buff, $fpointer, $xlen); + $thisInfo['raw']['xfield'] = substr($buff, $fpointer, $xlen); // Extra SubFields //+---+---+---+---+==================================+ //|SI1|SI2| LEN |... LEN bytes of subfield data ...| @@ -133,14 +140,14 @@ class getid3_gzip { //|...original file name, zero-terminated...| //+=========================================+ // GZIP files may have only one file, with no filename, so assume original filename is current filename without .gz - $thisThisFileInfo['filename'] = eregi_replace('.gz$', '', $ThisFileInfo['filename']); - if ($thisThisFileInfo['flags']['filename']) { + $thisInfo['filename'] = preg_replace('#\.gz$#i', '', $info['filename']); + if ($thisInfo['flags']['filename']) { while (true) { if (ord($buff[$fpointer]) == 0) { $fpointer++; break; } - $thisThisFileInfo['filename'] .= $buff[$fpointer]; + $thisInfo['filename'] .= $buff[$fpointer]; $fpointer++; } } @@ -148,13 +155,13 @@ class getid3_gzip { //+===================================+ //|...file comment, zero-terminated...| //+===================================+ - if ($thisThisFileInfo['flags']['comment']) { + if ($thisInfo['flags']['comment']) { while (true) { if (ord($buff[$fpointer]) == 0) { $fpointer++; break; } - $thisThisFileInfo['comment'] .= $buff[$fpointer]; + $thisInfo['comment'] .= $buff[$fpointer]; $fpointer++; } } @@ -162,21 +169,21 @@ class getid3_gzip { //+---+---+ //| CRC16 | //+---+---+ - if ($thisThisFileInfo['flags']['crc16']) { + if ($thisInfo['flags']['crc16']) { $w_crc = substr($buff, $fpointer, 2); - $thisThisFileInfo['crc16'] = getid3_lib::LittleEndian2Int($w_crc); + $thisInfo['crc16'] = getid3_lib::LittleEndian2Int($w_crc); $fpointer += 2; } // bit 0 - FLG.FTEXT - //if ($thisThisFileInfo['raw']['flags'] & 0x01) { + //if ($thisInfo['raw']['flags'] & 0x01) { // Ignored... //} // bits 5, 6, 7 - reserved - $thisThisFileInfo['crc32'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 8, 4)); - $thisThisFileInfo['filesize'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 4)); + $thisInfo['crc32'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 8, 4)); + $thisInfo['filesize'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 4)); - $ThisFileInfo['gzip']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['gzip']['files'], getid3_lib::CreateDeepArray($thisThisFileInfo['filename'], '/', $thisThisFileInfo['filesize'])); + $info['gzip']['files'] = getid3_lib::array_merge_clobber($info['gzip']['files'], getid3_lib::CreateDeepArray($thisInfo['filename'], '/', $thisInfo['filesize'])); if ($this->option_gzip_parse_contents) { // Try to inflate GZip @@ -190,44 +197,46 @@ class getid3_gzip { $inflated = gzinflate($cdata); // Calculate CRC32 for inflated content - $thisThisFileInfo['crc32_valid'] = (bool) (sprintf('%u', crc32($inflated)) == $thisThisFileInfo['crc32']); + $thisInfo['crc32_valid'] = (bool) (sprintf('%u', crc32($inflated)) == $thisInfo['crc32']); // determine format $formattest = substr($inflated, 0, 32774); - $newgetID3 = new getID3(); - $determined_format = $newgetID3->GetFileFormat($formattest); - unset($newgetID3); + $getid3_temp = new getID3(); + $determined_format = $getid3_temp->GetFileFormat($formattest); + unset($getid3_temp); - // file format is determined - switch (@$determined_format['module']) { - case 'tar': + // file format is determined + $determined_format['module'] = (isset($determined_format['module']) ? $determined_format['module'] : ''); + switch ($determined_format['module']) { + case 'tar': // view TAR-file info - if (file_exists(GETID3_INCLUDEPATH.$determined_format['include']) && @include_once(GETID3_INCLUDEPATH.$determined_format['include'])) { - if (($temp_tar_filename = tempnam('*', 'getID3')) === false) { + if (file_exists(GETID3_INCLUDEPATH.$determined_format['include']) && include_once(GETID3_INCLUDEPATH.$determined_format['include'])) { + if (($temp_tar_filename = tempnam(GETID3_TEMP_DIR, 'getID3')) === false) { // can't find anywhere to create a temp file, abort - $ThisFileInfo['error'][] = 'Unable to create temp file to parse TAR inside GZIP file'; + $info['error'][] = 'Unable to create temp file to parse TAR inside GZIP file'; break; } if ($fp_temp_tar = fopen($temp_tar_filename, 'w+b')) { fwrite($fp_temp_tar, $inflated); - rewind($fp_temp_tar); - $getid3_tar = new getid3_tar($fp_temp_tar, $dummy); - $ThisFileInfo['gzip']['member_header'][$idx]['tar'] = $dummy['tar']; - unset($dummy); - unset($getid3_tar); fclose($fp_temp_tar); + $getid3_temp = new getID3(); + $getid3_temp->openfile($temp_tar_filename); + $getid3_tar = new getid3_tar($getid3_temp); + $getid3_tar->Analyze(); + $info['gzip']['member_header'][$idx]['tar'] = $getid3_temp->info['tar']; + unset($getid3_temp, $getid3_tar); unlink($temp_tar_filename); } else { - $ThisFileInfo['error'][] = 'Unable to fopen() temp file to parse TAR inside GZIP file'; + $info['error'][] = 'Unable to fopen() temp file to parse TAR inside GZIP file'; break; } } break; - case '': - default: - // unknown or unhandled format - break; + case '': + default: + // unknown or unhandled format + break; } } } @@ -254,7 +263,7 @@ class getid3_gzip { '13' => 'Acorn RISCOS', '255' => 'unknown' ); - return @$os_type[$key]; + return (isset($os_type[$key]) ? $os_type[$key] : ''); } // Converts the eXtra FLags @@ -264,7 +273,7 @@ class getid3_gzip { '2' => 'maximum compression', '4' => 'fastest algorithm' ); - return @$xflag_type[$key]; + return (isset($xflag_type[$key]) ? $xflag_type[$key] : ''); } } diff --git a/apps/media/getID3/getid3/module.archive.rar.php b/3rdparty/getid3/module.archive.rar.php similarity index 62% rename from apps/media/getID3/getid3/module.archive.rar.php rename to 3rdparty/getid3/module.archive.rar.php index 59820b2fad..4f5d46f837 100644 --- a/apps/media/getID3/getid3/module.archive.rar.php +++ b/3rdparty/getid3/module.archive.rar.php @@ -14,33 +14,34 @@ ///////////////////////////////////////////////////////////////// -class getid3_rar +class getid3_rar extends getid3_handler { var $option_use_rar_extension = false; - function getid3_rar(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'rar'; + $info['fileformat'] = 'rar'; if ($this->option_use_rar_extension === true) { if (function_exists('rar_open')) { - if ($rp = rar_open($ThisFileInfo['filename'])) { - $ThisFileInfo['rar']['files'] = array(); + if ($rp = rar_open($info['filenamepath'])) { + $info['rar']['files'] = array(); $entries = rar_list($rp); foreach ($entries as $entry) { - $ThisFileInfo['rar']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['rar']['files'], getid3_lib::CreateDeepArray($entry->getName(), '/', $entry->getUnpackedSize())); + $info['rar']['files'] = getid3_lib::array_merge_clobber($info['rar']['files'], getid3_lib::CreateDeepArray($entry->getName(), '/', $entry->getUnpackedSize())); } rar_close($rp); return true; } else { - $ThisFileInfo['error'][] = 'failed to rar_open('.$ThisFileInfo['filename'].')'; + $info['error'][] = 'failed to rar_open('.$info['filename'].')'; } } else { - $ThisFileInfo['error'][] = 'RAR support does not appear to be available in this PHP installation'; + $info['error'][] = 'RAR support does not appear to be available in this PHP installation'; } } else { - $ThisFileInfo['error'][] = 'PHP-RAR processing has been disabled (set $getid3_rar->option_use_rar_extension=true to enable)'; + $info['error'][] = 'PHP-RAR processing has been disabled (set $getid3_rar->option_use_rar_extension=true to enable)'; } return false; diff --git a/apps/media/getID3/getid3/module.archive.szip.php b/3rdparty/getid3/module.archive.szip.php similarity index 75% rename from apps/media/getID3/getid3/module.archive.szip.php rename to 3rdparty/getid3/module.archive.szip.php index 2513c85c7b..3be6253263 100644 --- a/apps/media/getID3/getid3/module.archive.szip.php +++ b/3rdparty/getid3/module.archive.szip.php @@ -14,36 +14,35 @@ ///////////////////////////////////////////////////////////////// -class getid3_szip +class getid3_szip extends getid3_handler { - function getid3_szip(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $SZIPHeader = fread($fd, 6); - if (substr($SZIPHeader, 0, 4) != 'SZ'."\x0A\x04") { - $ThisFileInfo['error'][] = 'Expecting "SZ[x0A][x04]" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($SZIPHeader, 0, 4).'"'; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $SZIPHeader = fread($this->getid3->fp, 6); + if (substr($SZIPHeader, 0, 4) != "SZ\x0A\x04") { + $info['error'][] = 'Expecting "53 5A 0A 04" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($SZIPHeader, 0, 4)).'"'; return false; } + $info['fileformat'] = 'szip'; + $info['szip']['major_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 4, 1)); + $info['szip']['minor_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 5, 1)); - $ThisFileInfo['fileformat'] = 'szip'; - - $ThisFileInfo['szip']['major_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 4, 1)); - $ThisFileInfo['szip']['minor_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 5, 1)); - - while (!feof($fd)) { - $NextBlockID = fread($fd, 2); + while (!feof($this->getid3->fp)) { + $NextBlockID = fread($this->getid3->fp, 2); switch ($NextBlockID) { case 'SZ': // Note that szip files can be concatenated, this has the same effect as // concatenating the files. this also means that global header blocks // might be present between directory/data blocks. - fseek($fd, 4, SEEK_CUR); + fseek($this->getid3->fp, 4, SEEK_CUR); break; case 'BH': - $BHheaderbytes = getid3_lib::BigEndian2Int(fread($fd, 3)); - $BHheaderdata = fread($fd, $BHheaderbytes); + $BHheaderbytes = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 3)); + $BHheaderdata = fread($this->getid3->fp, $BHheaderbytes); $BHheaderoffset = 0; while (strpos($BHheaderdata, "\x00", $BHheaderoffset) > 0) { //filename as \0 terminated string (empty string indicates end) @@ -79,7 +78,7 @@ class getid3_szip $BHdataArray['access_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4)); $BHheaderoffset += 4; - $ThisFileInfo['szip']['BH'][] = $BHdataArray; + $info['szip']['BH'][] = $BHdataArray; } break; diff --git a/apps/media/getID3/getid3/module.archive.tar.php b/3rdparty/getid3/module.archive.tar.php similarity index 71% rename from apps/media/getID3/getid3/module.archive.tar.php rename to 3rdparty/getid3/module.archive.tar.php index aa4390fc9f..94d3203989 100644 --- a/apps/media/getID3/getid3/module.archive.tar.php +++ b/3rdparty/getid3/module.archive.tar.php @@ -19,18 +19,21 @@ ///////////////////////////////////////////////////////////////// -class getid3_tar { +class getid3_tar extends getid3_handler +{ - function getid3_tar(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'tar'; - $ThisFileInfo['tar']['files'] = array(); + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'tar'; + $info['tar']['files'] = array(); $unpack_header = 'a100fname/a8mode/a8uid/a8gid/a12size/a12mtime/a8chksum/a1typflag/a100lnkname/a6magic/a2ver/a32uname/a32gname/a8devmaj/a8devmin/a155prefix'; $null_512k = str_repeat("\x00", 512); // end-of-file marker - @fseek($fd, 0); - while (!feof($fd)) { - $buffer = fread($fd, 512); + fseek($this->getid3->fp, 0); + while (!feof($this->getid3->fp)) { + $buffer = fread($this->getid3->fp, 512); if (strlen($buffer) < 512) { break; } @@ -47,22 +50,22 @@ class getid3_tar { $checksum += ord($buffer{$i}); } $attr = unpack($unpack_header, $buffer); - $name = trim(@$attr['fname']); - $mode = octdec(trim(@$attr['mode'])); - $uid = octdec(trim(@$attr['uid'])); - $gid = octdec(trim(@$attr['gid'])); - $size = octdec(trim(@$attr['size'])); - $mtime = octdec(trim(@$attr['mtime'])); - $chksum = octdec(trim(@$attr['chksum'])); - $typflag = trim(@$attr['typflag']); - $lnkname = trim(@$attr['lnkname']); - $magic = trim(@$attr['magic']); - $ver = trim(@$attr['ver']); - $uname = trim(@$attr['uname']); - $gname = trim(@$attr['gname']); - $devmaj = octdec(trim(@$attr['devmaj'])); - $devmin = octdec(trim(@$attr['devmin'])); - $prefix = trim(@$attr['prefix']); + $name = (isset($attr['fname'] ) ? trim($attr['fname'] ) : ''); + $mode = octdec(isset($attr['mode'] ) ? trim($attr['mode'] ) : ''); + $uid = octdec(isset($attr['uid'] ) ? trim($attr['uid'] ) : ''); + $gid = octdec(isset($attr['gid'] ) ? trim($attr['gid'] ) : ''); + $size = octdec(isset($attr['size'] ) ? trim($attr['size'] ) : ''); + $mtime = octdec(isset($attr['mtime'] ) ? trim($attr['mtime'] ) : ''); + $chksum = octdec(isset($attr['chksum'] ) ? trim($attr['chksum'] ) : ''); + $typflag = (isset($attr['typflag']) ? trim($attr['typflag']) : ''); + $lnkname = (isset($attr['lnkname']) ? trim($attr['lnkname']) : ''); + $magic = (isset($attr['magic'] ) ? trim($attr['magic'] ) : ''); + $ver = (isset($attr['ver'] ) ? trim($attr['ver'] ) : ''); + $uname = (isset($attr['uname'] ) ? trim($attr['uname'] ) : ''); + $gname = (isset($attr['gname'] ) ? trim($attr['gname'] ) : ''); + $devmaj = octdec(isset($attr['devmaj'] ) ? trim($attr['devmaj'] ) : ''); + $devmin = octdec(isset($attr['devmin'] ) ? trim($attr['devmin'] ) : ''); + $prefix = (isset($attr['prefix'] ) ? trim($attr['prefix'] ) : ''); if (($checksum == 256) && ($chksum == 0)) { // EOF Found break; @@ -79,18 +82,18 @@ class getid3_tar { } // Read to the next chunk - fseek($fd, $size, SEEK_CUR); + fseek($this->getid3->fp, $size, SEEK_CUR); $diff = $size % 512; if ($diff != 0) { // Padding, throw away - fseek($fd, (512 - $diff), SEEK_CUR); + fseek($this->getid3->fp, (512 - $diff), SEEK_CUR); } // Protect against tar-files with garbage at the end if ($name == '') { break; } - $ThisFileInfo['tar']['file_details'][$name] = array ( + $info['tar']['file_details'][$name] = array ( 'name' => $name, 'mode_raw' => $mode, 'mode' => getid3_tar::display_perms($mode), @@ -108,7 +111,7 @@ class getid3_tar { 'devmajor' => $devmaj, 'devminor' => $devmin ); - $ThisFileInfo['tar']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['tar']['files'], getid3_lib::CreateDeepArray($ThisFileInfo['tar']['file_details'][$name]['name'], '/', $size)); + $info['tar']['files'] = getid3_lib::array_merge_clobber($info['tar']['files'], getid3_lib::CreateDeepArray($info['tar']['file_details'][$name]['name'], '/', $size)); } return true; } @@ -167,7 +170,7 @@ class getid3_tar { 'S' => 'LF_SPARSE', 'V' => 'LF_VOLHDR' ); - return @$flag_types[$typflag]; + return (isset($flag_types[$typflag]) ? $flag_types[$typflag] : ''); } } diff --git a/apps/media/getID3/getid3/module.archive.zip.php b/3rdparty/getid3/module.archive.zip.php similarity index 66% rename from apps/media/getID3/getid3/module.archive.zip.php rename to 3rdparty/getid3/module.archive.zip.php index 1d2be0691f..7db8fd23f1 100644 --- a/apps/media/getID3/getid3/module.archive.zip.php +++ b/3rdparty/getid3/module.archive.zip.php @@ -14,63 +14,67 @@ ///////////////////////////////////////////////////////////////// -class getid3_zip +class getid3_zip extends getid3_handler { - function getid3_zip(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'zip'; - $ThisFileInfo['zip']['encoding'] = 'ISO-8859-1'; - $ThisFileInfo['zip']['files'] = array(); + $info['fileformat'] = 'zip'; + $info['zip']['encoding'] = 'ISO-8859-1'; + $info['zip']['files'] = array(); - $ThisFileInfo['zip']['compressed_size'] = 0; - $ThisFileInfo['zip']['uncompressed_size'] = 0; - $ThisFileInfo['zip']['entries_count'] = 0; + $info['zip']['compressed_size'] = 0; + $info['zip']['uncompressed_size'] = 0; + $info['zip']['entries_count'] = 0; - if ($ThisFileInfo['filesize'] < pow(2, 31)) { + if (!getid3_lib::intValueSupported($info['filesize'])) { + $info['error'][] = 'File is larger than '.round(PHP_INT_MAX / 1073741824).'GB, not supported by PHP'; + return false; + } else { $EOCDsearchData = ''; $EOCDsearchCounter = 0; while ($EOCDsearchCounter++ < 512) { - fseek($fd, -128 * $EOCDsearchCounter, SEEK_END); - $EOCDsearchData = fread($fd, 128).$EOCDsearchData; + fseek($this->getid3->fp, -128 * $EOCDsearchCounter, SEEK_END); + $EOCDsearchData = fread($this->getid3->fp, 128).$EOCDsearchData; if (strstr($EOCDsearchData, 'PK'."\x05\x06")) { $EOCDposition = strpos($EOCDsearchData, 'PK'."\x05\x06"); - fseek($fd, (-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END); - $ThisFileInfo['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory($fd); + fseek($this->getid3->fp, (-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END); + $info['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory(); - fseek($fd, $ThisFileInfo['zip']['end_central_directory']['directory_offset'], SEEK_SET); - $ThisFileInfo['zip']['entries_count'] = 0; - while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($fd)) { - $ThisFileInfo['zip']['central_directory'][] = $centraldirectoryentry; - $ThisFileInfo['zip']['entries_count']++; - $ThisFileInfo['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; - $ThisFileInfo['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; + fseek($this->getid3->fp, $info['zip']['end_central_directory']['directory_offset'], SEEK_SET); + $info['zip']['entries_count'] = 0; + while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) { + $info['zip']['central_directory'][] = $centraldirectoryentry; + $info['zip']['entries_count']++; + $info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; + $info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; if ($centraldirectoryentry['uncompressed_size'] > 0) { - $ThisFileInfo['zip']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size'])); + $info['zip']['files'] = getid3_lib::array_merge_clobber($info['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size'])); } } - if ($ThisFileInfo['zip']['entries_count'] == 0) { - $ThisFileInfo['error'][] = 'No Central Directory entries found (truncated file?)'; + if ($info['zip']['entries_count'] == 0) { + $info['error'][] = 'No Central Directory entries found (truncated file?)'; return false; } - if (!empty($ThisFileInfo['zip']['end_central_directory']['comment'])) { - $ThisFileInfo['zip']['comments']['comment'][] = $ThisFileInfo['zip']['end_central_directory']['comment']; + if (!empty($info['zip']['end_central_directory']['comment'])) { + $info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment']; } - if (isset($ThisFileInfo['zip']['central_directory'][0]['compression_method'])) { - $ThisFileInfo['zip']['compression_method'] = $ThisFileInfo['zip']['central_directory'][0]['compression_method']; + if (isset($info['zip']['central_directory'][0]['compression_method'])) { + $info['zip']['compression_method'] = $info['zip']['central_directory'][0]['compression_method']; } - if (isset($ThisFileInfo['zip']['central_directory'][0]['flags']['compression_speed'])) { - $ThisFileInfo['zip']['compression_speed'] = $ThisFileInfo['zip']['central_directory'][0]['flags']['compression_speed']; + if (isset($info['zip']['central_directory'][0]['flags']['compression_speed'])) { + $info['zip']['compression_speed'] = $info['zip']['central_directory'][0]['flags']['compression_speed']; } - if (isset($ThisFileInfo['zip']['compression_method']) && ($ThisFileInfo['zip']['compression_method'] == 'store') && !isset($ThisFileInfo['zip']['compression_speed'])) { - $ThisFileInfo['zip']['compression_speed'] = 'store'; + if (isset($info['zip']['compression_method']) && ($info['zip']['compression_method'] == 'store') && !isset($info['zip']['compression_speed'])) { + $info['zip']['compression_speed'] = 'store'; } return true; @@ -79,88 +83,92 @@ class getid3_zip } } - if ($this->getZIPentriesFilepointer($fd, $ThisFileInfo)) { + if ($this->getZIPentriesFilepointer()) { // central directory couldn't be found and/or parsed // scan through actual file data entries, recover as much as possible from probable trucated file - if ($ThisFileInfo['zip']['compressed_size'] > ($ThisFileInfo['filesize'] - 46 - 22)) { - $ThisFileInfo['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$ThisFileInfo['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($ThisFileInfo['filesize'] - 46 - 22).' bytes)'; + if ($info['zip']['compressed_size'] > ($info['filesize'] - 46 - 22)) { + $info['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$info['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($info['filesize'] - 46 - 22).' bytes)'; } - $ThisFileInfo['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete'; - foreach ($ThisFileInfo['zip']['entries'] as $key => $valuearray) { - $ThisFileInfo['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size']; + $info['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete'; + foreach ($info['zip']['entries'] as $key => $valuearray) { + $info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size']; } return true; } else { - unset($ThisFileInfo['zip']); - $ThisFileInfo['fileformat'] = ''; - $ThisFileInfo['error'][] = 'Cannot find End Of Central Directory (truncated file?)'; + unset($info['zip']); + $info['fileformat'] = ''; + $info['error'][] = 'Cannot find End Of Central Directory (truncated file?)'; return false; } } - function getZIPHeaderFilepointerTopDown(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'zip'; + function getZIPHeaderFilepointerTopDown() { + $info = &$this->getid3->info; - $ThisFileInfo['zip']['compressed_size'] = 0; - $ThisFileInfo['zip']['uncompressed_size'] = 0; - $ThisFileInfo['zip']['entries_count'] = 0; + $info['fileformat'] = 'zip'; - rewind($fd); - while ($fileentry = $this->ZIPparseLocalFileHeader($fd)) { - $ThisFileInfo['zip']['entries'][] = $fileentry; - $ThisFileInfo['zip']['entries_count']++; + $info['zip']['compressed_size'] = 0; + $info['zip']['uncompressed_size'] = 0; + $info['zip']['entries_count'] = 0; + + rewind($this->getid3->fp); + while ($fileentry = $this->ZIPparseLocalFileHeader()) { + $info['zip']['entries'][] = $fileentry; + $info['zip']['entries_count']++; } - if ($ThisFileInfo['zip']['entries_count'] == 0) { - $ThisFileInfo['error'][] = 'No Local File Header entries found'; + if ($info['zip']['entries_count'] == 0) { + $info['error'][] = 'No Local File Header entries found'; return false; } - $ThisFileInfo['zip']['entries_count'] = 0; - while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($fd)) { - $ThisFileInfo['zip']['central_directory'][] = $centraldirectoryentry; - $ThisFileInfo['zip']['entries_count']++; - $ThisFileInfo['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; - $ThisFileInfo['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; + $info['zip']['entries_count'] = 0; + while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) { + $info['zip']['central_directory'][] = $centraldirectoryentry; + $info['zip']['entries_count']++; + $info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; + $info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; } - if ($ThisFileInfo['zip']['entries_count'] == 0) { - $ThisFileInfo['error'][] = 'No Central Directory entries found (truncated file?)'; + if ($info['zip']['entries_count'] == 0) { + $info['error'][] = 'No Central Directory entries found (truncated file?)'; return false; } - if ($EOCD = $this->ZIPparseEndOfCentralDirectory($fd)) { - $ThisFileInfo['zip']['end_central_directory'] = $EOCD; + if ($EOCD = $this->ZIPparseEndOfCentralDirectory()) { + $info['zip']['end_central_directory'] = $EOCD; } else { - $ThisFileInfo['error'][] = 'No End Of Central Directory entry found (truncated file?)'; + $info['error'][] = 'No End Of Central Directory entry found (truncated file?)'; return false; } - if (!empty($ThisFileInfo['zip']['end_central_directory']['comment'])) { - $ThisFileInfo['zip']['comments']['comment'][] = $ThisFileInfo['zip']['end_central_directory']['comment']; + if (!empty($info['zip']['end_central_directory']['comment'])) { + $info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment']; } return true; } - function getZIPentriesFilepointer(&$fd, &$ThisFileInfo) { - $ThisFileInfo['zip']['compressed_size'] = 0; - $ThisFileInfo['zip']['uncompressed_size'] = 0; - $ThisFileInfo['zip']['entries_count'] = 0; + function getZIPentriesFilepointer() { + $info = &$this->getid3->info; - rewind($fd); - while ($fileentry = $this->ZIPparseLocalFileHeader($fd)) { - $ThisFileInfo['zip']['entries'][] = $fileentry; - $ThisFileInfo['zip']['entries_count']++; - $ThisFileInfo['zip']['compressed_size'] += $fileentry['compressed_size']; - $ThisFileInfo['zip']['uncompressed_size'] += $fileentry['uncompressed_size']; + $info['zip']['compressed_size'] = 0; + $info['zip']['uncompressed_size'] = 0; + $info['zip']['entries_count'] = 0; + + rewind($this->getid3->fp); + while ($fileentry = $this->ZIPparseLocalFileHeader()) { + $info['zip']['entries'][] = $fileentry; + $info['zip']['entries_count']++; + $info['zip']['compressed_size'] += $fileentry['compressed_size']; + $info['zip']['uncompressed_size'] += $fileentry['uncompressed_size']; } - if ($ThisFileInfo['zip']['entries_count'] == 0) { - $ThisFileInfo['error'][] = 'No Local File Header entries found'; + if ($info['zip']['entries_count'] == 0) { + $info['error'][] = 'No Local File Header entries found'; return false; } @@ -168,15 +176,15 @@ class getid3_zip } - function ZIPparseLocalFileHeader(&$fd) { - $LocalFileHeader['offset'] = ftell($fd); + function ZIPparseLocalFileHeader() { + $LocalFileHeader['offset'] = ftell($this->getid3->fp); - $ZIPlocalFileHeader = fread($fd, 30); + $ZIPlocalFileHeader = fread($this->getid3->fp, 30); $LocalFileHeader['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 0, 4)); if ($LocalFileHeader['raw']['signature'] != 0x04034B50) { // invalid Local File Header Signature - fseek($fd, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + fseek($this->getid3->fp, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly return false; } $LocalFileHeader['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 4, 2)); @@ -200,7 +208,7 @@ class getid3_zip $FilenameExtrafieldLength = $LocalFileHeader['raw']['filename_length'] + $LocalFileHeader['raw']['extra_field_length']; if ($FilenameExtrafieldLength > 0) { - $ZIPlocalFileHeader .= fread($fd, $FilenameExtrafieldLength); + $ZIPlocalFileHeader .= fread($this->getid3->fp, $FilenameExtrafieldLength); if ($LocalFileHeader['raw']['filename_length'] > 0) { $LocalFileHeader['filename'] = substr($ZIPlocalFileHeader, 30, $LocalFileHeader['raw']['filename_length']); @@ -210,12 +218,12 @@ class getid3_zip } } - $LocalFileHeader['data_offset'] = ftell($fd); - //$LocalFileHeader['compressed_data'] = fread($fd, $LocalFileHeader['raw']['compressed_size']); - fseek($fd, $LocalFileHeader['raw']['compressed_size'], SEEK_CUR); + $LocalFileHeader['data_offset'] = ftell($this->getid3->fp); + //$LocalFileHeader['compressed_data'] = fread($this->getid3->fp, $LocalFileHeader['raw']['compressed_size']); + fseek($this->getid3->fp, $LocalFileHeader['raw']['compressed_size'], SEEK_CUR); if ($LocalFileHeader['flags']['data_descriptor_used']) { - $DataDescriptor = fread($fd, 12); + $DataDescriptor = fread($this->getid3->fp, 12); $LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4)); $LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4)); $LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4)); @@ -225,15 +233,15 @@ class getid3_zip } - function ZIPparseCentralDirectory(&$fd) { - $CentralDirectory['offset'] = ftell($fd); + function ZIPparseCentralDirectory() { + $CentralDirectory['offset'] = ftell($this->getid3->fp); - $ZIPcentralDirectory = fread($fd, 46); + $ZIPcentralDirectory = fread($this->getid3->fp, 46); $CentralDirectory['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 0, 4)); if ($CentralDirectory['raw']['signature'] != 0x02014B50) { // invalid Central Directory Signature - fseek($fd, $CentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + fseek($this->getid3->fp, $CentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly return false; } $CentralDirectory['raw']['create_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 4, 2)); @@ -265,7 +273,7 @@ class getid3_zip $FilenameExtrafieldCommentLength = $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'] + $CentralDirectory['raw']['file_comment_length']; if ($FilenameExtrafieldCommentLength > 0) { - $FilenameExtrafieldComment = fread($fd, $FilenameExtrafieldCommentLength); + $FilenameExtrafieldComment = fread($this->getid3->fp, $FilenameExtrafieldCommentLength); if ($CentralDirectory['raw']['filename_length'] > 0) { $CentralDirectory['filename'] = substr($FilenameExtrafieldComment, 0, $CentralDirectory['raw']['filename_length']); @@ -281,15 +289,15 @@ class getid3_zip return $CentralDirectory; } - function ZIPparseEndOfCentralDirectory(&$fd) { - $EndOfCentralDirectory['offset'] = ftell($fd); + function ZIPparseEndOfCentralDirectory() { + $EndOfCentralDirectory['offset'] = ftell($this->getid3->fp); - $ZIPendOfCentralDirectory = fread($fd, 22); + $ZIPendOfCentralDirectory = fread($this->getid3->fp, 22); $EndOfCentralDirectory['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 0, 4)); if ($EndOfCentralDirectory['signature'] != 0x06054B50) { // invalid End Of Central Directory Signature - fseek($fd, $EndOfCentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + fseek($this->getid3->fp, $EndOfCentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly return false; } $EndOfCentralDirectory['disk_number_current'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 4, 2)); @@ -301,14 +309,14 @@ class getid3_zip $EndOfCentralDirectory['comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 20, 2)); if ($EndOfCentralDirectory['comment_length'] > 0) { - $EndOfCentralDirectory['comment'] = fread($fd, $EndOfCentralDirectory['comment_length']); + $EndOfCentralDirectory['comment'] = fread($this->getid3->fp, $EndOfCentralDirectory['comment_length']); } return $EndOfCentralDirectory; } - function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) { + static function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) { $ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001); switch ($compressionmethod) { @@ -341,7 +349,7 @@ class getid3_zip } - function ZIPversionOSLookup($index) { + static function ZIPversionOSLookup($index) { static $ZIPversionOSLookup = array( 0 => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)', 1 => 'Amiga', @@ -366,7 +374,7 @@ class getid3_zip return (isset($ZIPversionOSLookup[$index]) ? $ZIPversionOSLookup[$index] : '[unknown]'); } - function ZIPcompressionMethodLookup($index) { + static function ZIPcompressionMethodLookup($index) { static $ZIPcompressionMethodLookup = array( 0 => 'store', 1 => 'shrink', @@ -384,7 +392,7 @@ class getid3_zip return (isset($ZIPcompressionMethodLookup[$index]) ? $ZIPcompressionMethodLookup[$index] : '[unknown]'); } - function DOStime2UNIXtime($DOSdate, $DOStime) { + static function DOStime2UNIXtime($DOSdate, $DOStime) { // wFatDate // Specifies the MS-DOS date. The date is a packed 16-bit value with the following format: // Bits Contents diff --git a/apps/media/getID3/getid3/module.audio-video.asf.php b/3rdparty/getid3/module.audio-video.asf.php similarity index 69% rename from apps/media/getID3/getid3/module.audio-video.asf.php rename to 3rdparty/getid3/module.audio-video.asf.php index 4526291be2..0bb095f52e 100644 --- a/apps/media/getID3/getid3/module.audio-video.asf.php +++ b/3rdparty/getid3/module.audio-video.asf.php @@ -15,24 +15,29 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); -$GUIDarray = getid3_asf::KnownGUIDs(); -foreach ($GUIDarray as $GUIDname => $hexstringvalue) { - // initialize all GUID constants - define($GUIDname, getid3_asf::GUIDtoBytestring($hexstringvalue)); -} - - - -class getid3_asf +class getid3_asf extends getid3_handler { - function getid3_asf(&$fd, &$ThisFileInfo) { + function __construct(getID3 $getid3) { + parent::__construct($getid3); // extends getid3_handler::__construct() + + // initialize all GUID constants + $GUIDarray = $this->KnownGUIDs(); + foreach ($GUIDarray as $GUIDname => $hexstringvalue) { + if (!defined($GUIDname)) { + define($GUIDname, $this->GUIDtoBytestring($hexstringvalue)); + } + } + } + + function Analyze() { + $info = &$this->getid3->info; // Shortcuts - $thisfile_audio = &$ThisFileInfo['audio']; - $thisfile_video = &$ThisFileInfo['video']; - $ThisFileInfo['asf'] = array(); - $thisfile_asf = &$ThisFileInfo['asf']; + $thisfile_audio = &$info['audio']; + $thisfile_video = &$info['video']; + $info['asf'] = array(); + $thisfile_asf = &$info['asf']; $thisfile_asf['comments'] = array(); $thisfile_asf_comments = &$thisfile_asf['comments']; $thisfile_asf['header_object'] = array(); @@ -59,17 +64,17 @@ class getid3_asf // Reserved1 BYTE 8 // hardcoded: 0x01 // Reserved2 BYTE 8 // hardcoded: 0x02 - $ThisFileInfo['fileformat'] = 'asf'; + $info['fileformat'] = 'asf'; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $HeaderObjectData = fread($fd, 30); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $HeaderObjectData = fread($this->getid3->fp, 30); $thisfile_asf_headerobject['objectid'] = substr($HeaderObjectData, 0, 16); $thisfile_asf_headerobject['objectid_guid'] = $this->BytestringToGUID($thisfile_asf_headerobject['objectid']); if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) { - $ThisFileInfo['warning'][] = 'ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['asf']); + $info['warning'][] = 'ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}'; + unset($info['fileformat']); + unset($info['asf']); return false; break; } @@ -78,11 +83,12 @@ class getid3_asf $thisfile_asf_headerobject['reserved1'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 28, 1)); $thisfile_asf_headerobject['reserved2'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 29, 1)); - $ASFHeaderData = fread($fd, $thisfile_asf_headerobject['objectsize'] - 30); + $NextObjectOffset = ftell($this->getid3->fp); + $ASFHeaderData = fread($this->getid3->fp, $thisfile_asf_headerobject['objectsize'] - 30); $offset = 0; for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) { - $NextObjectGUID = substr($ASFHeaderData, $offset, 16); + $NextObjectGUID = substr($ASFHeaderData, $offset, 16); $offset += 16; $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID); $NextObjectSize = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); @@ -113,6 +119,7 @@ class getid3_asf $thisfile_asf['file_properties_object'] = array(); $thisfile_asf_filepropertiesobject = &$thisfile_asf['file_properties_object']; + $thisfile_asf_filepropertiesobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_filepropertiesobject['objectid'] = $NextObjectGUID; $thisfile_asf_filepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_filepropertiesobject['objectsize'] = $NextObjectSize; @@ -157,10 +164,10 @@ class getid3_asf } else { // broadcast flag NOT set, perform calculations - $ThisFileInfo['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000); + $info['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000); - //$ThisFileInfo['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate']; - $ThisFileInfo['bitrate'] = ((isset($thisfile_asf_filepropertiesobject['filesize']) ? $thisfile_asf_filepropertiesobject['filesize'] : $ThisFileInfo['filesize']) * 8) / $ThisFileInfo['playtime_seconds']; + //$info['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate']; + $info['bitrate'] = ((isset($thisfile_asf_filepropertiesobject['filesize']) ? $thisfile_asf_filepropertiesobject['filesize'] : $info['filesize']) * 8) / $info['playtime_seconds']; } break; @@ -186,6 +193,7 @@ class getid3_asf // stream number isn't known until halfway through decoding the structure, hence it // it is decoded to a temporary variable and then stuck in the appropriate index later + $StreamPropertiesObjectData['offset'] = $NextObjectOffset + $offset; $StreamPropertiesObjectData['objectid'] = $NextObjectGUID; $StreamPropertiesObjectData['objectid_guid'] = $NextObjectGUIDtext; $StreamPropertiesObjectData['objectsize'] = $NextObjectSize; @@ -253,6 +261,7 @@ class getid3_asf $thisfile_asf['header_extension_object'] = array(); $thisfile_asf_headerextensionobject = &$thisfile_asf['header_extension_object']; + $thisfile_asf_headerextensionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_headerextensionobject['objectid'] = $NextObjectGUID; $thisfile_asf_headerextensionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_headerextensionobject['objectsize'] = $NextObjectSize; @@ -260,20 +269,25 @@ class getid3_asf $offset += 16; $thisfile_asf_headerextensionobject['reserved_1_guid'] = $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']); if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) { - $ThisFileInfo['warning'][] = 'header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')'; + $info['warning'][] = 'header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')'; //return false; break; } $thisfile_asf_headerextensionobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) { - $ThisFileInfo['warning'][] = 'header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"'; + $info['warning'][] = 'header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"'; //return false; break; } $thisfile_asf_headerextensionobject['extension_data_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_headerextensionobject['extension_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_headerextensionobject['extension_data_size']); + $unhandled_sections = 0; + $thisfile_asf_headerextensionobject['extension_data_parsed'] = $this->ASF_HeaderExtensionObjectDataParse($thisfile_asf_headerextensionobject['extension_data'], $unhandled_sections); + if ($unhandled_sections === 0) { + unset($thisfile_asf_headerextensionobject['extension_data']); + } $offset += $thisfile_asf_headerextensionobject['extension_data_size']; break; @@ -297,6 +311,7 @@ class getid3_asf $thisfile_asf['codec_list_object'] = array(); $thisfile_asf_codeclistobject = &$thisfile_asf['codec_list_object']; + $thisfile_asf_codeclistobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_codeclistobject['objectid'] = $NextObjectGUID; $thisfile_asf_codeclistobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_codeclistobject['objectsize'] = $NextObjectSize; @@ -304,7 +319,7 @@ class getid3_asf $offset += 16; $thisfile_asf_codeclistobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']); if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) { - $ThisFileInfo['warning'][] = 'codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}'; + $info['warning'][] = 'codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}'; //return false; break; } @@ -334,82 +349,84 @@ class getid3_asf $thisfile_asf_codeclistobject_codecentries_current['information'] = substr($ASFHeaderData, $offset, $CodecInformationLength); $offset += $CodecInformationLength; - if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { - // audio codec + if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { // audio codec + if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) { - $ThisFileInfo['error'][] = '[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-seperated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"'; - return false; - } - list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description'])); - $thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']); + $info['warning'][] = '[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-seperated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"'; + } else { - if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) { - $thisfile_audio['bitrate'] = (int) (trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000); - } - //if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) { - if (!@$thisfile_video['bitrate'] && @$thisfile_audio['bitrate'] && @$ThisFileInfo['bitrate']) { - //$thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate']; - $thisfile_video['bitrate'] = $ThisFileInfo['bitrate'] - $thisfile_audio['bitrate']; - } + list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description'])); + $thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']); - $AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency)); - switch ($AudioCodecFrequency) { - case 8: - case 8000: - $thisfile_audio['sample_rate'] = 8000; - break; - - case 11: - case 11025: - $thisfile_audio['sample_rate'] = 11025; - break; - - case 12: - case 12000: - $thisfile_audio['sample_rate'] = 12000; - break; - - case 16: - case 16000: - $thisfile_audio['sample_rate'] = 16000; - break; - - case 22: - case 22050: - $thisfile_audio['sample_rate'] = 22050; - break; - - case 24: - case 24000: - $thisfile_audio['sample_rate'] = 24000; - break; - - case 32: - case 32000: - $thisfile_audio['sample_rate'] = 32000; - break; - - case 44: - case 441000: - $thisfile_audio['sample_rate'] = 44100; - break; - - case 48: - case 48000: - $thisfile_audio['sample_rate'] = 48000; - break; - - default: - $ThisFileInfo['warning'][] = 'unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')'; - break; - } - - if (!isset($thisfile_audio['channels'])) { - if (strstr($AudioCodecChannels, 'stereo')) { - $thisfile_audio['channels'] = 2; - } elseif (strstr($AudioCodecChannels, 'mono')) { - $thisfile_audio['channels'] = 1; + if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) { + $thisfile_audio['bitrate'] = (int) (trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000); } + //if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) { + if (empty($thisfile_video['bitrate']) && !empty($thisfile_audio['bitrate']) && !empty($info['bitrate'])) { + //$thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate']; + $thisfile_video['bitrate'] = $info['bitrate'] - $thisfile_audio['bitrate']; + } + + $AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency)); + switch ($AudioCodecFrequency) { + case 8: + case 8000: + $thisfile_audio['sample_rate'] = 8000; + break; + + case 11: + case 11025: + $thisfile_audio['sample_rate'] = 11025; + break; + + case 12: + case 12000: + $thisfile_audio['sample_rate'] = 12000; + break; + + case 16: + case 16000: + $thisfile_audio['sample_rate'] = 16000; + break; + + case 22: + case 22050: + $thisfile_audio['sample_rate'] = 22050; + break; + + case 24: + case 24000: + $thisfile_audio['sample_rate'] = 24000; + break; + + case 32: + case 32000: + $thisfile_audio['sample_rate'] = 32000; + break; + + case 44: + case 441000: + $thisfile_audio['sample_rate'] = 44100; + break; + + case 48: + case 48000: + $thisfile_audio['sample_rate'] = 48000; + break; + + default: + $info['warning'][] = 'unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')'; + break; + } + + if (!isset($thisfile_audio['channels'])) { + if (strstr($AudioCodecChannels, 'stereo')) { + $thisfile_audio['channels'] = 2; + } elseif (strstr($AudioCodecChannels, 'mono')) { + $thisfile_audio['channels'] = 1; + } + } + } } } @@ -436,6 +453,7 @@ class getid3_asf $thisfile_asf['script_command_object'] = array(); $thisfile_asf_scriptcommandobject = &$thisfile_asf['script_command_object']; + $thisfile_asf_scriptcommandobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_scriptcommandobject['objectid'] = $NextObjectGUID; $thisfile_asf_scriptcommandobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_scriptcommandobject['objectsize'] = $NextObjectSize; @@ -443,7 +461,7 @@ class getid3_asf $offset += 16; $thisfile_asf_scriptcommandobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']); if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) { - $ThisFileInfo['warning'][] = 'script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}'; + $info['warning'][] = 'script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}'; //return false; break; } @@ -494,6 +512,7 @@ class getid3_asf $thisfile_asf['marker_object'] = array(); $thisfile_asf_markerobject = &$thisfile_asf['marker_object']; + $thisfile_asf_markerobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_markerobject['objectid'] = $NextObjectGUID; $thisfile_asf_markerobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_markerobject['objectsize'] = $NextObjectSize; @@ -501,7 +520,7 @@ class getid3_asf $offset += 16; $thisfile_asf_markerobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_markerobject['reserved']); if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) { - $ThisFileInfo['warning'][] = 'marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}'; + $info['warning'][] = 'marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}'; break; } $thisfile_asf_markerobject['markers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); @@ -509,7 +528,7 @@ class getid3_asf $thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; if ($thisfile_asf_markerobject['reserved_2'] != 0) { - $ThisFileInfo['warning'][] = 'marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"'; + $info['warning'][] = 'marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"'; break; } $thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); @@ -552,6 +571,7 @@ class getid3_asf $thisfile_asf['bitrate_mutual_exclusion_object'] = array(); $thisfile_asf_bitratemutualexclusionobject = &$thisfile_asf['bitrate_mutual_exclusion_object']; + $thisfile_asf_bitratemutualexclusionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_bitratemutualexclusionobject['objectid'] = $NextObjectGUID; $thisfile_asf_bitratemutualexclusionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_bitratemutualexclusionobject['objectsize'] = $NextObjectSize; @@ -559,7 +579,7 @@ class getid3_asf $thisfile_asf_bitratemutualexclusionobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']); $offset += 16; if (($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate) && ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown)) { - $ThisFileInfo['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}'; + $info['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}'; //return false; break; } @@ -584,6 +604,7 @@ class getid3_asf $thisfile_asf['error_correction_object'] = array(); $thisfile_asf_errorcorrectionobject = &$thisfile_asf['error_correction_object']; + $thisfile_asf_errorcorrectionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_errorcorrectionobject['objectid'] = $NextObjectGUID; $thisfile_asf_errorcorrectionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_errorcorrectionobject['objectsize'] = $NextObjectSize; @@ -619,7 +640,7 @@ class getid3_asf break; default: - $ThisFileInfo['warning'][] = 'error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}'; + $info['warning'][] = 'error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}'; //return false; break; } @@ -646,6 +667,7 @@ class getid3_asf $thisfile_asf['content_description_object'] = array(); $thisfile_asf_contentdescriptionobject = &$thisfile_asf['content_description_object']; + $thisfile_asf_contentdescriptionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_contentdescriptionobject['objectid'] = $NextObjectGUID; $thisfile_asf_contentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_contentdescriptionobject['objectsize'] = $NextObjectSize; @@ -701,6 +723,7 @@ class getid3_asf $thisfile_asf['extended_content_description_object'] = array(); $thisfile_asf_extendedcontentdescriptionobject = &$thisfile_asf['extended_content_description_object']; + $thisfile_asf_extendedcontentdescriptionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_extendedcontentdescriptionobject['objectid'] = $NextObjectGUID; $thisfile_asf_extendedcontentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_extendedcontentdescriptionobject['objectsize'] = $NextObjectSize; @@ -709,7 +732,7 @@ class getid3_asf for ($ExtendedContentDescriptorsCounter = 0; $ExtendedContentDescriptorsCounter < $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count']; $ExtendedContentDescriptorsCounter++) { // shortcut $thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter] = array(); - $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current = &$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter]; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current = &$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter]; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['base_offset'] = $offset + 30; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); @@ -741,7 +764,7 @@ class getid3_asf break; default: - $ThisFileInfo['warning'][] = 'extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')'; + $info['warning'][] = 'extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')'; //return false; break; } @@ -749,7 +772,8 @@ class getid3_asf case 'wm/albumartist': case 'artist': - $thisfile_asf_comments['artist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + // Note: not 'artist', that comes from 'author' tag + $thisfile_asf_comments['albumartist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; case 'wm/albumtitle': @@ -762,9 +786,19 @@ class getid3_asf $thisfile_asf_comments['genre'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; + case 'wm/partofset': + $thisfile_asf_comments['partofset'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + break; + case 'wm/tracknumber': case 'tracknumber': - $thisfile_asf_comments['track'] = array(intval($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']))); + // be careful casting to int: casting unicode strings to int gives unexpected results (stops parsing at first non-numeric character) + $thisfile_asf_comments['track'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + foreach ($thisfile_asf_comments['track'] as $key => $value) { + if (preg_match('/^[0-9\x00]+$/', $value)) { + $thisfile_asf_comments['track'][$key] = intval(str_replace("\x00", '', $value)); + } + } break; case 'wm/track': @@ -794,20 +828,20 @@ class getid3_asf case 'id3': // id3v2 module might not be loaded if (class_exists('getid3_id3v2')) { - $tempfile = tempnam('*', 'getID3'); - $tempfilehandle = fopen($tempfile, "wb"); - $tempThisfileInfo = array('encoding'=>$ThisFileInfo['encoding']); + $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); + $tempfilehandle = fopen($tempfile, 'wb'); + $tempThisfileInfo = array('encoding'=>$info['encoding']); fwrite($tempfilehandle, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); fclose($tempfilehandle); - $tempfilehandle = fopen($tempfile, "rb"); - $id3 = new getid3_id3v2($tempfilehandle, $tempThisfileInfo); - unset($id3); - fclose($tempfilehandle); - unlink($tempfile); + $getid3_temp = new getID3(); + $getid3_temp->openfile($tempfile); + $getid3_id3v2 = new getid3_id3v2($getid3_temp); + $getid3_id3v2->Analyze(); + $info['id3v2'] = $getid3_temp->info['id3v2']; + unset($getid3_temp, $getid3_id3v2); - $ThisFileInfo['id3v2'] = $tempThisfileInfo['id3v2']; - unset($tempThisfileInfo); + unlink($tempfile); } break; @@ -817,14 +851,12 @@ class getid3_asf break; case 'wm/picture': - //typedef struct _WMPicture{ - // LPWSTR pwszMIMEType; - // BYTE bPictureType; - // LPWSTR pwszDescription; - // DWORD dwDataLen; - // BYTE* pbData; - //} WM_PICTURE; - + $WMpicture = $this->ASF_WMpicture($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + foreach ($WMpicture as $key => $value) { + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current[$key] = $value; + } + unset($WMpicture); +/* $wm_picture_offset = 0; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 1)); $wm_picture_offset += 1; @@ -850,6 +882,18 @@ class getid3_asf $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'] = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset); unset($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + $imageinfo = array(); + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = ''; + $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], $imageinfo); + unset($imageinfo); + if (!empty($imagechunkcheck)) { + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + } + if (!isset($thisfile_asf_comments['picture'])) { + $thisfile_asf_comments['picture'] = array(); + } + $thisfile_asf_comments['picture'][] = array('data'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], 'image_mime'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime']); +*/ break; default: @@ -885,6 +929,7 @@ class getid3_asf $thisfile_asf['stream_bitrate_properties_object'] = array(); $thisfile_asf_streambitratepropertiesobject = &$thisfile_asf['stream_bitrate_properties_object']; + $thisfile_asf_streambitratepropertiesobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_streambitratepropertiesobject['objectid'] = $NextObjectGUID; $thisfile_asf_streambitratepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_streambitratepropertiesobject['objectsize'] = $NextObjectSize; @@ -910,6 +955,7 @@ class getid3_asf $thisfile_asf['padding_object'] = array(); $thisfile_asf_paddingobject = &$thisfile_asf['padding_object']; + $thisfile_asf_paddingobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_paddingobject['objectid'] = $NextObjectGUID; $thisfile_asf_paddingobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_paddingobject['objectsize'] = $NextObjectSize; @@ -927,9 +973,9 @@ class getid3_asf default: // Implementations shall ignore any standard or non-standard object that they do not know how to handle. if ($this->GUIDname($NextObjectGUIDtext)) { - $ThisFileInfo['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); + $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); } else { - $ThisFileInfo['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); + $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); } $offset += ($NextObjectSize - 16 - 8); break; @@ -1000,16 +1046,16 @@ class getid3_asf if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { - if (@$dataarray['flags']['stream_number'] == $streamnumber) { + if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) { $thisfile_asf_audiomedia_currentstream['bitrate'] = $dataarray['bitrate']; $thisfile_audio['bitrate'] += $dataarray['bitrate']; break; } } } else { - if (@$thisfile_asf_audiomedia_currentstream['bytes_sec']) { + if (!empty($thisfile_asf_audiomedia_currentstream['bytes_sec'])) { $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bytes_sec'] * 8; - } elseif (@$thisfile_asf_audiomedia_currentstream['bitrate']) { + } elseif (!empty($thisfile_asf_audiomedia_currentstream['bitrate'])) { $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bitrate']; } } @@ -1086,7 +1132,7 @@ class getid3_asf if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { - if (@$dataarray['flags']['stream_number'] == $streamnumber) { + if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) { $thisfile_asf_videomedia_currentstream['bitrate'] = $dataarray['bitrate']; $thisfile_video['streams'][$streamnumber]['bitrate'] = $dataarray['bitrate']; $thisfile_video['bitrate'] += $dataarray['bitrate']; @@ -1110,8 +1156,8 @@ class getid3_asf } } - while (ftell($fd) < $ThisFileInfo['avdataend']) { - $NextObjectDataHeader = fread($fd, 24); + while (ftell($this->getid3->fp) < $info['avdataend']) { + $NextObjectDataHeader = fread($this->getid3->fp, 24); $offset = 0; $NextObjectGUID = substr($NextObjectDataHeader, 0, 16); $offset += 16; @@ -1133,7 +1179,7 @@ class getid3_asf $thisfile_asf['data_object'] = array(); $thisfile_asf_dataobject = &$thisfile_asf['data_object']; - $DataObjectData = $NextObjectDataHeader.fread($fd, 50 - 24); + $DataObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 50 - 24); $offset = 24; $thisfile_asf_dataobject['objectid'] = $NextObjectGUID; @@ -1148,7 +1194,7 @@ class getid3_asf $thisfile_asf_dataobject['reserved'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2)); $offset += 2; if ($thisfile_asf_dataobject['reserved'] != 0x0101) { - $ThisFileInfo['warning'][] = 'data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"'; + $info['warning'][] = 'data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"'; //return false; break; } @@ -1161,9 +1207,9 @@ class getid3_asf // * * Error Correction Present bits 1 // If set, use Opaque Data Packet structure, else use Payload structure // * Error Correction Data - $ThisFileInfo['avdataoffset'] = ftell($fd); - fseek($fd, ($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data - $ThisFileInfo['avdataend'] = ftell($fd); + $info['avdataoffset'] = ftell($this->getid3->fp); + fseek($this->getid3->fp, ($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data + $info['avdataend'] = ftell($this->getid3->fp); break; case GETID3_ASF_Simple_Index_Object: @@ -1183,7 +1229,7 @@ class getid3_asf $thisfile_asf['simple_index_object'] = array(); $thisfile_asf_simpleindexobject = &$thisfile_asf['simple_index_object']; - $SimpleIndexObjectData = $NextObjectDataHeader.fread($fd, 56 - 24); + $SimpleIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 56 - 24); $offset = 24; $thisfile_asf_simpleindexobject['objectid'] = $NextObjectGUID; @@ -1200,7 +1246,7 @@ class getid3_asf $thisfile_asf_simpleindexobject['index_entries_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); $offset += 4; - $IndexEntriesData = $SimpleIndexObjectData.fread($fd, 6 * $thisfile_asf_simpleindexobject['index_entries_count']); + $IndexEntriesData = $SimpleIndexObjectData.fread($this->getid3->fp, 6 * $thisfile_asf_simpleindexobject['index_entries_count']); for ($IndexEntriesCounter = 0; $IndexEntriesCounter < $thisfile_asf_simpleindexobject['index_entries_count']; $IndexEntriesCounter++) { $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_number'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); $offset += 4; @@ -1237,7 +1283,7 @@ class getid3_asf $thisfile_asf['asf_index_object'] = array(); $thisfile_asf_asfindexobject = &$thisfile_asf['asf_index_object']; - $ASFIndexObjectData = $NextObjectDataHeader.fread($fd, 34 - 24); + $ASFIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 34 - 24); $offset = 24; $thisfile_asf_asfindexobject['objectid'] = $NextObjectGUID; @@ -1251,7 +1297,7 @@ class getid3_asf $thisfile_asf_asfindexobject['index_blocks_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; - $ASFIndexObjectData .= fread($fd, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']); + $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']); for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $IndexSpecifierStreamNumber = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); $offset += 2; @@ -1261,17 +1307,17 @@ class getid3_asf $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type_text'] = $this->ASFIndexObjectIndexTypeLookup($thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']); } - $ASFIndexObjectData .= fread($fd, 4); + $ASFIndexObjectData .= fread($this->getid3->fp, 4); $thisfile_asf_asfindexobject['index_entry_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; - $ASFIndexObjectData .= fread($fd, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']); + $ASFIndexObjectData .= fread($this->getid3->fp, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']); for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $thisfile_asf_asfindexobject['block_positions'][$IndexSpecifiersCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 8)); $offset += 8; } - $ASFIndexObjectData .= fread($fd, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); + $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); for ($IndexEntryCounter = 0; $IndexEntryCounter < $thisfile_asf_asfindexobject['index_entry_count']; $IndexEntryCounter++) { for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $thisfile_asf_asfindexobject['offsets'][$IndexSpecifiersCounter][$IndexEntryCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); @@ -1284,11 +1330,11 @@ class getid3_asf default: // Implementations shall ignore any standard or non-standard object that they do not know how to handle. if ($this->GUIDname($NextObjectGUIDtext)) { - $ThisFileInfo['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8); + $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8); } else { - $ThisFileInfo['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.(ftell($fd) - 16 - 8); + $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.(ftell($this->getid3->fp) - 16 - 8); } - fseek($fd, ($NextObjectSize - 16 - 8), SEEK_CUR); + fseek($this->getid3->fp, ($NextObjectSize - 16 - 8), SEEK_CUR); break; } } @@ -1299,14 +1345,14 @@ class getid3_asf case 'WMV1': case 'WMV2': case 'WMV3': - case 'MSS1': - case 'MSS2': - case 'WMVA': - case 'WVC1': - case 'WMVP': - case 'WVP2': + case 'MSS1': + case 'MSS2': + case 'WMVA': + case 'WVC1': + case 'WMVP': + case 'WVP2': $thisfile_video['dataformat'] = 'wmv'; - $ThisFileInfo['mime_type'] = 'video/x-ms-wmv'; + $info['mime_type'] = 'video/x-ms-wmv'; break; case 'MP42': @@ -1314,7 +1360,7 @@ class getid3_asf case 'MP4S': case 'mp4s': $thisfile_video['dataformat'] = 'asf'; - $ThisFileInfo['mime_type'] = 'video/x-ms-asf'; + $info['mime_type'] = 'video/x-ms-asf'; break; default: @@ -1322,8 +1368,8 @@ class getid3_asf case 1: if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { $thisfile_video['dataformat'] = 'wmv'; - if ($ThisFileInfo['mime_type'] == 'video/x-ms-asf') { - $ThisFileInfo['mime_type'] = 'video/x-ms-wmv'; + if ($info['mime_type'] == 'video/x-ms-asf') { + $info['mime_type'] = 'video/x-ms-wmv'; } } break; @@ -1331,8 +1377,8 @@ class getid3_asf case 2: if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { $thisfile_audio['dataformat'] = 'wma'; - if ($ThisFileInfo['mime_type'] == 'video/x-ms-asf') { - $ThisFileInfo['mime_type'] = 'audio/x-ms-wma'; + if ($info['mime_type'] == 'video/x-ms-asf') { + $info['mime_type'] = 'audio/x-ms-wma'; } } break; @@ -1343,7 +1389,7 @@ class getid3_asf } } - switch (@$thisfile_audio['codec']) { + switch (isset($thisfile_audio['codec']) ? $thisfile_audio['codec'] : '') { case 'MPEG Layer-3': $thisfile_audio['dataformat'] = 'mp3'; break; @@ -1370,14 +1416,14 @@ class getid3_asf break; default: - $ThisFileInfo['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw']; + $info['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw']; break; } } } - if (isset($ThisFileInfo['audio'])) { + if (isset($info['audio'])) { $thisfile_audio['lossless'] = (isset($thisfile_audio['lossless']) ? $thisfile_audio['lossless'] : false); $thisfile_audio['dataformat'] = (!empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'); } @@ -1396,16 +1442,16 @@ class getid3_asf } } } - $ThisFileInfo['bitrate'] = @$thisfile_audio['bitrate'] + @$thisfile_video['bitrate']; + $info['bitrate'] = (isset($thisfile_audio['bitrate']) ? $thisfile_audio['bitrate'] : 0) + (isset($thisfile_video['bitrate']) ? $thisfile_video['bitrate'] : 0); - if ((!isset($ThisFileInfo['playtime_seconds']) || ($ThisFileInfo['playtime_seconds'] <= 0)) && ($ThisFileInfo['bitrate'] > 0)) { - $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['filesize'] - $ThisFileInfo['avdataoffset']) / ($ThisFileInfo['bitrate'] / 8); + if ((!isset($info['playtime_seconds']) || ($info['playtime_seconds'] <= 0)) && ($info['bitrate'] > 0)) { + $info['playtime_seconds'] = ($info['filesize'] - $info['avdataoffset']) / ($info['bitrate'] / 8); } return true; } - function ASFCodecListObjectTypeLookup($CodecListType) { + static function ASFCodecListObjectTypeLookup($CodecListType) { static $ASFCodecListObjectTypeLookup = array(); if (empty($ASFCodecListObjectTypeLookup)) { $ASFCodecListObjectTypeLookup[0x0001] = 'Video Codec'; @@ -1416,128 +1462,129 @@ class getid3_asf return (isset($ASFCodecListObjectTypeLookup[$CodecListType]) ? $ASFCodecListObjectTypeLookup[$CodecListType] : 'Invalid Codec Type'); } - function KnownGUIDs() { - static $GUIDarray = array(); - if (empty($GUIDarray)) { - $GUIDarray['GETID3_ASF_Extended_Stream_Properties_Object'] = '14E6A5CB-C672-4332-8399-A96952065B5A'; - $GUIDarray['GETID3_ASF_Padding_Object'] = '1806D474-CADF-4509-A4BA-9AABCB96AAE8'; - $GUIDarray['GETID3_ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio'] = '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8'; - $GUIDarray['GETID3_ASF_Script_Command_Object'] = '1EFB1A30-0B62-11D0-A39B-00A0C90348F6'; - $GUIDarray['GETID3_ASF_No_Error_Correction'] = '20FB5700-5B55-11CF-A8FD-00805F5C442B'; - $GUIDarray['GETID3_ASF_Content_Branding_Object'] = '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E'; - $GUIDarray['GETID3_ASF_Content_Encryption_Object'] = '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E'; - $GUIDarray['GETID3_ASF_Digital_Signature_Object'] = '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E'; - $GUIDarray['GETID3_ASF_Extended_Content_Encryption_Object'] = '298AE614-2622-4C17-B935-DAE07EE9289C'; - $GUIDarray['GETID3_ASF_Simple_Index_Object'] = '33000890-E5B1-11CF-89F4-00A0C90349CB'; - $GUIDarray['GETID3_ASF_Degradable_JPEG_Media'] = '35907DE0-E415-11CF-A917-00805F5C442B'; - $GUIDarray['GETID3_ASF_Payload_Extension_System_Timecode'] = '399595EC-8667-4E2D-8FDB-98814CE76C1E'; - $GUIDarray['GETID3_ASF_Binary_Media'] = '3AFB65E2-47EF-40F2-AC2C-70A90D71D343'; - $GUIDarray['GETID3_ASF_Timecode_Index_Object'] = '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C'; - $GUIDarray['GETID3_ASF_Metadata_Library_Object'] = '44231C94-9498-49D1-A141-1D134E457054'; - $GUIDarray['GETID3_ASF_Reserved_3'] = '4B1ACBE3-100B-11D0-A39B-00A0C90348F6'; - $GUIDarray['GETID3_ASF_Reserved_4'] = '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB'; - $GUIDarray['GETID3_ASF_Command_Media'] = '59DACFC0-59E6-11D0-A3AC-00A0C90348F6'; - $GUIDarray['GETID3_ASF_Header_Extension_Object'] = '5FBF03B5-A92E-11CF-8EE3-00C00C205365'; - $GUIDarray['GETID3_ASF_Media_Object_Index_Parameters_Obj'] = '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7'; - $GUIDarray['GETID3_ASF_Header_Object'] = '75B22630-668E-11CF-A6D9-00AA0062CE6C'; - $GUIDarray['GETID3_ASF_Content_Description_Object'] = '75B22633-668E-11CF-A6D9-00AA0062CE6C'; - $GUIDarray['GETID3_ASF_Error_Correction_Object'] = '75B22635-668E-11CF-A6D9-00AA0062CE6C'; - $GUIDarray['GETID3_ASF_Data_Object'] = '75B22636-668E-11CF-A6D9-00AA0062CE6C'; - $GUIDarray['GETID3_ASF_Web_Stream_Media_Subtype'] = '776257D4-C627-41CB-8F81-7AC7FF1C40CC'; - $GUIDarray['GETID3_ASF_Stream_Bitrate_Properties_Object'] = '7BF875CE-468D-11D1-8D82-006097C9A2B2'; - $GUIDarray['GETID3_ASF_Language_List_Object'] = '7C4346A9-EFE0-4BFC-B229-393EDE415C85'; - $GUIDarray['GETID3_ASF_Codec_List_Object'] = '86D15240-311D-11D0-A3A4-00A0C90348F6'; - $GUIDarray['GETID3_ASF_Reserved_2'] = '86D15241-311D-11D0-A3A4-00A0C90348F6'; - $GUIDarray['GETID3_ASF_File_Properties_Object'] = '8CABDCA1-A947-11CF-8EE4-00C00C205365'; - $GUIDarray['GETID3_ASF_File_Transfer_Media'] = '91BD222C-F21C-497A-8B6D-5AA86BFC0185'; - $GUIDarray['GETID3_ASF_Old_RTP_Extension_Data'] = '96800C63-4C94-11D1-837B-0080C7A37F95'; - $GUIDarray['GETID3_ASF_Advanced_Mutual_Exclusion_Object'] = 'A08649CF-4775-4670-8A16-6E35357566CD'; - $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Object'] = 'A69609E6-517B-11D2-B6AF-00C04FD908E9'; - $GUIDarray['GETID3_ASF_Reserved_1'] = 'ABD3D211-A9BA-11cf-8EE6-00C00C205365'; - $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Exclusive'] = 'AF6060AA-5197-11D2-B6AF-00C04FD908E9'; - $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Partial'] = 'AF6060AB-5197-11D2-B6AF-00C04FD908E9'; - $GUIDarray['GETID3_ASF_JFIF_Media'] = 'B61BE100-5B4E-11CF-A8FD-00805F5C442B'; - $GUIDarray['GETID3_ASF_Stream_Properties_Object'] = 'B7DC0791-A9B7-11CF-8EE6-00C00C205365'; - $GUIDarray['GETID3_ASF_Video_Media'] = 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B'; - $GUIDarray['GETID3_ASF_Audio_Spread'] = 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220'; - $GUIDarray['GETID3_ASF_Metadata_Object'] = 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA'; - $GUIDarray['GETID3_ASF_Payload_Ext_Syst_Sample_Duration'] = 'C6BD9450-867F-4907-83A3-C77921B733AD'; - $GUIDarray['GETID3_ASF_Group_Mutual_Exclusion_Object'] = 'D1465A40-5A79-4338-B71B-E36B8FD6C249'; - $GUIDarray['GETID3_ASF_Extended_Content_Description_Object'] = 'D2D0A440-E307-11D2-97F0-00A0C95EA850'; - $GUIDarray['GETID3_ASF_Stream_Prioritization_Object'] = 'D4FED15B-88D3-454F-81F0-ED5C45999E24'; - $GUIDarray['GETID3_ASF_Payload_Ext_System_Content_Type'] = 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC'; - $GUIDarray['GETID3_ASF_Old_File_Properties_Object'] = 'D6E229D0-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ASF_Header_Object'] = 'D6E229D1-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ASF_Data_Object'] = 'D6E229D2-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Index_Object'] = 'D6E229D3-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Stream_Properties_Object'] = 'D6E229D4-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Content_Description_Object'] = 'D6E229D5-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Script_Command_Object'] = 'D6E229D6-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Marker_Object'] = 'D6E229D7-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Component_Download_Object'] = 'D6E229D8-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Stream_Group_Object'] = 'D6E229D9-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Scalable_Object'] = 'D6E229DA-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Prioritization_Object'] = 'D6E229DB-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Bitrate_Mutual_Exclusion_Object'] = 'D6E229DC-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Inter_Media_Dependency_Object'] = 'D6E229DD-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Rating_Object'] = 'D6E229DE-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Index_Parameters_Object'] = 'D6E229DF-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Color_Table_Object'] = 'D6E229E0-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Language_List_Object'] = 'D6E229E1-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Audio_Media'] = 'D6E229E2-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Video_Media'] = 'D6E229E3-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Image_Media'] = 'D6E229E4-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Timecode_Media'] = 'D6E229E5-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Text_Media'] = 'D6E229E6-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_MIDI_Media'] = 'D6E229E7-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Command_Media'] = 'D6E229E8-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_No_Error_Concealment'] = 'D6E229EA-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Scrambled_Audio'] = 'D6E229EB-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_No_Color_Table'] = 'D6E229EC-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_SMPTE_Time'] = 'D6E229ED-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ASCII_Text'] = 'D6E229EE-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Unicode_Text'] = 'D6E229EF-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_HTML_Text'] = 'D6E229F0-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_URL_Command'] = 'D6E229F1-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Filename_Command'] = 'D6E229F2-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ACM_Codec'] = 'D6E229F3-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_VCM_Codec'] = 'D6E229F4-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_QuickTime_Codec'] = 'D6E229F5-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_DirectShow_Transform_Filter'] = 'D6E229F6-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_DirectShow_Rendering_Filter'] = 'D6E229F7-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_No_Enhancement'] = 'D6E229F8-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Unknown_Enhancement_Type'] = 'D6E229F9-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Temporal_Enhancement'] = 'D6E229FA-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Spatial_Enhancement'] = 'D6E229FB-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Quality_Enhancement'] = 'D6E229FC-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Number_of_Channels_Enhancement'] = 'D6E229FD-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Frequency_Response_Enhancement'] = 'D6E229FE-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Media_Object'] = 'D6E229FF-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Mutex_Language'] = 'D6E22A00-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Mutex_Bitrate'] = 'D6E22A01-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Mutex_Unknown'] = 'D6E22A02-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_ASF_Placeholder_Object'] = 'D6E22A0E-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Old_Data_Unit_Extension_Object'] = 'D6E22A0F-35DA-11D1-9034-00A0C90349BE'; - $GUIDarray['GETID3_ASF_Web_Stream_Format'] = 'DA1E6B13-8359-4050-B398-388E965BF00C'; - $GUIDarray['GETID3_ASF_Payload_Ext_System_File_Name'] = 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B'; - $GUIDarray['GETID3_ASF_Marker_Object'] = 'F487CD01-A951-11CF-8EE6-00C00C205365'; - $GUIDarray['GETID3_ASF_Timecode_Index_Parameters_Object'] = 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24'; - $GUIDarray['GETID3_ASF_Audio_Media'] = 'F8699E40-5B4D-11CF-A8FD-00805F5C442B'; - $GUIDarray['GETID3_ASF_Media_Object_Index_Object'] = 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C'; - $GUIDarray['GETID3_ASF_Alt_Extended_Content_Encryption_Obj'] = 'FF889EF1-ADEE-40DA-9E71-98704BB928CE'; - } + static function KnownGUIDs() { + static $GUIDarray = array( + 'GETID3_ASF_Extended_Stream_Properties_Object' => '14E6A5CB-C672-4332-8399-A96952065B5A', + 'GETID3_ASF_Padding_Object' => '1806D474-CADF-4509-A4BA-9AABCB96AAE8', + 'GETID3_ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio' => '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8', + 'GETID3_ASF_Script_Command_Object' => '1EFB1A30-0B62-11D0-A39B-00A0C90348F6', + 'GETID3_ASF_No_Error_Correction' => '20FB5700-5B55-11CF-A8FD-00805F5C442B', + 'GETID3_ASF_Content_Branding_Object' => '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E', + 'GETID3_ASF_Content_Encryption_Object' => '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E', + 'GETID3_ASF_Digital_Signature_Object' => '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E', + 'GETID3_ASF_Extended_Content_Encryption_Object' => '298AE614-2622-4C17-B935-DAE07EE9289C', + 'GETID3_ASF_Simple_Index_Object' => '33000890-E5B1-11CF-89F4-00A0C90349CB', + 'GETID3_ASF_Degradable_JPEG_Media' => '35907DE0-E415-11CF-A917-00805F5C442B', + 'GETID3_ASF_Payload_Extension_System_Timecode' => '399595EC-8667-4E2D-8FDB-98814CE76C1E', + 'GETID3_ASF_Binary_Media' => '3AFB65E2-47EF-40F2-AC2C-70A90D71D343', + 'GETID3_ASF_Timecode_Index_Object' => '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C', + 'GETID3_ASF_Metadata_Library_Object' => '44231C94-9498-49D1-A141-1D134E457054', + 'GETID3_ASF_Reserved_3' => '4B1ACBE3-100B-11D0-A39B-00A0C90348F6', + 'GETID3_ASF_Reserved_4' => '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB', + 'GETID3_ASF_Command_Media' => '59DACFC0-59E6-11D0-A3AC-00A0C90348F6', + 'GETID3_ASF_Header_Extension_Object' => '5FBF03B5-A92E-11CF-8EE3-00C00C205365', + 'GETID3_ASF_Media_Object_Index_Parameters_Obj' => '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7', + 'GETID3_ASF_Header_Object' => '75B22630-668E-11CF-A6D9-00AA0062CE6C', + 'GETID3_ASF_Content_Description_Object' => '75B22633-668E-11CF-A6D9-00AA0062CE6C', + 'GETID3_ASF_Error_Correction_Object' => '75B22635-668E-11CF-A6D9-00AA0062CE6C', + 'GETID3_ASF_Data_Object' => '75B22636-668E-11CF-A6D9-00AA0062CE6C', + 'GETID3_ASF_Web_Stream_Media_Subtype' => '776257D4-C627-41CB-8F81-7AC7FF1C40CC', + 'GETID3_ASF_Stream_Bitrate_Properties_Object' => '7BF875CE-468D-11D1-8D82-006097C9A2B2', + 'GETID3_ASF_Language_List_Object' => '7C4346A9-EFE0-4BFC-B229-393EDE415C85', + 'GETID3_ASF_Codec_List_Object' => '86D15240-311D-11D0-A3A4-00A0C90348F6', + 'GETID3_ASF_Reserved_2' => '86D15241-311D-11D0-A3A4-00A0C90348F6', + 'GETID3_ASF_File_Properties_Object' => '8CABDCA1-A947-11CF-8EE4-00C00C205365', + 'GETID3_ASF_File_Transfer_Media' => '91BD222C-F21C-497A-8B6D-5AA86BFC0185', + 'GETID3_ASF_Old_RTP_Extension_Data' => '96800C63-4C94-11D1-837B-0080C7A37F95', + 'GETID3_ASF_Advanced_Mutual_Exclusion_Object' => 'A08649CF-4775-4670-8A16-6E35357566CD', + 'GETID3_ASF_Bandwidth_Sharing_Object' => 'A69609E6-517B-11D2-B6AF-00C04FD908E9', + 'GETID3_ASF_Reserved_1' => 'ABD3D211-A9BA-11cf-8EE6-00C00C205365', + 'GETID3_ASF_Bandwidth_Sharing_Exclusive' => 'AF6060AA-5197-11D2-B6AF-00C04FD908E9', + 'GETID3_ASF_Bandwidth_Sharing_Partial' => 'AF6060AB-5197-11D2-B6AF-00C04FD908E9', + 'GETID3_ASF_JFIF_Media' => 'B61BE100-5B4E-11CF-A8FD-00805F5C442B', + 'GETID3_ASF_Stream_Properties_Object' => 'B7DC0791-A9B7-11CF-8EE6-00C00C205365', + 'GETID3_ASF_Video_Media' => 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B', + 'GETID3_ASF_Audio_Spread' => 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220', + 'GETID3_ASF_Metadata_Object' => 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA', + 'GETID3_ASF_Payload_Ext_Syst_Sample_Duration' => 'C6BD9450-867F-4907-83A3-C77921B733AD', + 'GETID3_ASF_Group_Mutual_Exclusion_Object' => 'D1465A40-5A79-4338-B71B-E36B8FD6C249', + 'GETID3_ASF_Extended_Content_Description_Object' => 'D2D0A440-E307-11D2-97F0-00A0C95EA850', + 'GETID3_ASF_Stream_Prioritization_Object' => 'D4FED15B-88D3-454F-81F0-ED5C45999E24', + 'GETID3_ASF_Payload_Ext_System_Content_Type' => 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC', + 'GETID3_ASF_Old_File_Properties_Object' => 'D6E229D0-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ASF_Header_Object' => 'D6E229D1-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ASF_Data_Object' => 'D6E229D2-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Index_Object' => 'D6E229D3-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Stream_Properties_Object' => 'D6E229D4-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Content_Description_Object' => 'D6E229D5-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Script_Command_Object' => 'D6E229D6-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Marker_Object' => 'D6E229D7-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Component_Download_Object' => 'D6E229D8-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Stream_Group_Object' => 'D6E229D9-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Scalable_Object' => 'D6E229DA-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Prioritization_Object' => 'D6E229DB-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Bitrate_Mutual_Exclusion_Object' => 'D6E229DC-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Inter_Media_Dependency_Object' => 'D6E229DD-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Rating_Object' => 'D6E229DE-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Index_Parameters_Object' => 'D6E229DF-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Color_Table_Object' => 'D6E229E0-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Language_List_Object' => 'D6E229E1-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Audio_Media' => 'D6E229E2-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Video_Media' => 'D6E229E3-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Image_Media' => 'D6E229E4-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Timecode_Media' => 'D6E229E5-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Text_Media' => 'D6E229E6-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_MIDI_Media' => 'D6E229E7-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Command_Media' => 'D6E229E8-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_No_Error_Concealment' => 'D6E229EA-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Scrambled_Audio' => 'D6E229EB-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_No_Color_Table' => 'D6E229EC-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_SMPTE_Time' => 'D6E229ED-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ASCII_Text' => 'D6E229EE-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Unicode_Text' => 'D6E229EF-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_HTML_Text' => 'D6E229F0-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_URL_Command' => 'D6E229F1-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Filename_Command' => 'D6E229F2-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ACM_Codec' => 'D6E229F3-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_VCM_Codec' => 'D6E229F4-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_QuickTime_Codec' => 'D6E229F5-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_DirectShow_Transform_Filter' => 'D6E229F6-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_DirectShow_Rendering_Filter' => 'D6E229F7-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_No_Enhancement' => 'D6E229F8-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Unknown_Enhancement_Type' => 'D6E229F9-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Temporal_Enhancement' => 'D6E229FA-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Spatial_Enhancement' => 'D6E229FB-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Quality_Enhancement' => 'D6E229FC-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Number_of_Channels_Enhancement' => 'D6E229FD-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Frequency_Response_Enhancement' => 'D6E229FE-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Media_Object' => 'D6E229FF-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Mutex_Language' => 'D6E22A00-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Mutex_Bitrate' => 'D6E22A01-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Mutex_Unknown' => 'D6E22A02-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_ASF_Placeholder_Object' => 'D6E22A0E-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Old_Data_Unit_Extension_Object' => 'D6E22A0F-35DA-11D1-9034-00A0C90349BE', + 'GETID3_ASF_Web_Stream_Format' => 'DA1E6B13-8359-4050-B398-388E965BF00C', + 'GETID3_ASF_Payload_Ext_System_File_Name' => 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B', + 'GETID3_ASF_Marker_Object' => 'F487CD01-A951-11CF-8EE6-00C00C205365', + 'GETID3_ASF_Timecode_Index_Parameters_Object' => 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24', + 'GETID3_ASF_Audio_Media' => 'F8699E40-5B4D-11CF-A8FD-00805F5C442B', + 'GETID3_ASF_Media_Object_Index_Object' => 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C', + 'GETID3_ASF_Alt_Extended_Content_Encryption_Obj' => 'FF889EF1-ADEE-40DA-9E71-98704BB928CE', + 'GETID3_ASF_Index_Placeholder_Object' => 'D9AADE20-7C17-4F9C-BC28-8555DD98E2A2', // http://cpan.uwinnipeg.ca/htdocs/Audio-WMA/Audio/WMA.pm.html + 'GETID3_ASF_Compatibility_Object' => '26F18B5D-4584-47EC-9F5F-0E651F0452C9', // http://cpan.uwinnipeg.ca/htdocs/Audio-WMA/Audio/WMA.pm.html + ); return $GUIDarray; } - function GUIDname($GUIDstring) { + static function GUIDname($GUIDstring) { static $GUIDarray = array(); if (empty($GUIDarray)) { - $GUIDarray = $this->KnownGUIDs(); + $GUIDarray = getid3_asf::KnownGUIDs(); } return array_search($GUIDstring, $GUIDarray); } - function ASFIndexObjectIndexTypeLookup($id) { + static function ASFIndexObjectIndexTypeLookup($id) { static $ASFIndexObjectIndexTypeLookup = array(); if (empty($ASFIndexObjectIndexTypeLookup)) { $ASFIndexObjectIndexTypeLookup[1] = 'Nearest Past Data Packet'; @@ -1547,7 +1594,7 @@ class getid3_asf return (isset($ASFIndexObjectIndexTypeLookup[$id]) ? $ASFIndexObjectIndexTypeLookup[$id] : 'invalid'); } - function GUIDtoBytestring($GUIDstring) { + static function GUIDtoBytestring($GUIDstring) { // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way: // first 4 bytes are in little-endian order // next 2 bytes are appended in little-endian order @@ -1582,7 +1629,7 @@ class getid3_asf return $hexbytecharstring; } - function BytestringToGUID($Bytestring) { + static function BytestringToGUID($Bytestring) { $GUIDstring = str_pad(dechex(ord($Bytestring{3})), 2, '0', STR_PAD_LEFT); $GUIDstring .= str_pad(dechex(ord($Bytestring{2})), 2, '0', STR_PAD_LEFT); $GUIDstring .= str_pad(dechex(ord($Bytestring{1})), 2, '0', STR_PAD_LEFT); @@ -1607,7 +1654,7 @@ class getid3_asf return strtoupper($GUIDstring); } - function FILETIMEtoUNIXtime($FILETIME, $round=true) { + static function FILETIMEtoUNIXtime($FILETIME, $round=true) { // FILETIME is a 64-bit unsigned integer representing // the number of 100-nanosecond intervals since January 1, 1601 // UNIX timestamp is number of seconds since January 1, 1970 @@ -1618,7 +1665,7 @@ class getid3_asf return ($FILETIME - 116444736000000000) / 10000000; } - function WMpictureTypeLookup($WMpictureType) { + static function WMpictureTypeLookup($WMpictureType) { static $WMpictureTypeLookup = array(); if (empty($WMpictureTypeLookup)) { $WMpictureTypeLookup[0x03] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Front Cover'); @@ -1640,34 +1687,335 @@ class getid3_asf $WMpictureTypeLookup[0x13] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band Logotype'); $WMpictureTypeLookup[0x14] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Publisher Logotype'); } - return @$WMpictureTypeLookup[$WMpictureType]; + return (isset($WMpictureTypeLookup[$WMpictureType]) ? $WMpictureTypeLookup[$WMpictureType] : ''); } + function ASF_HeaderExtensionObjectDataParse(&$asf_header_extension_object_data, &$unhandled_sections) { + // http://msdn.microsoft.com/en-us/library/bb643323.aspx - // Remove terminator 00 00 and convert UNICODE to Latin-1 - function TrimConvert($string) { + $offset = 0; + $objectOffset = 0; + $HeaderExtensionObjectParsed = array(); + while ($objectOffset < strlen($asf_header_extension_object_data)) { + $offset = $objectOffset; + $thisObject = array(); - // remove terminator, only if present (it should be, but...) - if (substr($string, strlen($string) - 2, 2) == "\x00\x00") { - $string = substr($string, 0, strlen($string) - 2); + $thisObject['guid'] = substr($asf_header_extension_object_data, $offset, 16); + $offset += 16; + $thisObject['guid_text'] = $this->BytestringToGUID($thisObject['guid']); + $thisObject['guid_name'] = $this->GUIDname($thisObject['guid_text']); + + $thisObject['size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); + $offset += 8; + if ($thisObject['size'] <= 0) { + break; + } + + switch ($thisObject['guid']) { + case GETID3_ASF_Extended_Stream_Properties_Object: + $thisObject['start_time'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); + $offset += 8; + $thisObject['start_time_unix'] = $this->FILETIMEtoUNIXtime($thisObject['start_time']); + + $thisObject['end_time'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); + $offset += 8; + $thisObject['end_time_unix'] = $this->FILETIMEtoUNIXtime($thisObject['end_time']); + + $thisObject['data_bitrate'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['buffer_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['initial_buffer_fullness'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['alternate_data_bitrate'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['alternate_buffer_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['alternate_initial_buffer_fullness'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['maximum_object_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['flags_raw'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + $thisObject['flags']['reliable'] = (bool) $thisObject['flags_raw'] & 0x00000001; + $thisObject['flags']['seekable'] = (bool) $thisObject['flags_raw'] & 0x00000002; + $thisObject['flags']['no_cleanpoints'] = (bool) $thisObject['flags_raw'] & 0x00000004; + $thisObject['flags']['resend_live_cleanpoints'] = (bool) $thisObject['flags_raw'] & 0x00000008; + + $thisObject['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $thisObject['stream_language_id_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $thisObject['average_time_per_frame'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $thisObject['stream_name_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $thisObject['payload_extension_system_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisObject['stream_name_count']; $i++) { + $streamName = array(); + + $streamName['language_id_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $streamName['stream_name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $streamName['stream_name'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, $streamName['stream_name_length'])); + $offset += $streamName['stream_name_length']; + + $thisObject['stream_names'][$i] = $streamName; + } + + for ($i = 0; $i < $thisObject['payload_extension_system_count']; $i++) { + $payloadExtensionSystem = array(); + + $payloadExtensionSystem['extension_system_id'] = substr($asf_header_extension_object_data, $offset, 16); + $offset += 16; + $payloadExtensionSystem['extension_system_id_text'] = $this->BytestringToGUID($payloadExtensionSystem['extension_system_id']); + + $payloadExtensionSystem['extension_system_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + if ($payloadExtensionSystem['extension_system_size'] <= 0) { + break 2; + } + + $payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, $payloadExtensionSystem['extension_system_info_length'])); + $offset += $payloadExtensionSystem['extension_system_info_length']; + + $thisObject['payload_extension_systems'][$i] = $payloadExtensionSystem; + } + + break; + + case GETID3_ASF_Padding_Object: + // padding, skip it + break; + + case GETID3_ASF_Metadata_Object: + $thisObject['description_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisObject['description_record_counts']; $i++) { + $descriptionRecord = array(); + + $descriptionRecord['reserved_1'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); // must be zero + $offset += 2; + + $descriptionRecord['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['data_type'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); + + $descriptionRecord['data_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $descriptionRecord['name'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['name_length']); + $offset += $descriptionRecord['name_length']; + + $descriptionRecord['data'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['data_length']); + $offset += $descriptionRecord['data_length']; + switch ($descriptionRecord['data_type']) { + case 0x0000: // Unicode string + break; + + case 0x0001: // BYTE array + // do nothing + break; + + case 0x0002: // BOOL + $descriptionRecord['data'] = (bool) getid3_lib::LittleEndian2Int($descriptionRecord['data']); + break; + + case 0x0003: // DWORD + case 0x0004: // QWORD + case 0x0005: // WORD + $descriptionRecord['data'] = getid3_lib::LittleEndian2Int($descriptionRecord['data']); + break; + + case 0x0006: // GUID + $descriptionRecord['data_text'] = $this->BytestringToGUID($descriptionRecord['data']); + break; + } + + $thisObject['description_record'][$i] = $descriptionRecord; + } + break; + + case GETID3_ASF_Language_List_Object: + $thisObject['language_id_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisObject['language_id_record_counts']; $i++) { + $languageIDrecord = array(); + + $languageIDrecord['language_id_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 1)); + $offset += 1; + + $languageIDrecord['language_id'] = substr($asf_header_extension_object_data, $offset, $languageIDrecord['language_id_length']); + $offset += $languageIDrecord['language_id_length']; + + $thisObject['language_id_record'][$i] = $languageIDrecord; + } + break; + + case GETID3_ASF_Metadata_Library_Object: + $thisObject['description_records_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisObject['description_records_count']; $i++) { + $descriptionRecord = array(); + + $descriptionRecord['language_list_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + + $descriptionRecord['data_type'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); + $offset += 2; + $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); + + $descriptionRecord['data_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); + $offset += 4; + + $descriptionRecord['name'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['name_length']); + $offset += $descriptionRecord['name_length']; + + $descriptionRecord['data'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['data_length']); + $offset += $descriptionRecord['data_length']; + + if (preg_match('#^WM/Picture$#', str_replace("\x00", '', trim($descriptionRecord['name'])))) { + $WMpicture = $this->ASF_WMpicture($descriptionRecord['data']); + foreach ($WMpicture as $key => $value) { + $descriptionRecord['data'] = $WMpicture; + } + unset($WMpicture); + } + + $thisObject['description_record'][$i] = $descriptionRecord; + } + break; + + default: + $unhandled_sections++; + if ($this->GUIDname($thisObject['guid_text'])) { + $this->getid3->info['warning'][] = 'unhandled Header Extension Object GUID "'.$this->GUIDname($thisObject['guid_text']).'" {'.$thisObject['guid_text'].'} at offset '.($offset - 16 - 8); + } else { + $this->getid3->info['warning'][] = 'unknown Header Extension Object GUID {'.$thisObject['guid_text'].'} in at offset '.($offset - 16 - 8); + } + break; + } + $HeaderExtensionObjectParsed[] = $thisObject; + + $objectOffset += $thisObject['size']; } - - // convert - return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', $string), ' '); + return $HeaderExtensionObjectParsed; } - function TrimTerm($string) { + static function ASFmetadataLibraryObjectDataTypeLookup($id) { + static $ASFmetadataLibraryObjectDataTypeLookup = array( + 0x0000 => 'Unicode string', // The data consists of a sequence of Unicode characters + 0x0001 => 'BYTE array', // The type of the data is implementation-specific + 0x0002 => 'BOOL', // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values + 0x0003 => 'DWORD', // The data is 4 bytes long and should be interpreted as a 32-bit unsigned integer + 0x0004 => 'QWORD', // The data is 8 bytes long and should be interpreted as a 64-bit unsigned integer + 0x0005 => 'WORD', // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer + 0x0006 => 'GUID', // The data is 16 bytes long and should be interpreted as a 128-bit GUID + ); + return (isset($ASFmetadataLibraryObjectDataTypeLookup[$id]) ? $ASFmetadataLibraryObjectDataTypeLookup[$id] : 'invalid'); + } + function ASF_WMpicture(&$data) { + //typedef struct _WMPicture{ + // LPWSTR pwszMIMEType; + // BYTE bPictureType; + // LPWSTR pwszDescription; + // DWORD dwDataLen; + // BYTE* pbData; + //} WM_PICTURE; + + $WMpicture = array(); + + $offset = 0; + $WMpicture['image_type_id'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 1)); + $offset += 1; + $WMpicture['image_type'] = $this->WMpictureTypeLookup($WMpicture['image_type_id']); + $WMpicture['image_size'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 4)); + $offset += 4; + + $WMpicture['image_mime'] = ''; + do { + $next_byte_pair = substr($data, $offset, 2); + $offset += 2; + $WMpicture['image_mime'] .= $next_byte_pair; + } while ($next_byte_pair !== "\x00\x00"); + + $WMpicture['image_description'] = ''; + do { + $next_byte_pair = substr($data, $offset, 2); + $offset += 2; + $WMpicture['image_description'] .= $next_byte_pair; + } while ($next_byte_pair !== "\x00\x00"); + + $WMpicture['dataoffset'] = $offset; + $WMpicture['data'] = substr($data, $offset); + + $imageinfo = array(); + $WMpicture['image_mime'] = ''; + $imagechunkcheck = getid3_lib::GetDataImageSize($WMpicture['data'], $imageinfo); + unset($imageinfo); + if (!empty($imagechunkcheck)) { + $WMpicture['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + } + if (!isset($this->getid3->info['asf']['comments']['picture'])) { + $this->getid3->info['asf']['comments']['picture'] = array(); + } + $this->getid3->info['asf']['comments']['picture'][] = array('data'=>$WMpicture['data'], 'image_mime'=>$WMpicture['image_mime']); + + return $WMpicture; + } + + + // Remove terminator 00 00 and convert UTF-16LE to Latin-1 + static function TrimConvert($string) { + return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', getid3_asf::TrimTerm($string)), ' '); + } + + + // Remove terminator 00 00 + static function TrimTerm($string) { // remove terminator, only if present (it should be, but...) - if (substr($string, -2) == "\x00\x00") { + if (substr($string, -2) === "\x00\x00") { $string = substr($string, 0, -2); } return $string; } - } - ?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio-video.bink.php b/3rdparty/getid3/module.audio-video.bink.php new file mode 100644 index 0000000000..0a32139676 --- /dev/null +++ b/3rdparty/getid3/module.audio-video.bink.php @@ -0,0 +1,73 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.bink.php // +// module for analyzing Bink or Smacker audio-video files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_bink extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + +$info['error'][] = 'Bink / Smacker files not properly processed by this version of getID3() ['.$this->getid3->version().']'; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $fileTypeID = fread($this->getid3->fp, 3); + switch ($fileTypeID) { + case 'BIK': + return $this->ParseBink(); + break; + + case 'SMK': + return $this->ParseSmacker(); + break; + + default: + $info['error'][] = 'Expecting "BIK" or "SMK" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($fileTypeID).'"'; + return false; + break; + } + + return true; + + } + + function ParseBink() { + $info = &$this->getid3->info; + $info['fileformat'] = 'bink'; + $info['video']['dataformat'] = 'bink'; + + $fileData = 'BIK'.fread($this->getid3->fp, 13); + + $info['bink']['data_size'] = getid3_lib::LittleEndian2Int(substr($fileData, 4, 4)); + $info['bink']['frame_count'] = getid3_lib::LittleEndian2Int(substr($fileData, 8, 2)); + + if (($info['avdataend'] - $info['avdataoffset']) != ($info['bink']['data_size'] + 8)) { + $info['error'][] = 'Probably truncated file: expecting '.$info['bink']['data_size'].' bytes, found '.($info['avdataend'] - $info['avdataoffset']); + } + + return true; + } + + function ParseSmacker() { + $info = &$this->getid3->info; + $info['fileformat'] = 'smacker'; + $info['video']['dataformat'] = 'smacker'; + + return true; + } + +} + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio-video.flv.php b/3rdparty/getid3/module.audio-video.flv.php new file mode 100644 index 0000000000..ba3cd908df --- /dev/null +++ b/3rdparty/getid3/module.audio-video.flv.php @@ -0,0 +1,731 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// // +// FLV module by Seth Kaufman // +// // +// * version 0.1 (26 June 2005) // +// // +// // +// * version 0.1.1 (15 July 2005) // +// minor modifications by James Heinrich // +// // +// * version 0.2 (22 February 2006) // +// Support for On2 VP6 codec and meta information // +// by Steve Webster // +// // +// * version 0.3 (15 June 2006) // +// Modified to not read entire file into memory // +// by James Heinrich // +// // +// * version 0.4 (07 December 2007) // +// Bugfixes for incorrectly parsed FLV dimensions // +// and incorrect parsing of onMetaTag // +// by Evgeny Moysevich // +// // +// * version 0.5 (21 May 2009) // +// Fixed parsing of audio tags and added additional codec // +// details. The duration is now read from onMetaTag (if // +// exists), rather than parsing whole file // +// by Nigel Barnes // +// // +// * version 0.6 (24 May 2009) // +// Better parsing of files with h264 video // +// by Evgeny Moysevich // +// // +// * version 0.6.1 (30 May 2011) // +// prevent infinite loops in expGolombUe() // +// // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.flv.php // +// module for analyzing Shockwave Flash Video files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +define('GETID3_FLV_TAG_AUDIO', 8); +define('GETID3_FLV_TAG_VIDEO', 9); +define('GETID3_FLV_TAG_META', 18); + +define('GETID3_FLV_VIDEO_H263', 2); +define('GETID3_FLV_VIDEO_SCREEN', 3); +define('GETID3_FLV_VIDEO_VP6FLV', 4); +define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5); +define('GETID3_FLV_VIDEO_SCREENV2', 6); +define('GETID3_FLV_VIDEO_H264', 7); + +define('H264_AVC_SEQUENCE_HEADER', 0); +define('H264_PROFILE_BASELINE', 66); +define('H264_PROFILE_MAIN', 77); +define('H264_PROFILE_EXTENDED', 88); +define('H264_PROFILE_HIGH', 100); +define('H264_PROFILE_HIGH10', 110); +define('H264_PROFILE_HIGH422', 122); +define('H264_PROFILE_HIGH444', 144); +define('H264_PROFILE_HIGH444_PREDICTIVE', 244); + +class getid3_flv extends getid3_handler +{ + var $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $FLVdataLength = $info['avdataend'] - $info['avdataoffset']; + $FLVheader = fread($this->getid3->fp, 5); + + $info['fileformat'] = 'flv'; + $info['flv']['header']['signature'] = substr($FLVheader, 0, 3); + $info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1)); + $TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1)); + + $magic = 'FLV'; + if ($info['flv']['header']['signature'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"'; + unset($info['flv']); + unset($info['fileformat']); + return false; + } + + $info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04); + $info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01); + + $FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 4)); + $FLVheaderFrameLength = 9; + if ($FrameSizeDataLength > $FLVheaderFrameLength) { + fseek($this->getid3->fp, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR); + } + $Duration = 0; + $found_video = false; + $found_audio = false; + $found_meta = false; + $found_valid_meta_playtime = false; + $tagParseCount = 0; + $info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0); + $flv_framecount = &$info['flv']['framecount']; + while (((ftell($this->getid3->fp) + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) { + $ThisTagHeader = fread($this->getid3->fp, 16); + + $PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4)); + $TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1)); + $DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3)); + $Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3)); + $LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1)); + $NextOffset = ftell($this->getid3->fp) - 1 + $DataLength; + if ($Timestamp > $Duration) { + $Duration = $Timestamp; + } + + $flv_framecount['total']++; + switch ($TagType) { + case GETID3_FLV_TAG_AUDIO: + $flv_framecount['audio']++; + if (!$found_audio) { + $found_audio = true; + $info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F; + $info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03; + $info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01; + $info['flv']['audio']['audioType'] = $LastHeaderByte & 0x01; + } + break; + + case GETID3_FLV_TAG_VIDEO: + $flv_framecount['video']++; + if (!$found_video) { + $found_video = true; + $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07; + + $FLVvideoHeader = fread($this->getid3->fp, 11); + + if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) { + // this code block contributed by: moysevichØgmail*com + + $AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1)); + if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) { + // read AVCDecoderConfigurationRecord + $configurationVersion = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 1)); + $AVCProfileIndication = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 1)); + $profile_compatibility = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 1)); + $lengthSizeMinusOne = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 1)); + $numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 8, 1)); + + if (($numOfSequenceParameterSets & 0x1F) != 0) { + // there is at least one SequenceParameterSet + // read size of the first SequenceParameterSet + //$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2)); + $spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2)); + // read the first SequenceParameterSet + $sps = fread($this->getid3->fp, $spsSize); + if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red + $spsReader = new AVCSequenceParameterSetReader($sps); + $spsReader->readData(); + $info['video']['resolution_x'] = $spsReader->getWidth(); + $info['video']['resolution_y'] = $spsReader->getHeight(); + } + } + } + // end: moysevichØgmail*com + + } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) { + + $PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7; + $PictureSizeType = $PictureSizeType & 0x0007; + $info['flv']['header']['videoSizeType'] = $PictureSizeType; + switch ($PictureSizeType) { + case 0: + //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)); + //$PictureSizeEnc <<= 1; + //$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8; + //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2)); + //$PictureSizeEnc <<= 1; + //$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8; + + $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)); + $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)); + $PictureSizeEnc['x'] >>= 7; + $PictureSizeEnc['y'] >>= 7; + $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF; + $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF; + break; + + case 1: + $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)); + $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)); + $PictureSizeEnc['x'] >>= 7; + $PictureSizeEnc['y'] >>= 7; + $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF; + $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF; + break; + + case 2: + $info['video']['resolution_x'] = 352; + $info['video']['resolution_y'] = 288; + break; + + case 3: + $info['video']['resolution_x'] = 176; + $info['video']['resolution_y'] = 144; + break; + + case 4: + $info['video']['resolution_x'] = 128; + $info['video']['resolution_y'] = 96; + break; + + case 5: + $info['video']['resolution_x'] = 320; + $info['video']['resolution_y'] = 240; + break; + + case 6: + $info['video']['resolution_x'] = 160; + $info['video']['resolution_y'] = 120; + break; + + default: + $info['video']['resolution_x'] = 0; + $info['video']['resolution_y'] = 0; + break; + + } + } + $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y']; + } + break; + + // Meta tag + case GETID3_FLV_TAG_META: + if (!$found_meta) { + $found_meta = true; + fseek($this->getid3->fp, -1, SEEK_CUR); + $datachunk = fread($this->getid3->fp, $DataLength); + $AMFstream = new AMFStream($datachunk); + $reader = new AMFReader($AMFstream); + $eventName = $reader->readData(); + $info['flv']['meta'][$eventName] = $reader->readData(); + unset($reader); + + $copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate'); + foreach ($copykeys as $sourcekey => $destkey) { + if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) { + switch ($sourcekey) { + case 'width': + case 'height': + $info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey])); + break; + case 'audiodatarate': + $info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000)); + break; + case 'videodatarate': + case 'frame_rate': + default: + $info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey]; + break; + } + } + } + if (!empty($info['flv']['meta']['onMetaData']['duration'])) { + $found_valid_meta_playtime = true; + } + } + break; + + default: + // noop + break; + } + fseek($this->getid3->fp, $NextOffset, SEEK_SET); + } + + $info['playtime_seconds'] = $Duration / 1000; + if ($info['playtime_seconds'] > 0) { + $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + } + + if ($info['flv']['header']['hasAudio']) { + $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['audio']['audioFormat']); + $info['audio']['sample_rate'] = $this->FLVaudioRate($info['flv']['audio']['audioRate']); + $info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info['flv']['audio']['audioSampleSize']); + + $info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo + $info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed + $info['audio']['dataformat'] = 'flv'; + } + if (!empty($info['flv']['header']['hasVideo'])) { + $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['video']['videoCodec']); + $info['video']['dataformat'] = 'flv'; + $info['video']['lossless'] = false; + } + + // Set information from meta + if (!empty($info['flv']['meta']['onMetaData']['duration'])) { + $info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration']; + $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + } + if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) { + $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['meta']['onMetaData']['audiocodecid']); + } + if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) { + $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['meta']['onMetaData']['videocodecid']); + } + return true; + } + + + function FLVaudioFormat($id) { + $FLVaudioFormat = array( + 0 => 'Linear PCM, platform endian', + 1 => 'ADPCM', + 2 => 'mp3', + 3 => 'Linear PCM, little endian', + 4 => 'Nellymoser 16kHz mono', + 5 => 'Nellymoser 8kHz mono', + 6 => 'Nellymoser', + 7 => 'G.711A-law logarithmic PCM', + 8 => 'G.711 mu-law logarithmic PCM', + 9 => 'reserved', + 10 => 'AAC', + 11 => false, // unknown? + 12 => false, // unknown? + 13 => false, // unknown? + 14 => 'mp3 8kHz', + 15 => 'Device-specific sound', + ); + return (isset($FLVaudioFormat[$id]) ? $FLVaudioFormat[$id] : false); + } + + function FLVaudioRate($id) { + $FLVaudioRate = array( + 0 => 5500, + 1 => 11025, + 2 => 22050, + 3 => 44100, + ); + return (isset($FLVaudioRate[$id]) ? $FLVaudioRate[$id] : false); + } + + function FLVaudioBitDepth($id) { + $FLVaudioBitDepth = array( + 0 => 8, + 1 => 16, + ); + return (isset($FLVaudioBitDepth[$id]) ? $FLVaudioBitDepth[$id] : false); + } + + function FLVvideoCodec($id) { + $FLVvideoCodec = array( + GETID3_FLV_VIDEO_H263 => 'Sorenson H.263', + GETID3_FLV_VIDEO_SCREEN => 'Screen video', + GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6', + GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel', + GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2', + GETID3_FLV_VIDEO_H264 => 'Sorenson H.264', + ); + return (isset($FLVvideoCodec[$id]) ? $FLVvideoCodec[$id] : false); + } +} + +class AMFStream { + var $bytes; + var $pos; + + function AMFStream(&$bytes) { + $this->bytes =& $bytes; + $this->pos = 0; + } + + function readByte() { + return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1)); + } + + function readInt() { + return ($this->readByte() << 8) + $this->readByte(); + } + + function readLong() { + return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte(); + } + + function readDouble() { + return getid3_lib::BigEndian2Float($this->read(8)); + } + + function readUTF() { + $length = $this->readInt(); + return $this->read($length); + } + + function readLongUTF() { + $length = $this->readLong(); + return $this->read($length); + } + + function read($length) { + $val = substr($this->bytes, $this->pos, $length); + $this->pos += $length; + return $val; + } + + function peekByte() { + $pos = $this->pos; + $val = $this->readByte(); + $this->pos = $pos; + return $val; + } + + function peekInt() { + $pos = $this->pos; + $val = $this->readInt(); + $this->pos = $pos; + return $val; + } + + function peekLong() { + $pos = $this->pos; + $val = $this->readLong(); + $this->pos = $pos; + return $val; + } + + function peekDouble() { + $pos = $this->pos; + $val = $this->readDouble(); + $this->pos = $pos; + return $val; + } + + function peekUTF() { + $pos = $this->pos; + $val = $this->readUTF(); + $this->pos = $pos; + return $val; + } + + function peekLongUTF() { + $pos = $this->pos; + $val = $this->readLongUTF(); + $this->pos = $pos; + return $val; + } +} + +class AMFReader { + var $stream; + + function AMFReader(&$stream) { + $this->stream =& $stream; + } + + function readData() { + $value = null; + + $type = $this->stream->readByte(); + switch ($type) { + + // Double + case 0: + $value = $this->readDouble(); + break; + + // Boolean + case 1: + $value = $this->readBoolean(); + break; + + // String + case 2: + $value = $this->readString(); + break; + + // Object + case 3: + $value = $this->readObject(); + break; + + // null + case 6: + return null; + break; + + // Mixed array + case 8: + $value = $this->readMixedArray(); + break; + + // Array + case 10: + $value = $this->readArray(); + break; + + // Date + case 11: + $value = $this->readDate(); + break; + + // Long string + case 13: + $value = $this->readLongString(); + break; + + // XML (handled as string) + case 15: + $value = $this->readXML(); + break; + + // Typed object (handled as object) + case 16: + $value = $this->readTypedObject(); + break; + + // Long string + default: + $value = '(unknown or unsupported data type)'; + break; + } + + return $value; + } + + function readDouble() { + return $this->stream->readDouble(); + } + + function readBoolean() { + return $this->stream->readByte() == 1; + } + + function readString() { + return $this->stream->readUTF(); + } + + function readObject() { + // Get highest numerical index - ignored +// $highestIndex = $this->stream->readLong(); + + $data = array(); + + while ($key = $this->stream->readUTF()) { + $data[$key] = $this->readData(); + } + // Mixed array record ends with empty string (0x00 0x00) and 0x09 + if (($key == '') && ($this->stream->peekByte() == 0x09)) { + // Consume byte + $this->stream->readByte(); + } + return $data; + } + + function readMixedArray() { + // Get highest numerical index - ignored + $highestIndex = $this->stream->readLong(); + + $data = array(); + + while ($key = $this->stream->readUTF()) { + if (is_numeric($key)) { + $key = (float) $key; + } + $data[$key] = $this->readData(); + } + // Mixed array record ends with empty string (0x00 0x00) and 0x09 + if (($key == '') && ($this->stream->peekByte() == 0x09)) { + // Consume byte + $this->stream->readByte(); + } + + return $data; + } + + function readArray() { + $length = $this->stream->readLong(); + $data = array(); + + for ($i = 0; $i < $length; $i++) { + $data[] = $this->readData(); + } + return $data; + } + + function readDate() { + $timestamp = $this->stream->readDouble(); + $timezone = $this->stream->readInt(); + return $timestamp; + } + + function readLongString() { + return $this->stream->readLongUTF(); + } + + function readXML() { + return $this->stream->readLongUTF(); + } + + function readTypedObject() { + $className = $this->stream->readUTF(); + return $this->readObject(); + } +} + +class AVCSequenceParameterSetReader { + var $sps; + var $start = 0; + var $currentBytes = 0; + var $currentBits = 0; + var $width; + var $height; + + function AVCSequenceParameterSetReader($sps) { + $this->sps = $sps; + } + + function readData() { + $this->skipBits(8); + $this->skipBits(8); + $profile = $this->getBits(8); // read profile + $this->skipBits(16); + $this->expGolombUe(); // read sps id + if (in_array($profile, array(H264_PROFILE_HIGH, H264_PROFILE_HIGH10, H264_PROFILE_HIGH422, H264_PROFILE_HIGH444, H264_PROFILE_HIGH444_PREDICTIVE))) { + if ($this->expGolombUe() == 3) { + $this->skipBits(1); + } + $this->expGolombUe(); + $this->expGolombUe(); + $this->skipBits(1); + if ($this->getBit()) { + for ($i = 0; $i < 8; $i++) { + if ($this->getBit()) { + $size = $i < 6 ? 16 : 64; + $lastScale = 8; + $nextScale = 8; + for ($j = 0; $j < $size; $j++) { + if ($nextScale != 0) { + $deltaScale = $this->expGolombUe(); + $nextScale = ($lastScale + $deltaScale + 256) % 256; + } + if ($nextScale != 0) { + $lastScale = $nextScale; + } + } + } + } + } + } + $this->expGolombUe(); + $pocType = $this->expGolombUe(); + if ($pocType == 0) { + $this->expGolombUe(); + } elseif ($pocType == 1) { + $this->skipBits(1); + $this->expGolombSe(); + $this->expGolombSe(); + $pocCycleLength = $this->expGolombUe(); + for ($i = 0; $i < $pocCycleLength; $i++) { + $this->expGolombSe(); + } + } + $this->expGolombUe(); + $this->skipBits(1); + $this->width = ($this->expGolombUe() + 1) * 16; + $heightMap = $this->expGolombUe() + 1; + $this->height = (2 - $this->getBit()) * $heightMap * 16; + } + + function skipBits($bits) { + $newBits = $this->currentBits + $bits; + $this->currentBytes += (int)floor($newBits / 8); + $this->currentBits = $newBits % 8; + } + + function getBit() { + $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01; + $this->skipBits(1); + return $result; + } + + function getBits($bits) { + $result = 0; + for ($i = 0; $i < $bits; $i++) { + $result = ($result << 1) + $this->getBit(); + } + return $result; + } + + function expGolombUe() { + $significantBits = 0; + $bit = $this->getBit(); + while ($bit == 0) { + $significantBits++; + $bit = $this->getBit(); + + if ($significantBits > 31) { + // something is broken, this is an emergency escape to prevent infinite loops + return 0; + } + } + return (1 << $significantBits) + $this->getBits($significantBits) - 1; + } + + function expGolombSe() { + $result = $this->expGolombUe(); + if (($result & 0x01) == 0) { + return -($result >> 1); + } else { + return ($result + 1) >> 1; + } + } + + function getWidth() { + return $this->width; + } + + function getHeight() { + return $this->height; + } +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio-video.matroska.php b/3rdparty/getid3/module.audio-video.matroska.php similarity index 53% rename from apps/media/getID3/getid3/module.audio-video.matroska.php rename to 3rdparty/getid3/module.audio-video.matroska.php index 004056edb0..b7ab667989 100644 --- a/apps/media/getID3/getid3/module.audio-video.matroska.php +++ b/3rdparty/getid3/module.audio-video.matroska.php @@ -14,9 +14,10 @@ ///////////////////////////////////////////////////////////////// +// from: http://www.matroska.org/technical/specs/index.html define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation. define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements. -define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found here. +define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found . define('EBML_ID_INFO', 0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file. define('EBML_ID_TRACKS', 0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described. define('EBML_ID_SEGMENT', 0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment. @@ -111,7 +112,7 @@ define('EBML_ID_TARGETS', 0x23C0); // [63][C0] -- define('EBML_ID_CHAPTERPHYSICALEQUIV', 0x23C3); // [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values. define('EBML_ID_TAGCHAPTERUID', 0x23C4); // [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment. define('EBML_ID_TAGTRACKUID', 0x23C5); // [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment. -define('EBML_ID_ATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment. +define('EBML_ID_TAGATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment. define('EBML_ID_TAGEDITIONUID', 0x23C9); // [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment. define('EBML_ID_TARGETTYPE', 0x23CA); // [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType). define('EBML_ID_TRACKTRANSLATE', 0x2624); // [66][24] -- The track identification for the given Chapter Codec. @@ -205,176 +206,367 @@ define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block. -class getid3_matroska +class getid3_matroska extends getid3_handler { - var $read_buffer_size = 32768; // size of read buffer, 32kB is default - var $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful - var $warnings = array(); + // public options + public static $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE] + public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] - function getid3_matroska(&$fd, &$ThisFileInfo) { + // private parser settings/placeholders + private $EBMLbuffer = ''; + private $EBMLbuffer_offset = 0; + private $EBMLbuffer_length = 0; + private $current_offset = 0; + private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID); + + public function Analyze() + { + $info = &$this->getid3->info; - // http://www.matroska.org/technical/specs/index.html#EBMLBasics - $offset = $ThisFileInfo['avdataoffset']; - $EBMLdata = ''; - $EBMLdata_offset = $offset; - - if ($ThisFileInfo['avdataend'] > 2147483648) { - $this->warnings[] = 'This version of getID3() may or may not correctly handle Matroska files larger than 2GB'; + // parse container + try { + $this->parseEBML($info); + } + catch (Exception $e) { + $info['error'][] = 'EBML parser: '.$e->getMessage(); } - while ($offset < $ThisFileInfo['avdataend']) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - - $top_element_offset = $offset; - $top_element_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $top_element_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - if ($top_element_length === false) { - $this->warnings[] = 'invalid chunk length at '.$top_element_offset; - $offset = pow(2, 63); - break; + // calculate playtime + if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { + foreach ($info['matroska']['info'] as $key => $infoarray) { + if (isset($infoarray['Duration'])) { + // TimecodeScale is how many nanoseconds each Duration unit is + $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); + break; + } } - $top_element_endoffset = $offset + $top_element_length; - switch ($top_element_id) { + } + + // extract tags + if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { + foreach ($info['matroska']['tags'] as $key => $infoarray) { + $this->ExtractCommentsSimpleTag($infoarray); + } + } + + // process tracks + if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { + foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { + + $track_info = array(); + $track_info['dataformat'] = self::MatroskaCodecIDtoCommonName($trackarray['CodecID']); + $track_info['default'] = (isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true); + if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } + + switch ($trackarray['TrackType']) { + + case 1: // Video + $track_info['resolution_x'] = $trackarray['PixelWidth']; + $track_info['resolution_y'] = $trackarray['PixelHeight']; + if (isset($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } + if (isset($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } + if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } + //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + + switch ($trackarray['CodecID']) { + case 'V_MS/VFW/FOURCC': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { + $this->getid3->warning('Unable to parse codec private data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); + break; + } + $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); + $track_info['codec'] = getid3_riff::RIFFfourccLookup($parsed['fourcc']); + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; + break; + } + + $info['video']['streams'][] = $track_info; + break; + + case 2: // Audio + $track_info['sample_rate'] = (isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0); + $track_info['channels'] = (isset($trackarray['Channels']) ? $trackarray['Channels'] : 1); + $track_info['language'] = (isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'); + if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } + //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + + switch ($trackarray['CodecID']) { + case 'A_PCM/INT/LIT': + case 'A_PCM/INT/BIG': + $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; + break; + + case 'A_AC3': + case 'A_DTS': + case 'A_MPEG/L3': + //case 'A_FLAC': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.$track_info['dataformat'].'.php', __FILE__, false)) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.'.$track_info['dataformat'].'.php"'); + break; + } + + if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'); + break; + } + + // create temp instance + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; + if ($track_info['dataformat'] == 'mp3' || $track_info['dataformat'] == 'flac') { + $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; + } + + // analyze + $class = 'getid3_'.$track_info['dataformat']; + $header_data_key = $track_info['dataformat'] == 'mp3' ? 'mpeg' : $track_info['dataformat']; + $getid3_audio = new $class($getid3_temp); + if ($track_info['dataformat'] == 'mp3') { + $getid3_audio->allow_bruteforce = true; + } + if ($track_info['dataformat'] == 'flac') { + $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); + } + else { + $getid3_audio->Analyze(); + } + if (!empty($getid3_temp->info[$header_data_key])) { + unset($getid3_temp->info[$header_data_key]['GETID3_VERSION']); + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; + if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { + foreach ($getid3_temp->info['audio'] as $key => $value) { + $track_info[$key] = $value; + } + } + } + else { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']); + } + + // copy errors and warnings + if (!empty($getid3_temp->info['error'])) { + foreach ($getid3_temp->info['error'] as $newerror) { + $this->getid3->warning($class.'() says: ['.$newerror.']'); + } + } + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + if ($track_info['dataformat'] == 'mp3' && preg_match('/^Probable truncated file: expecting \d+ bytes of audio data, only found \d+ \(short by \d+ bytes\)$/', $newerror)) { + // LAME/Xing header is probably set, but audio data is chunked into Matroska file and near-impossible to verify if audio stream is complete, so ignore useless warning + continue; + } + $this->getid3->warning($class.'() says: ['.$newerror.']'); + } + } + unset($getid3_temp, $getid3_audio); + break; + + case 'A_AAC': + case 'A_AAC/MPEG2/LC': + case 'A_AAC/MPEG4/LC': + case 'A_AAC/MPEG4/LC/SBR': + $this->getid3->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated'); + break; + + case 'A_VORBIS': + if (!isset($trackarray['CodecPrivate'])) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set'); + break; + } + $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); + if ($vorbis_offset === false) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword'); + break; + } + $vorbis_offset -= 1; + + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ogg.php"'); + } + + // create temp instance + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + + // analyze + $getid3_ogg = new getid3_ogg($getid3_temp); + $oggpageinfo['page_seqno'] = 0; + $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); + if (!empty($getid3_temp->info['ogg'])) { + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; + if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { + foreach ($getid3_temp->info['audio'] as $key => $value) { + $track_info[$key] = $value; + } + } + } + + // copy errors and warnings + if (!empty($getid3_temp->info['error'])) { + foreach ($getid3_temp->info['error'] as $newerror) { + $this->getid3->warning('getid3_ogg() says: ['.$newerror.']'); + } + } + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->getid3->warning('getid3_ogg() says: ['.$newerror.']'); + } + } + + if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { + $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; + } + unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); + break; + + case 'A_MS/ACM': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { + $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); + break; + } + + $parsed = getid3_riff::RIFFparseWAVEFORMATex($trackarray['CodecPrivate']); + foreach ($parsed as $key => $value) { + if ($key != 'raw') { + $track_info[$key] = $value; + } + } + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; + break; + + default: + $this->getid3->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"'); + } + + $info['audio']['streams'][] = $track_info; + break; + } + } + + if (!empty($info['video']['streams'])) { + $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); + } + if (!empty($info['audio']['streams'])) { + $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); + } + } + + // determine mime type + if (!empty($info['video']['streams'])) { + $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'); + } elseif (!empty($info['audio']['streams'])) { + $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'); + } elseif (isset($info['mime_type'])) { + unset($info['mime_type']); + } + + return true; + } + + +/////////////////////////////////////// + + private function parseEBML(&$info) + { + // http://www.matroska.org/technical/specs/index.html#EBMLBasics + $this->current_offset = $info['avdataoffset']; + + while ($this->getEBMLelement($top_element, $info['avdataend'])) { + switch ($top_element['id']) { + case EBML_ID_EBML: - $ThisFileInfo['fileformat'] = 'matroska'; - $ThisFileInfo['matroska']['header']['offset'] = $top_element_offset; - $ThisFileInfo['matroska']['header']['length'] = $top_element_length; - - while ($offset < $top_element_endoffset) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $element_data = array(); - $element_data_offset = $offset; - $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $element_data['id_name'] = $this->EBMLidName($element_data['id']); - $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $end_offset = $offset + $element_data['length']; + $info['fileformat'] = 'matroska'; + $info['matroska']['header']['offset'] = $top_element['offset']; + $info['matroska']['header']['length'] = $top_element['length']; + while ($this->getEBMLelement($element_data, $top_element['end'], true)) { switch ($element_data['id']) { + case EBML_ID_EBMLVERSION: case EBML_ID_EBMLREADVERSION: case EBML_ID_EBMLMAXIDLENGTH: case EBML_ID_EBMLMAXSIZELENGTH: case EBML_ID_DOCTYPEVERSION: case EBML_ID_DOCTYPEREADVERSION: - $element_data['data'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length'])); + $element_data['data'] = getid3_lib::BigEndian2Int($element_data['data']); break; + case EBML_ID_DOCTYPE: - $element_data['data'] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length']), "\x00"); + $element_data['data'] = getid3_lib::trimNullByte($element_data['data']); + $info['matroska']['doctype'] = $element_data['data']; break; + + case EBML_ID_CRC32: // not useful, ignore + $this->current_offset = $element_data['end']; + unset($element_data); + break; + default: - $this->warnings[] = 'Unhandled track.video element['.__LINE__.'] ('.$element_data['id'].'::'.$element_data['id_name'].') at '.$element_data_offset; - break; + $this->unhandledElement('header', __LINE__, $element_data); + } + if (!empty($element_data)) { + unset($element_data['offset'], $element_data['end']); + $info['matroska']['header']['elements'][] = $element_data; } - $offset = $end_offset; - $ThisFileInfo['matroska']['header']['elements'][] = $element_data; } break; - case EBML_ID_SEGMENT: - $ThisFileInfo['matroska']['segment'][0]['offset'] = $top_element_offset; - $ThisFileInfo['matroska']['segment'][0]['length'] = $top_element_length; + $info['matroska']['segment'][0]['offset'] = $top_element['offset']; + $info['matroska']['segment'][0]['length'] = $top_element['length']; - $segment_key = -1; - while ($offset < $ThisFileInfo['avdataend']) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - - $element_data = array(); - $element_data['offset'] = $offset; - $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $element_data['id_name'] = $this->EBMLidName($element_data['id']); - $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - if ($element_data['length'] === false) { - $this->warnings[] = 'invalid chunk length at '.$element_data['offset']; - //$offset = pow(2, 63); - $offset = $ThisFileInfo['avdataend']; - break; + while ($this->getEBMLelement($element_data, $top_element['end'])) { + if ($element_data['id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required + $info['matroska']['segments'][] = $element_data; } - $element_end = $offset + $element_data['length']; switch ($element_data['id']) { - //case EBML_ID_CLUSTER: - // // too many cluster entries, probably not useful - // break; - case false: - $this->warnings[] = 'invalid ID at '.$element_data['offset']; - $offset = $element_end; - continue 3; - default: - $ThisFileInfo['matroska']['segments'][] = $element_data; - break; - } - $segment_key++; - switch ($element_data['id']) { - case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $seek_entry = array(); - $seek_entry['offset'] = $offset; - $seek_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $seek_entry['id_name'] = $this->EBMLidName($seek_entry['id']); - $seek_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $seek_end_offset = $offset + $seek_entry['length']; + case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements. + + while ($this->getEBMLelement($seek_entry, $element_data['end'])) { switch ($seek_entry['id']) { + case EBML_ID_SEEK: // Contains a single seek entry to an EBML element - while ($offset < $seek_end_offset) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $value = substr($EBMLdata, $offset - $EBMLdata_offset, $length); - $offset += $length; - switch ($id) { + while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) { + + switch ($sub_seek_entry['id']) { + case EBML_ID_SEEKID: - $dummy = 0; - $seek_entry['target_id'] = $this->readEBMLint($value, $dummy); - $seek_entry['target_name'] = $this->EBMLidName($seek_entry['target_id']); + $seek_entry['target_id'] = self::EBML2Int($sub_seek_entry['data']); + $seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']); break; + case EBML_ID_SEEKPOSITION: - $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($value); + $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($sub_seek_entry['data']); break; + default: - $ThisFileInfo['error'][] = 'Unhandled segment['.__LINE__.'] ('.$id.') at '.$offset; - break; - } + $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry); } + } + + if ($seek_entry['target_id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required + $info['matroska']['seek'][] = $seek_entry; } - $ThisFileInfo['matroska']['seek'][] = $seek_entry; - //switch ($seek_entry['target_id']) { - // case EBML_ID_CLUSTER: - // // too many cluster seek points, probably not useful - // break; - // default: - // $ThisFileInfo['matroska']['seek'][] = $seek_entry; - // break; - //} break; + default: - $this->warnings[] = 'Unhandled seekhead element['.__LINE__.'] ('.$seek_entry['id'].') at '.$offset; - break; + $this->unhandledElement('seekhead', __LINE__, $seek_entry); } - $offset = $seek_end_offset; } break; - case EBML_ID_TRACKS: // information about all tracks in segment - $ThisFileInfo['matroska']['tracks'] = $element_data; - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $track_entry = array(); - $track_entry['offset'] = $offset; - $track_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $track_entry['id_name'] = $this->EBMLidName($track_entry['id']); - $track_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $track_entry_endoffset = $offset + $track_entry['length']; + case EBML_ID_TRACKS: // A top-level block of information with many tracks described. + $info['matroska']['tracks'] = $element_data; + + while ($this->getEBMLelement($track_entry, $element_data['end'])) { switch ($track_entry['id']) { + case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. - while ($offset < $track_entry_endoffset) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { + + while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS))) { + switch ($subelement['id']) { + case EBML_ID_TRACKNUMBER: case EBML_ID_TRACKUID: case EBML_ID_TRACKTYPE: @@ -382,33 +574,37 @@ class getid3_matroska case EBML_ID_MAXCACHE: case EBML_ID_MAXBLOCKADDITIONID: case EBML_ID_DEFAULTDURATION: // nanoseconds per frame - $track_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; + case EBML_ID_TRACKTIMECODESCALE: - $track_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); break; + case EBML_ID_CODECID: case EBML_ID_LANGUAGE: case EBML_ID_NAME: - case EBML_ID_CODECPRIVATE: - $track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); + case EBML_ID_CODECNAME: + $track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); break; + + case EBML_ID_CODECPRIVATE: + $track_entry[$subelement['id_name']] = $subelement['data']; + break; + case EBML_ID_FLAGENABLED: case EBML_ID_FLAGDEFAULT: case EBML_ID_FLAGFORCED: case EBML_ID_FLAGLACING: case EBML_ID_CODECDECODEALL: - $track_entry[$subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $track_entry[$subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($subelement['data']); break; + case EBML_ID_VIDEO: - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: case EBML_ID_STEREOMODE: @@ -420,978 +616,581 @@ class getid3_matroska case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: - $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; - case EBML_ID_FLAGINTERLACED: - $track_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); - break; - case EBML_ID_GAMMAVALUE: - $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); - break; - case EBML_ID_COLOURSPACE: - $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), "\x00"); - break; - default: - $this->warnings[] = 'Unhandled track.video element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; - } - $offset = $sub_subelement_end; - } - if ((@$track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC') && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { - $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); - } else { - $this->warnings[] = 'Unable to parse codec private data['.__LINE__.'] because cannot include "module.audio-video.riff.php"'; + case EBML_ID_FLAGINTERLACED: + $track_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_GAMMAVALUE: + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); + break; + + case EBML_ID_COLOURSPACE: + $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); + break; + + default: + $this->unhandledElement('track.video', __LINE__, $sub_subelement); } } break; + case EBML_ID_AUDIO: - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + case EBML_ID_CHANNELS: case EBML_ID_BITDEPTH: - $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; + case EBML_ID_SAMPLINGFREQUENCY: case EBML_ID_OUTPUTSAMPLINGFREQUENCY: - $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); break; + case EBML_ID_CHANNELPOSITIONS: - $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), "\x00"); + $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled track.video element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('track.audio', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } break; case EBML_ID_CONTENTENCODINGS: - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'])) { + switch ($sub_subelement['id']) { + case EBML_ID_CONTENTENCODING: - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CONTENTCOMPRESSION, EBML_ID_CONTENTENCRYPTION))) { + switch ($sub_sub_subelement['id']) { + case EBML_ID_CONTENTENCODINGORDER: case EBML_ID_CONTENTENCODINGSCOPE: case EBML_ID_CONTENTENCODINGTYPE: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + case EBML_ID_CONTENTCOMPRESSION: - while ($offset < $sub_sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_offset = $offset; - $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; - switch ($sub_sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + case EBML_ID_CONTENTCOMPALGO: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; + case EBML_ID_CONTENTCOMPSETTINGS: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; + default: - $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); } - $offset = $sub_sub_sub_subelement_end; } break; case EBML_ID_CONTENTENCRYPTION: - while ($offset < $sub_sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_offset = $offset; - $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; - switch ($sub_sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + case EBML_ID_CONTENTENCALGO: case EBML_ID_CONTENTSIGALGO: case EBML_ID_CONTENTSIGHASHALGO: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; + case EBML_ID_CONTENTENCKEYID: case EBML_ID_CONTENTSIGNATURE: case EBML_ID_CONTENTSIGKEYID: - $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; + default: - $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); } - $offset = $sub_sub_sub_subelement_end; } break; default: - $this->warnings[] = 'Unhandled track.contentencodings.contentencoding element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement); } - $offset = $sub_sub_subelement_end; } break; + default: - $this->warnings[] = 'Unhandled track.contentencodings element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } break; default: - $this->warnings[] = 'Unhandled track element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('track', __LINE__, $subelement); } - $offset = $subelement_end; } + + $info['matroska']['tracks']['tracks'][] = $track_entry; break; + default: - $this->warnings[] = 'Unhandled track element['.__LINE__.'] ('.$track_entry['id'].'::'.$track_entry['id_name'].') at '.$track_entry['offset']; - $offset = $track_entry_endoffset; - break; + $this->unhandledElement('tracks', __LINE__, $track_entry); } - $ThisFileInfo['matroska']['tracks']['tracks'][] = $track_entry; } break; - case EBML_ID_INFO: // Contains the position of other level 1 elements + case EBML_ID_INFO: // Contains miscellaneous general information and statistics on the file. $info_entry = array(); - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { + + while ($this->getEBMLelement($subelement, $element_data['end'], true)) { + switch ($subelement['id']) { + case EBML_ID_CHAPTERTRANSLATEEDITIONUID: case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: - $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; + case EBML_ID_DURATION: - $info_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); break; + case EBML_ID_DATEUTC: - $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); - $info_entry[$subelement_idname.'_unix'] = $this->EBMLdate2unix($info_entry[$subelement_idname]); + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); + $info_entry[$subelement['id_name'].'_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]); break; + case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: case EBML_ID_SEGMENTFAMILY: case EBML_ID_CHAPTERTRANSLATEID: - $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); + $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); break; + case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: case EBML_ID_TITLE: case EBML_ID_MUXINGAPP: case EBML_ID_WRITINGAPP: - $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); + $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); + $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']]; break; + default: - $this->warnings[] = 'Unhandled info element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('info', __LINE__, $subelement); } - $offset = $subelement_end; } - $ThisFileInfo['matroska']['info'][] = $info_entry; + $info['matroska']['info'][] = $info_entry; break; - case EBML_ID_CUES: + case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. + if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway + $this->current_offset = $element_data['end']; + break; + } $cues_entry = array(); - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { + case EBML_ID_CUEPOINT: $cuepoint_entry = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CUETRACKPOSITIONS))) { + switch ($sub_subelement['id']) { + case EBML_ID_CUETRACKPOSITIONS: - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { + $cuetrackpositions_entry = array(); + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { + switch ($sub_sub_subelement['id']) { + case EBML_ID_CUETRACK: - $cuepoint_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + case EBML_ID_CUECLUSTERPOSITION: + case EBML_ID_CUEBLOCKNUMBER: + case EBML_ID_CUECODECSTATE: + $cuetrackpositions_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled cues.cuepoint.cuetrackpositions element['.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; - break; + $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement); } - $offset = $sub_subelement_end; } + $cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry; break; + case EBML_ID_CUETIME: - $cuepoint_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $cuepoint_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled cues.cuepoint element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } $cues_entry[] = $cuepoint_entry; - $offset = $sub_subelement_end; break; + default: - $this->warnings[] = 'Unhandled cues element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('cues', __LINE__, $subelement); } - $offset = $subelement_end; } - $ThisFileInfo['matroska']['cues'] = $cues_entry; + $info['matroska']['cues'] = $cues_entry; break; - case EBML_ID_TAGS: - $tags_entry = array(); - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { - case EBML_ID_WRITINGAPP: - $tags_entry[$subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length); - break; + case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters. + $tags_entry = array(); + + while ($this->getEBMLelement($subelement, $element_data['end'], false)) { + switch ($subelement['id']) { + case EBML_ID_TAG: $tag_entry = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) { + switch ($sub_subelement['id']) { + case EBML_ID_TARGETS: $targets_entry = array(); - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { + switch ($sub_sub_subelement['id']) { + case EBML_ID_TARGETTYPEVALUE: - case EBML_ID_EDITIONUID: - case EBML_ID_CHAPTERUID: - case EBML_ID_ATTACHMENTUID: - case EBML_ID_TAGTRACKUID: + $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); + $targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::MatroskaTargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); + break; + + case EBML_ID_TARGETTYPE: + $targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; + break; + + case EBML_ID_TAGTRACKUID: + case EBML_ID_TAGEDITIONUID: case EBML_ID_TAGCHAPTERUID: - $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + case EBML_ID_TAGATTACHMENTUID: + $targets_entry[$sub_sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled tag.targets element['.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; - break; + $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement); } - $offset = $sub_sub_subelement_end; } - $tag_entry[$sub_subelement_idname][] = $targets_entry; + $tag_entry[$sub_subelement['id_name']] = $targets_entry; break; + case EBML_ID_SIMPLETAG: - $simpletag_entry = array(); - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { - case EBML_ID_TAGNAME: - case EBML_ID_TAGLANGUAGE: - case EBML_ID_TAGSTRING: - case EBML_ID_TAGBINARY: - $simpletag_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); - break; - case EBML_ID_TAGDEFAULT: - $simpletag_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); - break; - default: - $this->warnings[] = 'Unhandled tag.simpletag element['.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; - break; - } - $offset = $sub_sub_subelement_end; - } - $tag_entry[$sub_subelement_idname][] = $simpletag_entry; - break; - case EBML_ID_TARGETTYPE: - $tag_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); - break; - case EBML_ID_TRACKUID: - $tag_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']); break; + default: - $this->warnings[] = 'Unhandled tags.tag element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('tags.tag', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $tags_entry['tags'][] = $tag_entry; - $offset = $sub_subelement_end; + $tags_entry[] = $tag_entry; break; + default: - $this->warnings[] = 'Unhandled tags element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('tags', __LINE__, $subelement); } - $offset = $subelement_end; } - $ThisFileInfo['matroska']['tags'] = $tags_entry; + $info['matroska']['tags'] = $tags_entry; break; + case EBML_ID_ATTACHMENTS: // Contain attached files. + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { - case EBML_ID_ATTACHMENTS: - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { case EBML_ID_ATTACHEDFILE: $attachedfile_entry = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_FILEDATA))) { + switch ($sub_subelement['id']) { + case EBML_ID_FILEDESCRIPTION: case EBML_ID_FILENAME: case EBML_ID_FILEMIMETYPE: - $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); + $attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data']; break; case EBML_ID_FILEDATA: - $attachedfile_entry['data_offset'] = $offset; - $attachedfile_entry['data_length'] = $sub_subelement_length; - if ($sub_subelement_length < 1024) { - $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); + $attachedfile_entry['data_offset'] = $this->current_offset; + $attachedfile_entry['data_length'] = $sub_subelement['length']; + + $this->getid3->saveAttachment( + $attachedfile_entry[$sub_subelement['id_name']], + $attachedfile_entry['FileName'], + $attachedfile_entry['data_offset'], + $attachedfile_entry['data_length']); + + if (@$attachedfile_entry[$sub_subelement['id_name']] && is_file($attachedfile_entry[$sub_subelement['id_name']])) { + $attachedfile_entry[$sub_subelement['id_name'].'_filename'] = $attachedfile_entry[$sub_subelement['id_name']]; + unset($attachedfile_entry[$sub_subelement['id_name']]); } + + $this->current_offset = $sub_subelement['end']; break; case EBML_ID_FILEUID: - $attachedfile_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $attachedfile_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; default: - $this->warnings[] = 'Unhandled attachment.attachedfile element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $ThisFileInfo['matroska']['attachments'][] = $attachedfile_entry; - $offset = $sub_subelement_end; + if (!empty($attachedfile_entry['FileData']) && !empty($attachedfile_entry['FileMimeType']) && preg_match('#^image/#i', $attachedfile_entry['FileMimeType'])) { + if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { + $attachedfile_entry['data'] = $attachedfile_entry['FileData']; + $attachedfile_entry['image_mime'] = $attachedfile_entry['FileMimeType']; + $info['matroska']['comments']['picture'][] = array('data' => $attachedfile_entry['data'], 'image_mime' => $attachedfile_entry['image_mime'], 'filename' => (!empty($attachedfile_entry['FileName']) ? $attachedfile_entry['FileName'] : '')); + unset($attachedfile_entry['FileData'], $attachedfile_entry['FileMimeType']); + } + } + if (!empty($attachedfile_entry['image_mime']) && preg_match('#^image/#i', $attachedfile_entry['image_mime'])) { + // don't add a second copy of attached images, which are grouped under the standard location [comments][picture] + } else { + $info['matroska']['attachments'][] = $attachedfile_entry; + } break; + default: - $this->warnings[] = 'Unhandled tags element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('attachments', __LINE__, $subelement); } - $offset = $subelement_end; } break; + case EBML_ID_CHAPTERS: + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { - case EBML_ID_CHAPTERS: // not important to us, contains mostly actual audio/video data, ignore - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $subelement_end = $offset + $subelement_length; - switch ($subelement_id) { case EBML_ID_EDITIONENTRY: $editionentry_entry = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CHAPTERATOM))) { + switch ($sub_subelement['id']) { + case EBML_ID_EDITIONUID: - $editionentry_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $editionentry_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; + case EBML_ID_EDITIONFLAGHIDDEN: case EBML_ID_EDITIONFLAGDEFAULT: case EBML_ID_EDITIONFLAGORDERED: - $editionentry_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $editionentry_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); break; + case EBML_ID_CHAPTERATOM: $chapteratom_entry = array(); - while ($offset < $sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_offset = $offset; - $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; - switch ($sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CHAPTERTRACK, EBML_ID_CHAPTERDISPLAY))) { + switch ($sub_sub_subelement['id']) { + case EBML_ID_CHAPTERSEGMENTUID: case EBML_ID_CHAPTERSEGMENTEDITIONUID: - $chapteratom_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); + $chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; break; + case EBML_ID_CHAPTERFLAGENABLED: case EBML_ID_CHAPTERFLAGHIDDEN: - $chapteratom_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + $chapteratom_entry[$sub_sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + case EBML_ID_CHAPTERUID: case EBML_ID_CHAPTERTIMESTART: case EBML_ID_CHAPTERTIMEEND: - $chapteratom_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); + $chapteratom_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; + case EBML_ID_CHAPTERTRACK: $chaptertrack_entry = array(); - while ($offset < $sub_sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_offset = $offset; - $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); - $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; - switch ($sub_sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + case EBML_ID_CHAPTERTRACKNUMBER: - $chaptertrack_entry[$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); + $chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chaptertrack element['.__LINE__.'] ('.$sub_sub_sub_subelement_id.'::'.$sub_sub_sub_subelement_idname.') at '.$sub_sub_sub_subelement_offset; - break; + $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement); } - $offset = $sub_sub_sub_subelement_end; } - $chapteratom_entry[$sub_sub_subelement_idname][] = $chaptertrack_entry; + $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry; break; + case EBML_ID_CHAPTERDISPLAY: $chapterdisplay_entry = array(); - while ($offset < $sub_sub_subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_offset = $offset; - $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_sub_subelement_id); - $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; - switch ($sub_sub_sub_subelement_id) { + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + case EBML_ID_CHAPSTRING: case EBML_ID_CHAPLANGUAGE: case EBML_ID_CHAPCOUNTRY: - $chapterdisplay_entry[$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); + $chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; + default: - $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chapterdisplay element['.__LINE__.'] ('.$sub_sub_sub_subelement_id.'::'.$sub_sub_sub_subelement_idname.') at '.$sub_sub_sub_subelement_offset; - break; + $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement); } - $offset = $sub_sub_sub_subelement_end; } - $chapteratom_entry[$sub_sub_subelement_idname][] = $chapterdisplay_entry; + $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry; break; + default: - $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom element['.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; - break; + $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement); } - $offset = $sub_sub_subelement_end; } - $editionentry_entry[$sub_subelement_idname][] = $chapteratom_entry; + $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry; break; + default: - $this->warnings[] = 'Unhandled chapters.editionentry element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $ThisFileInfo['matroska']['chapters'][] = $editionentry_entry; - $offset = $sub_subelement_end; + $info['matroska']['chapters'][] = $editionentry_entry; break; + default: - $this->warnings[] = 'Unhandled chapters element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('chapters', __LINE__, $subelement); } - $offset = $subelement_end; } break; - - case EBML_ID_VOID: // padding, ignore - $void_entry = array(); - $void_entry['offset'] = $offset; - $ThisFileInfo['matroska']['void'][] = $void_entry; - break; - - case EBML_ID_CLUSTER: // not important to us, contains mostly actual audio/video data, ignore + case EBML_ID_CLUSTER: // The lower level element containing the (monolithic) Block structure. $cluster_entry = array(); - while ($offset < $element_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $subelement_offset = $offset; -//var_dump($offset); - $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); -//var_dump($subelement_id); -//echo '
    '; - $subelement_idname = $this->EBMLidName($subelement_id); - $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); -//var_dump($subelement_length); - $subelement_end = $offset + $subelement_length; -//exit; - switch ($subelement_id) { + + while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_CLUSTERSILENTTRACKS, EBML_ID_CLUSTERBLOCKGROUP, EBML_ID_CLUSTERSIMPLEBLOCK))) { + switch ($subelement['id']) { + case EBML_ID_CLUSTERTIMECODE: case EBML_ID_CLUSTERPOSITION: case EBML_ID_CLUSTERPREVSIZE: - $cluster_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); + $cluster_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; case EBML_ID_CLUSTERSILENTTRACKS: $cluster_silent_tracks = array(); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + case EBML_ID_CLUSTERSILENTTRACKNUMBER: - $cluster_silent_tracks[] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $cluster_silent_tracks[] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; + default: - $this->warnings[] = 'Unhandled clusters.silenttracks element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $cluster_entry[$subelement_idname][] = $cluster_silent_tracks; - $offset = $sub_subelement_end; + $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks; break; case EBML_ID_CLUSTERBLOCKGROUP: - $cluster_block_group = array('offset'=>$offset); - while ($offset < $subelement_end) { - $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_offset = $offset; - $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); - $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $sub_subelement_end = $offset + $sub_subelement_length; - switch ($sub_subelement_id) { + $cluster_block_group = array('offset' => $this->current_offset); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CLUSTERBLOCK))) { + switch ($sub_subelement['id']) { + case EBML_ID_CLUSTERBLOCK: - $cluster_block_data = array(); - $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); - $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); - $offset += 2; - // unsure whether this is 1 octect or 2 octets? (http://matroska.org/technical/specs/index.html#block_structure) - $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); - $offset += 1; - //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); - $cluster_block_data['flags']['invisible'] = (bool) (($cluster_block_data['flags_raw'] & 0x08) >> 3); - $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); - //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); - $cluster_block_data['flags']['lacing_type'] = $this->MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); - if ($cluster_block_data['flags']['lacing'] != 0) { - $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Number of frames in the lace-1 (uint8) - $offset += 1; - if ($cluster_block_data['flags']['lacing'] != 2) { - $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). - $offset += 1; - } - } - if (!isset($ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { - $ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']] = $offset; - } - $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; + $cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, EBML_ID_CLUSTERBLOCK, $info); break; case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int - $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); + $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int - $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), false, true); + $cluster_block_group[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true); + break; + + case EBML_ID_CLUSTERCODECSTATE: + $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); break; default: - $this->warnings[] = 'Unhandled clusters.blockgroup element['.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; - break; + $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement); } - $offset = $sub_subelement_end; } - $cluster_entry[$subelement_idname][] = $cluster_block_group; - $offset = $sub_subelement_end; + $cluster_entry[$subelement['id_name']][] = $cluster_block_group; + break; + + case EBML_ID_CLUSTERSIMPLEBLOCK: + $cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, EBML_ID_CLUSTERSIMPLEBLOCK, $info); break; default: - $this->warnings[] = 'Unhandled cluster element['.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; - break; + $this->unhandledElement('cluster', __LINE__, $subelement); } - $offset = $subelement_end; + $this->current_offset = $subelement['end']; + } + if (!self::$hide_clusters) { + $info['matroska']['cluster'][] = $cluster_entry; } - $ThisFileInfo['matroska']['cluster'][] = $cluster_entry; // check to see if all the data we need exists already, if so, break out of the loop - if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { - if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { - break 2; + if (!self::$parse_whole_file) { + if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { + if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { + return; + } } } break; default: - if ($element_data['id_name'] == dechex($element_data['id'])) { - $ThisFileInfo['error'][] = 'Unhandled segment['.__LINE__.'] ('.$element_data['id'].') at '.$element_data_offset; - } else { - $this->warnings[] = 'Unhandled segment['.__LINE__.'] ('.$element_data['id'].'::'.$element_data['id_name'].') at '.$element_data['offset']; - } - break; + $this->unhandledElement('segment', __LINE__, $element_data); } - $offset = $element_end; } break; - default: - $ThisFileInfo['error'][] = 'Unhandled chunk['.__LINE__.'] ('.$top_element_id.') at '.$offset; - break; - } - $offset = $top_element_endoffset; - } - - - - if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { - foreach ($ThisFileInfo['matroska']['info'] as $key => $infoarray) { - if (isset($infoarray['Duration'])) { - // TimecodeScale is how many nanoseconds each Duration unit is - $ThisFileInfo['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); - break; - } + $this->unhandledElement('root', __LINE__, $top_element); } } - if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { - foreach ($ThisFileInfo['matroska']['tracks']['tracks'] as $key => $trackarray) { - $track_info = array(); - switch (@$trackarray['TrackType']) { - case 1: // Video - if (@$trackarray['PixelWidth']) { $track_info['resolution_x'] = $trackarray['PixelWidth']; } - if (@$trackarray['PixelHeight']) { $track_info['resolution_y'] = $trackarray['PixelHeight']; } - if (@$trackarray['DisplayWidth']) { $track_info['display_x'] = $trackarray['DisplayWidth']; } - if (@$trackarray['DisplayHeight']) { $track_info['display_y'] = $trackarray['DisplayHeight']; } - if (@$trackarray['DefaultDuration']) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } - if (@$trackarray['CodecID']) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } - if (!empty($trackarray['codec_private_parsed']['fourcc'])) { - $track_info['fourcc'] = $trackarray['codec_private_parsed']['fourcc']; - } - $ThisFileInfo['video']['streams'][] = $track_info; - if (isset($track_info['resolution_x']) && empty($ThisFileInfo['video']['resolution_x'])) { - foreach ($track_info as $key => $value) { - $ThisFileInfo['video'][$key] = $value; - } - } - break; - case 2: // Audio - if (@$trackarray['CodecID']) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } - if (@$trackarray['SamplingFrequency']) { $track_info['sample_rate'] = $trackarray['SamplingFrequency']; } - if (@$trackarray['Channels']) { $track_info['channels'] = $trackarray['Channels']; } - if (@$trackarray['BitDepth']) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } - switch (@$trackarray[$this->EBMLidName(EBML_ID_CODECID)]) { - case 'A_PCM/INT/LIT': - case 'A_PCM/INT/BIG': - $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; - break; + } - case 'A_AC3': - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - $ac3_thisfileinfo = array('avdataoffset'=>$ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]); - $getid3_ac3 = new getid3_ac3($fd, $ac3_thisfileinfo); - $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $ac3_thisfileinfo; - if (!empty($ac3_thisfileinfo['error'])) { - foreach ($ac3_thisfileinfo['error'] as $newerror) { - $this->warnings[] = 'getid3_ac3() says: ['.$newerror.']'; - } - } - if (!empty($ac3_thisfileinfo['warning'])) { - foreach ($ac3_thisfileinfo['warning'] as $newerror) { - $this->warnings[] = 'getid3_ac3() says: ['.$newerror.']'; - } - } - if (isset($ac3_thisfileinfo['audio']) && is_array($ac3_thisfileinfo['audio'])) { - foreach ($ac3_thisfileinfo['audio'] as $key => $value) { - $track_info[$key] = $value; - } - } - unset($ac3_thisfileinfo); - unset($getid3_ac3); - } else { - $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.ac3.php"'; - } - break; + private function EnsureBufferHasEnoughData($min_data = 1024) + { + if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) { - case 'A_DTS': - $dts_offset = $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]; - // this is a NASTY hack, but sometimes audio data is off by a byte or two and not sure why, email info@getid3.org if you can explain better - fseek($fd, $dts_offset, SEEK_SET); - $magic_test = fread($fd, 8); - for ($i = 0; $i < 4; $i++) { - // look to see if DTS "magic" is here, if so adjust offset by that many bytes - if (substr($magic_test, $i, 4) == "\x7F\xFE\x80\x01") { - $dts_offset += $i; - break; - } - } - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, false)) { - $dts_thisfileinfo = array('avdataoffset'=>$dts_offset); - $getid3_dts = new getid3_dts($fd, $dts_thisfileinfo); - $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $dts_thisfileinfo; - if (!empty($dts_thisfileinfo['error'])) { - foreach ($dts_thisfileinfo['error'] as $newerror) { - $this->warnings[] = 'getid3_dts() says: ['.$newerror.']'; - } - } - if (!empty($dts_thisfileinfo['warning'])) { - foreach ($dts_thisfileinfo['warning'] as $newerror) { - $this->warnings[] = 'getid3_dts() says: ['.$newerror.']'; - } - } - if (isset($dts_thisfileinfo['audio']) && is_array($dts_thisfileinfo['audio'])) { - foreach ($dts_thisfileinfo['audio'] as $key => $value) { - $track_info[$key] = $value; - } - } - unset($dts_thisfileinfo); - unset($getid3_dts); - } else { - $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.dts.php"'; - } - break; - - //case 'A_AAC': - // if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.aac.php', __FILE__, false)) { - // $aac_thisfileinfo = array('avdataoffset'=>$ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]); - // $getid3_aac = new getid3_aac($fd, $aac_thisfileinfo); - // $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $aac_thisfileinfo; - // if (isset($aac_thisfileinfo['audio']) && is_array($aac_thisfileinfo['audio'])) { - // foreach ($aac_thisfileinfo['audio'] as $key => $value) { - // $track_info[$key] = $value; - // } - // } - // unset($aac_thisfileinfo); - // unset($getid3_aac); - // } else { - // $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.aac.php"'; - // } - // break; - - case 'A_MPEG/L3': - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, false)) { - $mp3_thisfileinfo = array( - 'avdataoffset' => $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']], - 'avdataend' => $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']] + 1024, - ); - $getid3_mp3 = new getid3_mp3($fd, $mp3_thisfileinfo); - $getid3_mp3->allow_bruteforce = true; - //getid3_mp3::getOnlyMPEGaudioInfo($fd, $mp3_thisfileinfo, $offset, false); - $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $mp3_thisfileinfo; - if (!empty($mp3_thisfileinfo['error'])) { - foreach ($mp3_thisfileinfo['error'] as $newerror) { - $this->warnings[] = 'getid3_mp3() says: ['.$newerror.']'; - } - } - if (!empty($mp3_thisfileinfo['warning'])) { - foreach ($mp3_thisfileinfo['warning'] as $newerror) { - $this->warnings[] = 'getid3_mp3() says: ['.$newerror.']'; - } - } - if (isset($mp3_thisfileinfo['audio']) && is_array($mp3_thisfileinfo['audio'])) { - foreach ($mp3_thisfileinfo['audio'] as $key => $value) { - $track_info[$key] = $value; - } - } - unset($mp3_thisfileinfo); - unset($getid3_mp3); - } else { - $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.mp3.php"'; - } - break; - - case 'A_VORBIS': - if (isset($trackarray['CodecPrivate'])) { - // this is a NASTY hack, email info@getid3.org if you have a better idea how to get this info out - $found_vorbis = false; - for ($vorbis_offset = 1; $vorbis_offset < 16; $vorbis_offset++) { - if (substr($trackarray['CodecPrivate'], $vorbis_offset, 6) == 'vorbis') { - $vorbis_offset--; - $found_vorbis = true; - break; - } - } - if ($found_vorbis) { - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { - $vorbis_fileinfo = array(); - $oggpageinfo['page_seqno'] = 0; - getid3_ogg::ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $vorbis_fileinfo, $oggpageinfo); - $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $vorbis_fileinfo; - if (!empty($vorbis_fileinfo['error'])) { - foreach ($vorbis_fileinfo['error'] as $newerror) { - $this->warnings[] = 'getid3_ogg() says: ['.$newerror.']'; - } - } - if (!empty($vorbis_fileinfo['warning'])) { - foreach ($vorbis_fileinfo['warning'] as $newerror) { - $this->warnings[] = 'getid3_ogg() says: ['.$newerror.']'; - } - } - if (isset($vorbis_fileinfo['audio']) && is_array($vorbis_fileinfo['audio'])) { - foreach ($vorbis_fileinfo['audio'] as $key => $value) { - $track_info[$key] = $value; - } - } - if (@$vorbis_fileinfo['ogg']['bitrate_average']) { - $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_average']; - } elseif (@$vorbis_fileinfo['ogg']['bitrate_nominal']) { - $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_nominal']; - } - unset($vorbis_fileinfo); - unset($oggpageinfo); - } else { - $this->warnings[] = 'Unable to parse audio data['.__LINE__.'] because cannot include "module.audio.ogg.php"'; - } - } else { - } - } else { - } - break; - - default: - $this->warnings[] = 'Unhandled audio type "'.@$trackarray[$this->EBMLidName(EBML_ID_CODECID)].'"'; - break; - } - - - $ThisFileInfo['audio']['streams'][] = $track_info; - if (isset($track_info['dataformat']) && empty($ThisFileInfo['audio']['dataformat'])) { - foreach ($track_info as $key => $value) { - $ThisFileInfo['audio'][$key] = $value; - } - } - break; - default: - // ignore, do nothing - break; - } + if (!getid3_lib::intValueSupported($this->current_offset + $this->getid3->fread_buffer_size())) { + $this->getid3->info['error'][] = 'EBML parser: cannot read past '.$this->current_offset; + return false; } - } - if ($this->hide_clusters) { - // too much data returned that is usually not useful - if (isset($ThisFileInfo['matroska']['segments']) && is_array($ThisFileInfo['matroska']['segments'])) { - foreach ($ThisFileInfo['matroska']['segments'] as $key => $segmentsarray) { - if ($segmentsarray['id'] == EBML_ID_CLUSTER) { - unset($ThisFileInfo['matroska']['segments'][$key]); - } - } + fseek($this->getid3->fp, $this->current_offset, SEEK_SET); + $this->EBMLbuffer_offset = $this->current_offset; + $this->EBMLbuffer = fread($this->getid3->fp, max($min_data, $this->getid3->fread_buffer_size())); + $this->EBMLbuffer_length = strlen($this->EBMLbuffer); + + if ($this->EBMLbuffer_length == 0 && feof($this->getid3->fp)) { + $this->getid3->info['error'][] = 'EBML parser: ran out of file at offset '.$this->current_offset; + return false; } - if (isset($ThisFileInfo['matroska']['seek']) && is_array($ThisFileInfo['matroska']['seek'])) { - foreach ($ThisFileInfo['matroska']['seek'] as $key => $seekarray) { - if ($seekarray['target_id'] == EBML_ID_CLUSTER) { - unset($ThisFileInfo['matroska']['seek'][$key]); - } - } - } - //unset($ThisFileInfo['matroska']['cluster']); - //unset($ThisFileInfo['matroska']['track_data_offsets']); - } - - if (!empty($ThisFileInfo['video']['streams'])) { - $ThisFileInfo['mime_type'] = 'video/x-matroska'; - } elseif (!empty($ThisFileInfo['video']['streams'])) { - $ThisFileInfo['mime_type'] = 'audio/x-matroska'; - } elseif (isset($ThisFileInfo['mime_type'])) { - unset($ThisFileInfo['mime_type']); - } - - foreach ($this->warnings as $key => $value) { - $ThisFileInfo['warning'][] = $value; } return true; } + private function readEBMLint() + { + $actual_offset = $this->current_offset - $this->EBMLbuffer_offset; -/////////////////////////////////////// - - - function EnsureBufferHasEnoughData(&$fd, &$EBMLdata, &$offset, &$EBMLdata_offset) { - $min_data = 1024; - if ($offset > 2147450880) { // 2^31 - 2^15 (2G-32k) - $offset = pow(2,63); - return false; - } elseif (($offset - $EBMLdata_offset) >= (strlen($EBMLdata) - $min_data)) { - fseek($fd, $offset, SEEK_SET); - $EBMLdata_offset = ftell($fd); - $EBMLdata = fread($fd, $this->read_buffer_size); - } - return true; - } - - function readEBMLint(&$string, &$offset, $dataoffset=0) { - $actual_offset = $offset - $dataoffset; - if ($offset > 2147450880) { // 2^31 - 2^15 (2G-32k) - $this->warnings[] = 'aborting readEBMLint() because $offset larger than 2GB'; - return false; - } elseif ($actual_offset >= strlen($string)) { - $this->warnings[] = '$actual_offset > $string in readEBMLint($string['.strlen($string).'], '.$offset.', '.$dataoffset.')'; - return false; - } elseif ($actual_offset < 0) { - $this->warnings[] = '$actual_offset < 0 in readEBMLint($string['.strlen($string).'], '.$offset.', '.$dataoffset.')'; - return false; - } - $first_byte_int = ord($string{$actual_offset}); + // get length of integer + $first_byte_int = ord($this->EBMLbuffer[$actual_offset]); if (0x80 & $first_byte_int) { $length = 1; } elseif (0x40 & $first_byte_int) { @@ -1409,16 +1208,175 @@ class getid3_matroska } elseif (0x01 & $first_byte_int) { $length = 8; } else { - $offset = pow(2,63); // abort processing, skip to end of file - $this->warnings[] = 'invalid EBML integer (leading 0x00) at '.$offset; - return false; + throw new Exception('invalid EBML integer (leading 0x00) at '.$this->current_offset); } - $int_value = $this->EBML2Int(substr($string, $actual_offset, $length)); - $offset += $length; + + // read + $int_value = self::EBML2Int(substr($this->EBMLbuffer, $actual_offset, $length)); + $this->current_offset += $length; + return $int_value; } - function EBML2Int($EBMLstring) { + private function readEBMLelementData($length) + { + $data = substr($this->EBMLbuffer, $this->current_offset - $this->EBMLbuffer_offset, $length); + $this->current_offset += $length; + + return $data; + } + + private function getEBMLelement(&$element, $parent_end, $get_data = false) + { + if ($this->current_offset >= $parent_end) { + return false; + } + + if (!$this->EnsureBufferHasEnoughData()) { + $this->current_offset = PHP_INT_MAX; // do not exit parser right now, allow to finish current loop to gather maximum information + return false; + } + + $element = array(); + + // set offset + $element['offset'] = $this->current_offset; + + // get ID + $element['id'] = $this->readEBMLint(); + + // get name + $element['id_name'] = self::EBMLidName($element['id']); + + // get length + $element['length'] = $this->readEBMLint(); + + // get end offset + $element['end'] = $this->current_offset + $element['length']; + + // get raw data + $dont_parse = (in_array($element['id'], $this->unuseful_elements) || $element['id_name'] == dechex($element['id'])); + if (($get_data === true || (is_array($get_data) && !in_array($element['id'], $get_data))) && !$dont_parse) { + $element['data'] = $this->readEBMLelementData($element['length'], $element); + } + + return true; + } + + private function unhandledElement($type, $line, $element) + { + // warn only about unknown and missed elements, not about unuseful + if (!in_array($element['id'], $this->unuseful_elements)) { + $this->getid3->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']); + } + + // increase offset for unparsed elements + if (!isset($element['data'])) { + $this->current_offset = $element['end']; + } + } + + private function ExtractCommentsSimpleTag($SimpleTagArray) + { + if (!empty($SimpleTagArray['SimpleTag'])) { + foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) { + if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) { + $this->getid3->info['matroska']['comments'][strtolower($SimpleTagData['TagName'])][] = $SimpleTagData['TagString']; + } + if (!empty($SimpleTagData['SimpleTag'])) { + $this->ExtractCommentsSimpleTag($SimpleTagData); + } + } + } + + return true; + } + + private function HandleEMBLSimpleTag($parent_end) + { + $simpletag_entry = array(); + + while ($this->getEBMLelement($element, $parent_end, array(EBML_ID_SIMPLETAG))) { + switch ($element['id']) { + + case EBML_ID_TAGNAME: + case EBML_ID_TAGLANGUAGE: + case EBML_ID_TAGSTRING: + case EBML_ID_TAGBINARY: + $simpletag_entry[$element['id_name']] = $element['data']; + break; + + case EBML_ID_SIMPLETAG: + $simpletag_entry[$element['id_name']][] = $this->HandleEMBLSimpleTag($element['end']); + break; + + case EBML_ID_TAGDEFAULT: + $simpletag_entry[$element['id_name']] = (bool)getid3_lib::BigEndian2Int($element['data']); + break; + + default: + $this->unhandledElement('tag.simpletag', __LINE__, $element); + } + } + + return $simpletag_entry; + } + + private function HandleEMBLClusterBlock($element, $block_type, &$info) + { + // http://www.matroska.org/technical/specs/index.html#block_structure + // http://www.matroska.org/technical/specs/index.html#simpleblock_structure + + $cluster_block_data = array(); + $cluster_block_data['tracknumber'] = $this->readEBMLint(); + $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(2)); + $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + + if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { + $cluster_block_data['flags']['keyframe'] = (($cluster_block_data['flags_raw'] & 0x80) >> 7); + //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0x70) >> 4); + } + else { + //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); + } + $cluster_block_data['flags']['invisible'] = (bool)(($cluster_block_data['flags_raw'] & 0x08) >> 3); + $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing + if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { + $cluster_block_data['flags']['discardable'] = (($cluster_block_data['flags_raw'] & 0x01)); + } + else { + //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); + } + $cluster_block_data['flags']['lacing_type'] = self::MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); + + // Lace (when lacing bit is set) + if ($cluster_block_data['flags']['lacing'] > 0) { + $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8) + if ($cluster_block_data['flags']['lacing'] != 0x02) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). + for ($i = 1; $i < $cluster_block_data['lace_frames']; $i ++) { + if ($cluster_block_data['flags']['lacing'] == 0x03) { // EBML lacing + // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing. + $cluster_block_data['lace_frames_size'][$i] = $this->readEBMLint(); + } + else { // Xiph lacing + $cluster_block_data['lace_frames_size'][$i] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + } + } + } + } + + if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { + $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $this->current_offset; + $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset; + } + + // set offset manually + $this->current_offset = $element['end']; + + return $cluster_block_data; + } + + private static function EBML2Int($EBMLstring) { // http://matroska.org/specs/ // Element ID coded with an UTF-8 like system: @@ -1438,38 +1396,50 @@ class getid3_matroska // 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 // 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 - $first_byte_int = ord($EBMLstring{0}); + $first_byte_int = ord($EBMLstring[0]); if (0x80 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x7F); + $EBMLstring[0] = chr($first_byte_int & 0x7F); } elseif (0x40 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x3F); + $EBMLstring[0] = chr($first_byte_int & 0x3F); } elseif (0x20 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x1F); + $EBMLstring[0] = chr($first_byte_int & 0x1F); } elseif (0x10 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x0F); + $EBMLstring[0] = chr($first_byte_int & 0x0F); } elseif (0x08 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x07); + $EBMLstring[0] = chr($first_byte_int & 0x07); } elseif (0x04 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x03); + $EBMLstring[0] = chr($first_byte_int & 0x03); } elseif (0x02 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x01); + $EBMLstring[0] = chr($first_byte_int & 0x01); } elseif (0x01 & $first_byte_int) { - $EBMLstring{0} = chr($first_byte_int & 0x00); - } else { - return false; + $EBMLstring[0] = chr($first_byte_int & 0x00); } + return getid3_lib::BigEndian2Int($EBMLstring); } - - function EBMLdate2unix($EBMLdatestamp) { + private static function EBMLdate2unix($EBMLdatestamp) { // Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC) // 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC return round(($EBMLdatestamp / 1000000000) + 978307200); } + public static function MatroskaTargetTypeValue($target_type) { + // http://www.matroska.org/technical/specs/tagging/index.html + static $MatroskaTargetTypeValue = array(); + if (empty($MatroskaTargetTypeValue)) { + $MatroskaTargetTypeValue[10] = 'A: ~ V:shot'; // the lowest hierarchy found in music or movies + $MatroskaTargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene'; // corresponds to parts of a track for audio (like a movement) + $MatroskaTargetTypeValue[30] = 'A:track/song ~ V:chapter'; // the common parts of an album or a movie + $MatroskaTargetTypeValue[40] = 'A:part/session ~ V:part/session'; // when an album or episode has different logical parts + $MatroskaTargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert'; // the most common grouping level of music and video (equals to an episode for TV series) + $MatroskaTargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume'; // a list of lower levels grouped together + $MatroskaTargetTypeValue[70] = 'A:collection ~ V:collection'; // the high hierarchy consisting of many different lower items + } + return (isset($MatroskaTargetTypeValue[$target_type]) ? $MatroskaTargetTypeValue[$target_type] : $target_type); + } - function MatroskaBlockLacingType($lacingtype) { + public static function MatroskaBlockLacingType($lacingtype) { // http://matroska.org/technical/specs/index.html#block_structure static $MatroskaBlockLacingType = array(); if (empty($MatroskaBlockLacingType)) { @@ -1481,7 +1451,7 @@ class getid3_matroska return (isset($MatroskaBlockLacingType[$lacingtype]) ? $MatroskaBlockLacingType[$lacingtype] : $lacingtype); } - function MatroskaCodecIDtoCommonName($codecid) { + public static function MatroskaCodecIDtoCommonName($codecid) { // http://www.matroska.org/technical/specs/codecid/index.html static $MatroskaCodecIDlist = array(); if (empty($MatroskaCodecIDlist)) { @@ -1509,18 +1479,20 @@ class getid3_matroska $MatroskaCodecIDlist['V_MPEG4/ISO/ASP'] = 'mpeg4'; $MatroskaCodecIDlist['V_MPEG4/ISO/AVC'] = 'h264'; $MatroskaCodecIDlist['V_MPEG4/ISO/SP'] = 'mpeg4'; + $MatroskaCodecIDlist['V_VP8'] = 'vp8'; + $MatroskaCodecIDlist['V_MS/VFW/FOURCC'] = 'riff'; + $MatroskaCodecIDlist['A_MS/ACM'] = 'riff'; } return (isset($MatroskaCodecIDlist[$codecid]) ? $MatroskaCodecIDlist[$codecid] : $codecid); } - function EBMLidName($value) { + private static function EBMLidName($value) { static $EBMLidList = array(); if (empty($EBMLidList)) { $EBMLidList[EBML_ID_ASPECTRATIOTYPE] = 'AspectRatioType'; $EBMLidList[EBML_ID_ATTACHEDFILE] = 'AttachedFile'; $EBMLidList[EBML_ID_ATTACHMENTLINK] = 'AttachmentLink'; $EBMLidList[EBML_ID_ATTACHMENTS] = 'Attachments'; - $EBMLidList[EBML_ID_ATTACHMENTUID] = 'AttachmentUID'; $EBMLidList[EBML_ID_AUDIO] = 'Audio'; $EBMLidList[EBML_ID_BITDEPTH] = 'BitDepth'; $EBMLidList[EBML_ID_CHANNELPOSITIONS] = 'ChannelPositions'; @@ -1623,6 +1595,7 @@ class getid3_matroska $EBMLidList[EBML_ID_DOCTYPEREADVERSION] = 'DocTypeReadVersion'; $EBMLidList[EBML_ID_DOCTYPEVERSION] = 'DocTypeVersion'; $EBMLidList[EBML_ID_DURATION] = 'Duration'; + $EBMLidList[EBML_ID_EBML] = 'EBML'; $EBMLidList[EBML_ID_EBMLMAXIDLENGTH] = 'EBMLMaxIDLength'; $EBMLidList[EBML_ID_EBMLMAXSIZELENGTH] = 'EBMLMaxSizeLength'; $EBMLidList[EBML_ID_EBMLREADVERSION] = 'EBMLReadVersion'; @@ -1667,6 +1640,7 @@ class getid3_matroska $EBMLidList[EBML_ID_SEEKHEAD] = 'SeekHead'; $EBMLidList[EBML_ID_SEEKID] = 'SeekID'; $EBMLidList[EBML_ID_SEEKPOSITION] = 'SeekPosition'; + $EBMLidList[EBML_ID_SEGMENT] = 'Segment'; $EBMLidList[EBML_ID_SEGMENTFAMILY] = 'SegmentFamily'; $EBMLidList[EBML_ID_SEGMENTFILENAME] = 'SegmentFilename'; $EBMLidList[EBML_ID_SEGMENTUID] = 'SegmentUID'; @@ -1674,6 +1648,7 @@ class getid3_matroska $EBMLidList[EBML_ID_CLUSTERSLICES] = 'ClusterSlices'; $EBMLidList[EBML_ID_STEREOMODE] = 'StereoMode'; $EBMLidList[EBML_ID_TAG] = 'Tag'; + $EBMLidList[EBML_ID_TAGATTACHMENTUID] = 'TagAttachmentUID'; $EBMLidList[EBML_ID_TAGBINARY] = 'TagBinary'; $EBMLidList[EBML_ID_TAGCHAPTERUID] = 'TagChapterUID'; $EBMLidList[EBML_ID_TAGDEFAULT] = 'TagDefault'; @@ -1704,8 +1679,27 @@ class getid3_matroska $EBMLidList[EBML_ID_VOID] = 'Void'; $EBMLidList[EBML_ID_WRITINGAPP] = 'WritingApp'; } + return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); } + + private static function getDefaultStreamInfo($streams) + { + foreach (array_reverse($streams) as $stream) { + if ($stream['default']) { + break; + } + } + unset($stream['default']); + if (isset($stream['name'])) { + unset($stream['name']); + } + + $info = $stream; + $info['streams'] = $streams; + + return $info; + } } diff --git a/apps/media/getID3/getid3/module.audio-video.mpeg.php b/3rdparty/getid3/module.audio-video.mpeg.php similarity index 60% rename from apps/media/getID3/getid3/module.audio-video.mpeg.php rename to 3rdparty/getid3/module.audio-video.mpeg.php index 8f48784849..499b740c39 100644 --- a/apps/media/getID3/getid3/module.audio-video.mpeg.php +++ b/3rdparty/getid3/module.audio-video.mpeg.php @@ -25,17 +25,19 @@ define('GETID3_MPEG_VIDEO_GROUP_START', "\x00\x00\x01\xB8"); define('GETID3_MPEG_AUDIO_START', "\x00\x00\x01\xC0"); -class getid3_mpeg +class getid3_mpeg extends getid3_handler { - function getid3_mpeg(&$fd, &$ThisFileInfo) { - if ($ThisFileInfo['avdataend'] <= $ThisFileInfo['avdataoffset']) { - $ThisFileInfo['error'][] = '"avdataend" ('.$ThisFileInfo['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$ThisFileInfo['avdataoffset'].')'; + function Analyze() { + $info = &$this->getid3->info; + + if ($info['avdataend'] <= $info['avdataoffset']) { + $info['error'][] = '"avdataend" ('.$info['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$info['avdataoffset'].')'; return false; } - $ThisFileInfo['fileformat'] = 'mpeg'; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MPEGstreamData = fread($fd, min(100000, $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])); + $info['fileformat'] = 'mpeg'; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $MPEGstreamData = fread($this->getid3->fp, min(100000, $info['avdataend'] - $info['avdataoffset'])); $MPEGstreamDataLength = strlen($MPEGstreamData); $foundVideo = true; @@ -62,7 +64,7 @@ class getid3_mpeg // non-intra quant. matrix flag 1 bit // non-intra quant. matrix values 512 bits (present if matrix flag == 1) - $ThisFileInfo['video']['dataformat'] = 'mpeg'; + $info['video']['dataformat'] = 'mpeg'; $VideoChunkOffset += (strlen(GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1); @@ -75,71 +77,71 @@ class getid3_mpeg $assortedinformation = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4)); $VideoChunkOffset += 4; - $ThisFileInfo['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size - $ThisFileInfo['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size - $ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4; - $ThisFileInfo['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F); + $info['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size + $info['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size + $info['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4; + $info['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F); - $ThisFileInfo['mpeg']['video']['framesize_horizontal'] = $ThisFileInfo['mpeg']['video']['raw']['framesize_horizontal']; - $ThisFileInfo['mpeg']['video']['framesize_vertical'] = $ThisFileInfo['mpeg']['video']['raw']['framesize_vertical']; + $info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal']; + $info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical']; - $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio']); - $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio']); - $ThisFileInfo['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($ThisFileInfo['mpeg']['video']['raw']['frame_rate']); + $info['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); + $info['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); + $info['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($info['mpeg']['video']['raw']['frame_rate']); - $ThisFileInfo['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18)); - $ThisFileInfo['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1)); - $ThisFileInfo['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10)); - $ThisFileInfo['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1)); - $ThisFileInfo['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1)); - if ($ThisFileInfo['mpeg']['video']['raw']['intra_quant_flag']) { + $info['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18)); + $info['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1)); + $info['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10)); + $info['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1)); + $info['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1)); + if ($info['mpeg']['video']['raw']['intra_quant_flag']) { // read 512 bits - $ThisFileInfo['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); + $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); $VideoChunkOffset += 64; - $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($ThisFileInfo['mpeg']['video']['raw']['intra_quant'], 511, 1)); - $ThisFileInfo['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511); + $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($info['mpeg']['video']['raw']['intra_quant'], 511, 1)); + $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511); - if ($ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag']) { - $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); + if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { + $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); $VideoChunkOffset += 64; } } else { - $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)); - if ($ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag']) { - $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); + $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)); + if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { + $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); $VideoChunkOffset += 64; } } - if ($ThisFileInfo['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits + if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits - $ThisFileInfo['warning'][] = 'This version of getID3() ['.GETID3_VERSION.'] cannot determine average bitrate of VBR MPEG video files'; - $ThisFileInfo['mpeg']['video']['bitrate_mode'] = 'vbr'; + $info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files'; + $info['mpeg']['video']['bitrate_mode'] = 'vbr'; } else { - $ThisFileInfo['mpeg']['video']['bitrate'] = $ThisFileInfo['mpeg']['video']['raw']['bitrate'] * 400; - $ThisFileInfo['mpeg']['video']['bitrate_mode'] = 'cbr'; - $ThisFileInfo['video']['bitrate'] = $ThisFileInfo['mpeg']['video']['bitrate']; + $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400; + $info['mpeg']['video']['bitrate_mode'] = 'cbr'; + $info['video']['bitrate'] = $info['mpeg']['video']['bitrate']; } - $ThisFileInfo['video']['resolution_x'] = $ThisFileInfo['mpeg']['video']['framesize_horizontal']; - $ThisFileInfo['video']['resolution_y'] = $ThisFileInfo['mpeg']['video']['framesize_vertical']; - $ThisFileInfo['video']['frame_rate'] = $ThisFileInfo['mpeg']['video']['frame_rate']; - $ThisFileInfo['video']['bitrate_mode'] = $ThisFileInfo['mpeg']['video']['bitrate_mode']; - $ThisFileInfo['video']['pixel_aspect_ratio'] = $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio']; - $ThisFileInfo['video']['lossless'] = false; - $ThisFileInfo['video']['bits_per_sample'] = 24; + $info['video']['resolution_x'] = $info['mpeg']['video']['framesize_horizontal']; + $info['video']['resolution_y'] = $info['mpeg']['video']['framesize_vertical']; + $info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate']; + $info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode']; + $info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio']; + $info['video']['lossless'] = false; + $info['video']['bits_per_sample'] = 24; } else { - $ThisFileInfo['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?'; + $info['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?'; } @@ -151,9 +153,9 @@ class getid3_mpeg //difference between MPEG-1 and MPEG-2 video streams. if (substr($MPEGstreamData, $VideoChunkOffset, 4) == GETID3_MPEG_VIDEO_EXTENSION_START) { - $ThisFileInfo['video']['codec'] = 'MPEG-2'; + $info['video']['codec'] = 'MPEG-2'; } else { - $ThisFileInfo['video']['codec'] = 'MPEG-1'; + $info['video']['codec'] = 'MPEG-1'; } @@ -165,33 +167,38 @@ class getid3_mpeg } } + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info = $info; + $getid3_mp3 = new getid3_mp3($getid3_temp); for ($i = 0; $i <= 7; $i++) { // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after // I have no idea why or what the difference is, so this is a stupid hack. // If anybody has any better idea of what's going on, please let me know - info@getid3.org - - $dummy = $ThisFileInfo; - if (getid3_mp3::decodeMPEGaudioHeader($fd, ($AudioChunkOffset + 3) + 8 + $i, $dummy, false)) { - $ThisFileInfo = $dummy; - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; - $ThisFileInfo['audio']['lossless'] = false; + fseek($getid3_temp->fp, ftell($this->getid3->fp), SEEK_SET); + $getid3_temp->info = $info; // only overwrite real data if valid header found + if ($getid3_mp3->decodeMPEGaudioHeader(($AudioChunkOffset + 3) + 8 + $i, $getid3_temp->info, false)) { + $info = $getid3_temp->info; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; + unset($getid3_temp, $getid3_mp3); break 2; - } } + unset($getid3_temp, $getid3_mp3); } // Temporary hack to account for interleaving overhead: - if (!empty($ThisFileInfo['video']['bitrate']) && !empty($ThisFileInfo['audio']['bitrate'])) { - $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / ($ThisFileInfo['video']['bitrate'] + $ThisFileInfo['audio']['bitrate']); + if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) { + $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']); // Interleaved MPEG audio/video files have a certain amount of overhead that varies // by both video and audio bitrates, and not in any sensible, linear/logarithmic patter // Use interpolated lookup tables to approximately guess how much is overhead, because // playtime is calculated as filesize / total-bitrate - $ThisFileInfo['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($ThisFileInfo['video']['bitrate'], $ThisFileInfo['audio']['bitrate']); + $info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']); - //switch ($ThisFileInfo['video']['bitrate']) { + //switch ($info['video']['bitrate']) { // case('5000000'): // $multiplier = 0.93292642112380355828048824319889; // break; @@ -208,10 +215,10 @@ class getid3_mpeg // $multiplier = 1; // break; //} - //$ThisFileInfo['playtime_seconds'] *= $multiplier; - //$ThisFileInfo['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; - if ($ThisFileInfo['video']['bitrate'] < 50000) { - $ThisFileInfo['warning'][] = 'Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'; + //$info['playtime_seconds'] *= $multiplier; + //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; + if ($info['video']['bitrate'] < 50000) { + $info['warning'][] = 'Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'; } } diff --git a/3rdparty/getid3/module.audio-video.nsv.php b/3rdparty/getid3/module.audio-video.nsv.php new file mode 100644 index 0000000000..5a587e6720 --- /dev/null +++ b/3rdparty/getid3/module.audio-video.nsv.php @@ -0,0 +1,226 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.nsv.php // +// module for analyzing Nullsoft NSV files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_nsv extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $NSVheader = fread($this->getid3->fp, 4); + + switch ($NSVheader) { + case 'NSVs': + if ($this->getNSVsHeaderFilepointer(0)) { + $info['fileformat'] = 'nsv'; + $info['audio']['dataformat'] = 'nsv'; + $info['video']['dataformat'] = 'nsv'; + $info['audio']['lossless'] = false; + $info['video']['lossless'] = false; + } + break; + + case 'NSVf': + if ($this->getNSVfHeaderFilepointer(0)) { + $info['fileformat'] = 'nsv'; + $info['audio']['dataformat'] = 'nsv'; + $info['video']['dataformat'] = 'nsv'; + $info['audio']['lossless'] = false; + $info['video']['lossless'] = false; + $this->getNSVsHeaderFilepointer($info['nsv']['NSVf']['header_length']); + } + break; + + default: + $info['error'][] = 'Expecting "NSVs" or "NSVf" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($NSVheader).'"'; + return false; + break; + } + + if (!isset($info['nsv']['NSVf'])) { + $info['warning'][] = 'NSVf header not present - cannot calculate playtime or bitrate'; + } + + return true; + } + + function getNSVsHeaderFilepointer($fileoffset) { + $info = &$this->getid3->info; + fseek($this->getid3->fp, $fileoffset, SEEK_SET); + $NSVsheader = fread($this->getid3->fp, 28); + $offset = 0; + + $info['nsv']['NSVs']['identifier'] = substr($NSVsheader, $offset, 4); + $offset += 4; + + if ($info['nsv']['NSVs']['identifier'] != 'NSVs') { + $info['error'][] = 'expected "NSVs" at offset ('.$fileoffset.'), found "'.$info['nsv']['NSVs']['identifier'].'" instead'; + unset($info['nsv']['NSVs']); + return false; + } + + $info['nsv']['NSVs']['offset'] = $fileoffset; + + $info['nsv']['NSVs']['video_codec'] = substr($NSVsheader, $offset, 4); + $offset += 4; + $info['nsv']['NSVs']['audio_codec'] = substr($NSVsheader, $offset, 4); + $offset += 4; + $info['nsv']['NSVs']['resolution_x'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); + $offset += 2; + $info['nsv']['NSVs']['resolution_y'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); + $offset += 2; + + $info['nsv']['NSVs']['framerate_index'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$info['nsv']['NSVs']['unknown1b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$info['nsv']['NSVs']['unknown1c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$info['nsv']['NSVs']['unknown1d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$info['nsv']['NSVs']['unknown2a'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$info['nsv']['NSVs']['unknown2b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$info['nsv']['NSVs']['unknown2c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$info['nsv']['NSVs']['unknown2d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + + switch ($info['nsv']['NSVs']['audio_codec']) { + case 'PCM ': + $info['nsv']['NSVs']['bits_channel'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + $info['nsv']['NSVs']['channels'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + $info['nsv']['NSVs']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); + $offset += 2; + + $info['audio']['sample_rate'] = $info['nsv']['NSVs']['sample_rate']; + break; + + case 'MP3 ': + case 'NONE': + default: + //$info['nsv']['NSVs']['unknown3'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 4)); + $offset += 4; + break; + } + + $info['video']['resolution_x'] = $info['nsv']['NSVs']['resolution_x']; + $info['video']['resolution_y'] = $info['nsv']['NSVs']['resolution_y']; + $info['nsv']['NSVs']['frame_rate'] = $this->NSVframerateLookup($info['nsv']['NSVs']['framerate_index']); + $info['video']['frame_rate'] = $info['nsv']['NSVs']['frame_rate']; + $info['video']['bits_per_sample'] = 24; + $info['video']['pixel_aspect_ratio'] = (float) 1; + + return true; + } + + function getNSVfHeaderFilepointer($fileoffset, $getTOCoffsets=false) { + $info = &$this->getid3->info; + fseek($this->getid3->fp, $fileoffset, SEEK_SET); + $NSVfheader = fread($this->getid3->fp, 28); + $offset = 0; + + $info['nsv']['NSVf']['identifier'] = substr($NSVfheader, $offset, 4); + $offset += 4; + + if ($info['nsv']['NSVf']['identifier'] != 'NSVf') { + $info['error'][] = 'expected "NSVf" at offset ('.$fileoffset.'), found "'.$info['nsv']['NSVf']['identifier'].'" instead'; + unset($info['nsv']['NSVf']); + return false; + } + + $info['nsv']['NSVs']['offset'] = $fileoffset; + + $info['nsv']['NSVf']['header_length'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $info['nsv']['NSVf']['file_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + + if ($info['nsv']['NSVf']['file_size'] > $info['avdataend']) { + $info['warning'][] = 'truncated file - NSVf header indicates '.$info['nsv']['NSVf']['file_size'].' bytes, file actually '.$info['avdataend'].' bytes'; + } + + $info['nsv']['NSVf']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $info['nsv']['NSVf']['meta_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $info['nsv']['NSVf']['TOC_entries_1'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $info['nsv']['NSVf']['TOC_entries_2'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + + if ($info['nsv']['NSVf']['playtime_ms'] == 0) { + $info['error'][] = 'Corrupt NSV file: NSVf.playtime_ms == zero'; + return false; + } + + $NSVfheader .= fread($this->getid3->fp, $info['nsv']['NSVf']['meta_size'] + (4 * $info['nsv']['NSVf']['TOC_entries_1']) + (4 * $info['nsv']['NSVf']['TOC_entries_2'])); + $NSVfheaderlength = strlen($NSVfheader); + $info['nsv']['NSVf']['metadata'] = substr($NSVfheader, $offset, $info['nsv']['NSVf']['meta_size']); + $offset += $info['nsv']['NSVf']['meta_size']; + + if ($getTOCoffsets) { + $TOCcounter = 0; + while ($TOCcounter < $info['nsv']['NSVf']['TOC_entries_1']) { + if ($TOCcounter < $info['nsv']['NSVf']['TOC_entries_1']) { + $info['nsv']['NSVf']['TOC_1'][$TOCcounter] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $TOCcounter++; + } + } + } + + if (trim($info['nsv']['NSVf']['metadata']) != '') { + $info['nsv']['NSVf']['metadata'] = str_replace('`', "\x01", $info['nsv']['NSVf']['metadata']); + $CommentPairArray = explode("\x01".' ', $info['nsv']['NSVf']['metadata']); + foreach ($CommentPairArray as $CommentPair) { + if (strstr($CommentPair, '='."\x01")) { + list($key, $value) = explode('='."\x01", $CommentPair, 2); + $info['nsv']['comments'][strtolower($key)][] = trim(str_replace("\x01", '', $value)); + } + } + } + + $info['playtime_seconds'] = $info['nsv']['NSVf']['playtime_ms'] / 1000; + $info['bitrate'] = ($info['nsv']['NSVf']['file_size'] * 8) / $info['playtime_seconds']; + + return true; + } + + + static function NSVframerateLookup($framerateindex) { + if ($framerateindex <= 127) { + return (float) $framerateindex; + } + static $NSVframerateLookup = array(); + if (empty($NSVframerateLookup)) { + $NSVframerateLookup[129] = (float) 29.970; + $NSVframerateLookup[131] = (float) 23.976; + $NSVframerateLookup[133] = (float) 14.985; + $NSVframerateLookup[197] = (float) 59.940; + $NSVframerateLookup[199] = (float) 47.952; + } + return (isset($NSVframerateLookup[$framerateindex]) ? $NSVframerateLookup[$framerateindex] : false); + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio-video.quicktime.php b/3rdparty/getid3/module.audio-video.quicktime.php new file mode 100644 index 0000000000..3e96ea1a0c --- /dev/null +++ b/3rdparty/getid3/module.audio-video.quicktime.php @@ -0,0 +1,2134 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.quicktime.php // +// module for analyzing Quicktime and MP3-in-MP4 files // +// dependencies: module.audio.mp3.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); + +class getid3_quicktime extends getid3_handler +{ + + var $ReturnAtomData = true; + var $ParseAllPossibleAtoms = false; + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'quicktime'; + $info['quicktime']['hinting'] = false; + $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $offset = 0; + $atomcounter = 0; + + while ($offset < $info['avdataend']) { + if (!getid3_lib::intValueSupported($offset)) { + $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; + break; + } + fseek($this->getid3->fp, $offset, SEEK_SET); + $AtomHeader = fread($this->getid3->fp, 8); + + $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); + $atomname = substr($AtomHeader, 4, 4); + + // 64-bit MOV patch by jlegateØktnc*com + if ($atomsize == 1) { + $atomsize = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 8)); + } + + $info['quicktime'][$atomname]['name'] = $atomname; + $info['quicktime'][$atomname]['size'] = $atomsize; + $info['quicktime'][$atomname]['offset'] = $offset; + + if (($offset + $atomsize) > $info['avdataend']) { + $info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)'; + return false; + } + + if ($atomsize == 0) { + // Furthermore, for historical reasons the list of atoms is optionally + // terminated by a 32-bit integer set to 0. If you are writing a program + // to read user data atoms, you should allow for the terminating 0. + break; + } + switch ($atomname) { + case 'mdat': // Media DATa atom + // 'mdat' contains the actual data for the audio/video + if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { + + $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8; + $OldAVDataEnd = $info['avdataend']; + $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_mp3 = new getid3_mp3($getid3_temp); + if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) { + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $value) { + $info['warning'][] = $value; + } + } + if (!empty($getid3_temp->info['mpeg'])) { + $info['mpeg'] = $getid3_temp->info['mpeg']; + if (isset($info['mpeg']['audio'])) { + $info['audio']['dataformat'] = 'mp3'; + $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3'))); + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + $info['bitrate'] = $info['audio']['bitrate']; + } + } + } + unset($getid3_mp3, $getid3_temp); + $info['avdataend'] = $OldAVDataEnd; + unset($OldAVDataEnd); + + } + break; + + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + break; + + default: + $atomHierarchy = array(); + $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); + break; + } + + $offset += $atomsize; + $atomcounter++; + } + + if (!empty($info['avdataend_tmp'])) { + // this value is assigned to a temp value and then erased because + // otherwise any atoms beyond the 'mdat' atom would not get parsed + $info['avdataend'] = $info['avdataend_tmp']; + unset($info['avdataend_tmp']); + } + + if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { + $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + } + if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { + $info['audio']['bitrate'] = $info['bitrate']; + } + if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { + foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { + $samples_per_second = $samples_count / $info['playtime_seconds']; + if ($samples_per_second > 240) { + // has to be audio samples + } else { + $info['video']['frame_rate'] = $samples_per_second; + break; + } + } + } + if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) { + $info['fileformat'] = 'mp4'; + $info['mime_type'] = 'audio/mp4'; + unset($info['video']['dataformat']); + } + + if (!$this->ReturnAtomData) { + unset($info['quicktime']['moov']); + } + + if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) { + $info['audio']['dataformat'] = 'quicktime'; + } + if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { + $info['video']['dataformat'] = 'quicktime'; + } + + return true; + } + + function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { + // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm + + $info = &$this->getid3->info; + + $atom_parent = array_pop($atomHierarchy); + array_push($atomHierarchy, $atomname); + $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); + $atom_structure['name'] = $atomname; + $atom_structure['size'] = $atomsize; + $atom_structure['offset'] = $baseoffset; +//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8)).'
    '; +//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8), false).'

    '; + switch ($atomname) { + case 'moov': // MOVie container atom + case 'trak': // TRAcK container atom + case 'clip': // CLIPping container atom + case 'matt': // track MATTe container atom + case 'edts': // EDiTS container atom + case 'tref': // Track REFerence container atom + case 'mdia': // MeDIA container atom + case 'minf': // Media INFormation container atom + case 'dinf': // Data INFormation container atom + case 'udta': // User DaTA container atom + case 'cmov': // Compressed MOVie container atom + case 'rmra': // Reference Movie Record Atom + case 'rmda': // Reference Movie Descriptor Atom + case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'ilst': // Item LiST container atom + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + + // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted + $allnumericnames = true; + foreach ($atom_structure['subatoms'] as $subatomarray) { + if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { + $allnumericnames = false; + break; + } + } + if ($allnumericnames) { + $newData = array(); + foreach ($atom_structure['subatoms'] as $subatomarray) { + foreach ($subatomarray['subatoms'] as $newData_subatomarray) { + unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); + $newData[$subatomarray['name']] = $newData_subatomarray; + break; + } + } + $atom_structure['data'] = $newData; + unset($atom_structure['subatoms']); + } + break; + + case "\x00\x00\x00\x01": + case "\x00\x00\x00\x02": + case "\x00\x00\x00\x03": + case "\x00\x00\x00\x04": + case "\x00\x00\x00\x05": + $atomname = getid3_lib::BigEndian2Int($atomname); + $atom_structure['name'] = $atomname; + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'stbl': // Sample TaBLe container atom + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + $isVideo = false; + $framerate = 0; + $framecount = 0; + foreach ($atom_structure['subatoms'] as $key => $value_array) { + if (isset($value_array['sample_description_table'])) { + foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { + if (isset($value_array2['data_format'])) { + switch ($value_array2['data_format']) { + case 'avc1': + case 'mp4v': + // video data + $isVideo = true; + break; + case 'mp4a': + // audio data + break; + } + } + } + } elseif (isset($value_array['time_to_sample_table'])) { + foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { + if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) { + $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); + $framecount = $value_array2['sample_count']; + } + } + } + } + if ($isVideo && $framerate) { + $info['quicktime']['video']['frame_rate'] = $framerate; + $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; + } + if ($isVideo && $framecount) { + $info['quicktime']['video']['frame_count'] = $framecount; + } + break; + + + case 'aART': // Album ARTist + case 'catg': // CaTeGory + case 'covr': // COVeR artwork + case 'cpil': // ComPILation + case 'cprt': // CoPyRighT + case 'desc': // DESCription + case 'disk': // DISK number + case 'egid': // Episode Global ID + case 'gnre': // GeNRE + case 'keyw': // KEYWord + case 'ldes': + case 'pcst': // PodCaST + case 'pgap': // GAPless Playback + case 'purd': // PURchase Date + case 'purl': // Podcast URL + case 'rati': + case 'rndu': + case 'rpdu': + case 'rtng': // RaTiNG + case 'stik': + case 'tmpo': // TeMPO (BPM) + case 'trkn': // TRacK Number + case 'tves': // TV EpiSode + case 'tvnn': // TV Network Name + case 'tvsh': // TV SHow Name + case 'tvsn': // TV SeasoN + case 'akID': // iTunes store account type + case 'apID': + case 'atID': + case 'cmID': + case 'cnID': + case 'geID': + case 'plID': + case 'sfID': // iTunes store country + case '©alb': // ALBum + case '©art': // ARTist + case '©ART': + case '©aut': + case '©cmt': // CoMmenT + case '©com': // COMposer + case '©cpy': + case '©day': // content created year + case '©dir': + case '©ed1': + case '©ed2': + case '©ed3': + case '©ed4': + case '©ed5': + case '©ed6': + case '©ed7': + case '©ed8': + case '©ed9': + case '©enc': + case '©fmt': + case '©gen': // GENre + case '©grp': // GRouPing + case '©hst': + case '©inf': + case '©lyr': // LYRics + case '©mak': + case '©mod': + case '©nam': // full NAMe + case '©ope': + case '©PRD': + case '©prd': + case '©prf': + case '©req': + case '©src': + case '©swr': + case '©too': // encoder + case '©trk': // TRacK + case '©url': + case '©wrn': + case '©wrt': // WRiTer + case '----': // itunes specific + if ($atom_parent == 'udta') { + // User data atom handler + $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); + $atom_structure['data'] = substr($atom_data, 4); + + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + } else { + // Apple item list box atom handler + $atomoffset = 0; + if (substr($atom_data, 2, 2) == "\x10\xB5") { + // not sure what it means, but observed on iPhone4 data. + // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data + while ($atomoffset < strlen($atom_data)) { + $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); + $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); + $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); + switch ($boxsmalltype) { + case "\x10\xB5": + $atom_structure['data'] = $boxsmalldata; + break; + default: + $info['warning'][] = 'Unknown QuickTime smallbox type: "'.getid3_lib::PrintHexBytes($boxsmalltype).'" at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + break; + } + $atomoffset += (4 + $boxsmallsize); + } + } else { + while ($atomoffset < strlen($atom_data)) { + $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); + $boxtype = substr($atom_data, $atomoffset + 4, 4); + $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); + + switch ($boxtype) { + case 'mean': + case 'name': + $atom_structure[$boxtype] = substr($boxdata, 4); + break; + + case 'data': + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); + switch ($atom_structure['flags_raw']) { + case 0: // data flag + case 21: // tmpo/cpil flag + switch ($atomname) { + case 'cpil': + case 'pcst': + case 'pgap': + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + break; + + case 'tmpo': + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); + break; + + case 'disk': + case 'trkn': + $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); + $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); + $atom_structure['data'] = empty($num) ? '' : $num; + $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; + break; + + case 'gnre': + $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); + break; + + case 'rtng': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); + break; + + case 'stik': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); + break; + + case 'sfID': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); + break; + + case 'egid': + case 'purl': + $atom_structure['data'] = substr($boxdata, 8); + break; + + default: + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + } + break; + + case 1: // text flag + case 13: // image flag + default: + $atom_structure['data'] = substr($boxdata, 8); + break; + + } + break; + + default: + $info['warning'][] = 'Unknown QuickTime box type: "'.getid3_lib::PrintHexBytes($boxtype).'" at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + + } + $atomoffset += $boxsize; + } + } + } + $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); + break; + + + case 'play': // auto-PLAY atom + $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + + $info['quicktime']['autoplay'] = $atom_structure['autoplay']; + break; + + + case 'WLOC': // Window LOCation atom + $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); + break; + + + case 'LOOP': // LOOPing atom + case 'SelO': // play SELection Only atom + case 'AllF': // play ALL Frames atom + $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); + break; + + + case 'name': // + case 'MCPS': // Media Cleaner PRo + case '@PRM': // adobe PReMiere version + case '@PRQ': // adobe PRemiere Quicktime version + $atom_structure['data'] = $atom_data; + break; + + + case 'cmvd': // Compressed MooV Data atom + // Code by ubergeekØubergeek*tv based on information from + // http://developer.apple.com/quicktime/icefloe/dispatch012.html + $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + + $CompressedFileData = substr($atom_data, 4); + if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); + } else { + $info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset']; + } + break; + + + case 'dcom': // Data COMpression atom + $atom_structure['compression_id'] = $atom_data; + $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); + break; + + + case 'rdrf': // Reference movie Data ReFerence atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); + + $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); + $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + switch ($atom_structure['reference_type_name']) { + case 'url ': + $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); + break; + + case 'alis': + $atom_structure['file_alias'] = substr($atom_data, 12); + break; + + case 'rsrc': + $atom_structure['resource_alias'] = substr($atom_data, 12); + break; + + default: + $atom_structure['data'] = substr($atom_data, 12); + break; + } + break; + + + case 'rmqu': // Reference Movie QUality atom + $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); + break; + + + case 'rmcs': // Reference Movie Cpu Speed atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + break; + + + case 'rmvc': // Reference Movie Version Check atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); + $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); + break; + + + case 'rmcd': // Reference Movie Component check atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['component_type'] = substr($atom_data, 4, 4); + $atom_structure['component_subtype'] = substr($atom_data, 8, 4); + $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); + $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); + break; + + + case 'rmdr': // Reference Movie Data Rate atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + + $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; + break; + + + case 'rmla': // Reference Movie Language Atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + break; + + + case 'rmla': // Reference Movie Language Atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + break; + + + case 'ptv ': // Print To Video - defines a movie's full screen mode + // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm + $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 + $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 + $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); + $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); + + $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; + $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; + + $ptv_lookup[0] = 'normal'; + $ptv_lookup[1] = 'double'; + $ptv_lookup[2] = 'half'; + $ptv_lookup[3] = 'full'; + $ptv_lookup[4] = 'current'; + if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { + $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; + } else { + $info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'; + } + break; + + + case 'stsd': // Sample Table Sample Description atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stsdEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); + $stsdEntriesDataOffset += 4; + $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); + $stsdEntriesDataOffset += 4; + $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); + $stsdEntriesDataOffset += 6; + $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); + $stsdEntriesDataOffset += 2; + $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); + $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); + + $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); + $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); + $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); + + switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { + + case "\x00\x00\x00\x00": + // audio atom + $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); + $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); + $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); + $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); + $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); + + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'avc1': + case 'mp4v': + $info['fileformat'] = 'mp4'; + $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; + //$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] does not fully support MPEG-4 audio/video streams'; // 2011-02-18: why am I warning about this again? What's not supported? + break; + + case 'qtvr': + $info['video']['dataformat'] = 'quicktimevr'; + break; + + case 'mp4a': + default: + $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; + $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; + $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; + $info['audio']['codec'] = $info['quicktime']['audio']['codec']; + $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; + $info['audio']['channels'] = $info['quicktime']['audio']['channels']; + $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'raw ': // PCM + case 'alac': // Apple Lossless Audio Codec + $info['audio']['lossless'] = true; + break; + default: + $info['audio']['lossless'] = false; + break; + } + break; + } + break; + + default: + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'mp4s': + $info['fileformat'] = 'mp4'; + break; + + default: + // video atom + $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); + $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); + $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); + $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); + $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); + $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); + $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); + $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); + $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); + $atom_structure['sample_description_table'][$i]['video_encoder_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']); + $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); + $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); + + $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); + $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); + + if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { + $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; + $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['video']['codec'] = (($atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; + $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; + + $info['video']['codec'] = $info['quicktime']['video']['codec']; + $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; + } + $info['video']['lossless'] = false; + $info['video']['pixel_aspect_ratio'] = (float) 1; + break; + } + break; + } + switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { + case 'mp4a': + $info['audio']['dataformat'] = 'mp4'; + $info['quicktime']['audio']['codec'] = 'mp4'; + break; + + case '3ivx': + case '3iv1': + case '3iv2': + $info['video']['dataformat'] = '3ivx'; + break; + + case 'xvid': + $info['video']['dataformat'] = 'xvid'; + break; + + case 'mp4v': + $info['video']['dataformat'] = 'mpeg4'; + break; + + case 'divx': + case 'div1': + case 'div2': + case 'div3': + case 'div4': + case 'div5': + case 'div6': + $info['video']['dataformat'] = 'divx'; + break; + + default: + // do nothing + break; + } + unset($atom_structure['sample_description_table'][$i]['data']); + } + break; + + + case 'stts': // Sample Table Time-to-Sample atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $sttsEntriesDataOffset = 8; + //$FrameRateCalculatorArray = array(); + $frames_count = 0; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + + $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; + + // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM + //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { + // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; + // if ($stts_new_framerate <= 60) { + // // some atoms have durations of "1" giving a very large framerate, which probably is not right + // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); + // } + //} + // + //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; + } + $info['quicktime']['stts_framecount'][] = $frames_count; + //$sttsFramesTotal = 0; + //$sttsSecondsTotal = 0; + //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { + // if (($frames_per_second > 60) || ($frames_per_second < 1)) { + // // not video FPS information, probably audio information + // $sttsFramesTotal = 0; + // $sttsSecondsTotal = 0; + // break; + // } + // $sttsFramesTotal += $frame_count; + // $sttsSecondsTotal += $frame_count / $frames_per_second; + //} + //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { + // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { + // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; + // } + //} + break; + + + case 'stss': // Sample Table Sync Sample (key frames) atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stssEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); + $stssEntriesDataOffset += 4; + } + } + break; + + + case 'stsc': // Sample Table Sample-to-Chunk atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stscEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + } + } + break; + + + case 'stsz': // Sample Table SiZe atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $stszEntriesDataOffset = 12; + if ($atom_structure['sample_size'] == 0) { + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); + $stszEntriesDataOffset += 4; + } + } + } + break; + + + case 'stco': // Sample Table Chunk Offset atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); + $stcoEntriesDataOffset += 4; + } + } + break; + + + case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); + $stcoEntriesDataOffset += 8; + } + } + break; + + + case 'dref': // Data REFerence atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $drefDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); + $drefDataOffset += 4; + $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); + $drefDataOffset += 4; + $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); + $drefDataOffset += 1; + $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 + $drefDataOffset += 3; + $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); + $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); + + $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); + } + break; + + + case 'gmin': // base Media INformation atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); + $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); + $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); + $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); + break; + + + case 'smhd': // Sound Media information HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + break; + + + case 'vmhd': // Video Media information HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); + $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); + + $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); + break; + + + case 'hdlr': // HanDLeR reference atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['component_type'] = substr($atom_data, 4, 4); + $atom_structure['component_subtype'] = substr($atom_data, 8, 4); + $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); + $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); + + if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { + $info['video']['dataformat'] = 'quicktimevr'; + } + break; + + + case 'mdhd': // MeDia HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); + $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); + + if ($atom_structure['time_scale'] == 0) { + $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero'; + return false; + } + $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); + + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + break; + + + case 'pnot': // Preview atom + $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" + $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 + $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' + $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 + + $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); + break; + + + case 'crgn': // Clipping ReGioN atom + $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, + $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields + $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. + break; + + + case 'load': // track LOAD settings atom + $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + + $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); + $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); + break; + + + case 'tmcd': // TiMe CoDe atom + case 'chap': // CHAPter list atom + case 'sync': // SYNChronization atom + case 'scpt': // tranSCriPT atom + case 'ssrc': // non-primary SouRCe atom + for ($i = 0; $i < (strlen($atom_data) % 4); $i++) { + $atom_structure['track_id'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $i * 4, 4)); + } + break; + + + case 'elst': // Edit LiST atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { + $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); + $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); + $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); + } + break; + + + case 'kmat': // compressed MATte atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['matte_data_raw'] = substr($atom_data, 4); + break; + + + case 'ctab': // Color TABle atom + $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 + $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 + $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; + for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { + $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); + $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); + $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); + $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); + } + break; + + + case 'mvhd': // MoVie HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); + $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); + $atom_structure['reserved'] = substr($atom_data, 26, 10); + $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); + $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); + $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); + $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); + $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); + $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); + $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); + $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); + $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); + $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); + $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); + $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); + + if ($atom_structure['time_scale'] == 0) { + $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero'; + return false; + } + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); + $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; + $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; + break; + + + case 'tkhd': // TracK HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); + $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); + $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); + $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); + $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); + $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); + $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); + $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); + $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint2_30(substr($atom_data, 64, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); + $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); + $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); + $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); + + $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); + $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); + $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); + $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + + if ($atom_structure['flags']['enabled'] == 1) { + if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { + $info['video']['resolution_x'] = $atom_structure['width']; + $info['video']['resolution_y'] = $atom_structure['height']; + } + $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); + $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); + $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; + $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; + } else { + if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } + if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } + if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } + } + break; + + + case 'iods': // Initial Object DeScriptor atom + // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h + // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html + $offset = 0; + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); + $offset += 3; + $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); + //$offset already adjusted by quicktime_read_mp4_descr_length() + $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); + $offset += 2; + $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + + $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields + for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { + $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); + //$offset already adjusted by quicktime_read_mp4_descr_length() + $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); + $offset += 4; + } + + $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); + $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); + break; + + case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) + $atom_structure['signature'] = substr($atom_data, 0, 4); + $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['fourcc'] = substr($atom_data, 8, 4); + break; + + case 'mdat': // Media DATa atom + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'mdat' data is too big to deal with, contains no useful metadata + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + + // When writing QuickTime files, it is sometimes necessary to update an atom's size. + // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom + // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime + // puts an 8-byte placeholder atom before any atoms it may have to update the size of. + // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the + // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. + // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). + break; + + + case 'nsav': // NoSAVe atom + // http://developer.apple.com/technotes/tn/tn2038.html + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + break; + + case 'ctyp': // Controller TYPe atom (seen on QTVR) + // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt + // some controller names are: + // 0x00 + 'std' for linear movie + // 'none' for no controls + $atom_structure['ctyp'] = substr($atom_data, 0, 4); + $info['quicktime']['controller'] = $atom_structure['ctyp']; + switch ($atom_structure['ctyp']) { + case 'qtvr': + $info['video']['dataformat'] = 'quicktimevr'; + break; + } + break; + + case 'pano': // PANOrama track (seen on QTVR) + $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + break; + + case 'hint': // HINT track + case 'hinf': // + case 'hinv': // + case 'hnti': // + $info['quicktime']['hinting'] = true; + break; + + case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) + for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { + $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); + } + break; + + + // Observed-but-not-handled atom types are just listed here to prevent warnings being generated + case 'FXTC': // Something to do with Adobe After Effects (?) + case 'PrmA': + case 'code': + case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html + case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html + // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] + // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html + // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html + case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + //$atom_structure['data'] = $atom_data; + break; + + case '©xyz': // GPS latitude+longitude+altitude + $atom_structure['data'] = $atom_data; + if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { + @list($all, $latitude, $longitude, $altitude) = $matches; + $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); + $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); + if (!empty($altitude)) { + $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); + } + } else { + $info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'; + } + break; + + case 'NCDT': + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); + break; + case 'NCTH': // Nikon Camera THumbnail image + case 'NCVW': // Nikon Camera preVieW image + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { + $atom_structure['data'] = $atom_data; + $atom_structure['image_mime'] = 'image/jpeg'; + $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); + $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); + } + break; + case 'NCHD': // MakerNoteVersion + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + $atom_structure['data'] = $atom_data; + break; + case 'NCTG': // NikonTags + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG + $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); + break; + case 'NCDB': // NikonTags + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + $atom_structure['data'] = $atom_data; + break; + + case "\x00\x00\x00\x00": + case 'meta': // METAdata atom + // some kind of metacontainer, may contain a big data dump such as: + // mdta keys  mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst   data DEApple 0  (data DE2011-05-11T17:54:04+0200 2  *data DE+52.4936+013.3897+040.247/   data DE4.3.1  data DEiPhone 4 + // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt + + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'data': // metaDATA atom + // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data + $atom_structure['language'] = substr($atom_data, 4 + 0, 2); + $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); + $atom_structure['data'] = substr($atom_data, 4 + 4); + break; + + default: + $info['warning'][] = 'Unknown QuickTime atom type: "'.getid3_lib::PrintHexBytes($atomname).'" at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + break; + } + array_pop($atomHierarchy); + return $atom_structure; + } + + function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { +//echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'

    '; + $atom_structure = false; + $subatomoffset = 0; + $subatomcounter = 0; + if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) { + return false; + } + while ($subatomoffset < strlen($atom_data)) { + $subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4)); + $subatomname = substr($atom_data, $subatomoffset + 4, 4); + $subatomdata = substr($atom_data, $subatomoffset + 8, $subatomsize - 8); + if ($subatomsize == 0) { + // Furthermore, for historical reasons the list of atoms is optionally + // terminated by a 32-bit integer set to 0. If you are writing a program + // to read user data atoms, you should allow for the terminating 0. + return $atom_structure; + } + + $atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); + + $subatomoffset += $subatomsize; + $subatomcounter++; + } + return $atom_structure; + } + + + function quicktime_read_mp4_descr_length($data, &$offset) { + // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html + $num_bytes = 0; + $length = 0; + do { + $b = ord(substr($data, $offset++, 1)); + $length = ($length << 7) | ($b & 0x7F); + } while (($b & 0x80) && ($num_bytes++ < 4)); + return $length; + } + + + function QuicktimeLanguageLookup($languageid) { + static $QuicktimeLanguageLookup = array(); + if (empty($QuicktimeLanguageLookup)) { + $QuicktimeLanguageLookup[0] = 'English'; + $QuicktimeLanguageLookup[1] = 'French'; + $QuicktimeLanguageLookup[2] = 'German'; + $QuicktimeLanguageLookup[3] = 'Italian'; + $QuicktimeLanguageLookup[4] = 'Dutch'; + $QuicktimeLanguageLookup[5] = 'Swedish'; + $QuicktimeLanguageLookup[6] = 'Spanish'; + $QuicktimeLanguageLookup[7] = 'Danish'; + $QuicktimeLanguageLookup[8] = 'Portuguese'; + $QuicktimeLanguageLookup[9] = 'Norwegian'; + $QuicktimeLanguageLookup[10] = 'Hebrew'; + $QuicktimeLanguageLookup[11] = 'Japanese'; + $QuicktimeLanguageLookup[12] = 'Arabic'; + $QuicktimeLanguageLookup[13] = 'Finnish'; + $QuicktimeLanguageLookup[14] = 'Greek'; + $QuicktimeLanguageLookup[15] = 'Icelandic'; + $QuicktimeLanguageLookup[16] = 'Maltese'; + $QuicktimeLanguageLookup[17] = 'Turkish'; + $QuicktimeLanguageLookup[18] = 'Croatian'; + $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)'; + $QuicktimeLanguageLookup[20] = 'Urdu'; + $QuicktimeLanguageLookup[21] = 'Hindi'; + $QuicktimeLanguageLookup[22] = 'Thai'; + $QuicktimeLanguageLookup[23] = 'Korean'; + $QuicktimeLanguageLookup[24] = 'Lithuanian'; + $QuicktimeLanguageLookup[25] = 'Polish'; + $QuicktimeLanguageLookup[26] = 'Hungarian'; + $QuicktimeLanguageLookup[27] = 'Estonian'; + $QuicktimeLanguageLookup[28] = 'Lettish'; + $QuicktimeLanguageLookup[28] = 'Latvian'; + $QuicktimeLanguageLookup[29] = 'Saamisk'; + $QuicktimeLanguageLookup[29] = 'Lappish'; + $QuicktimeLanguageLookup[30] = 'Faeroese'; + $QuicktimeLanguageLookup[31] = 'Farsi'; + $QuicktimeLanguageLookup[31] = 'Persian'; + $QuicktimeLanguageLookup[32] = 'Russian'; + $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)'; + $QuicktimeLanguageLookup[34] = 'Flemish'; + $QuicktimeLanguageLookup[35] = 'Irish'; + $QuicktimeLanguageLookup[36] = 'Albanian'; + $QuicktimeLanguageLookup[37] = 'Romanian'; + $QuicktimeLanguageLookup[38] = 'Czech'; + $QuicktimeLanguageLookup[39] = 'Slovak'; + $QuicktimeLanguageLookup[40] = 'Slovenian'; + $QuicktimeLanguageLookup[41] = 'Yiddish'; + $QuicktimeLanguageLookup[42] = 'Serbian'; + $QuicktimeLanguageLookup[43] = 'Macedonian'; + $QuicktimeLanguageLookup[44] = 'Bulgarian'; + $QuicktimeLanguageLookup[45] = 'Ukrainian'; + $QuicktimeLanguageLookup[46] = 'Byelorussian'; + $QuicktimeLanguageLookup[47] = 'Uzbek'; + $QuicktimeLanguageLookup[48] = 'Kazakh'; + $QuicktimeLanguageLookup[49] = 'Azerbaijani'; + $QuicktimeLanguageLookup[50] = 'AzerbaijanAr'; + $QuicktimeLanguageLookup[51] = 'Armenian'; + $QuicktimeLanguageLookup[52] = 'Georgian'; + $QuicktimeLanguageLookup[53] = 'Moldavian'; + $QuicktimeLanguageLookup[54] = 'Kirghiz'; + $QuicktimeLanguageLookup[55] = 'Tajiki'; + $QuicktimeLanguageLookup[56] = 'Turkmen'; + $QuicktimeLanguageLookup[57] = 'Mongolian'; + $QuicktimeLanguageLookup[58] = 'MongolianCyr'; + $QuicktimeLanguageLookup[59] = 'Pashto'; + $QuicktimeLanguageLookup[60] = 'Kurdish'; + $QuicktimeLanguageLookup[61] = 'Kashmiri'; + $QuicktimeLanguageLookup[62] = 'Sindhi'; + $QuicktimeLanguageLookup[63] = 'Tibetan'; + $QuicktimeLanguageLookup[64] = 'Nepali'; + $QuicktimeLanguageLookup[65] = 'Sanskrit'; + $QuicktimeLanguageLookup[66] = 'Marathi'; + $QuicktimeLanguageLookup[67] = 'Bengali'; + $QuicktimeLanguageLookup[68] = 'Assamese'; + $QuicktimeLanguageLookup[69] = 'Gujarati'; + $QuicktimeLanguageLookup[70] = 'Punjabi'; + $QuicktimeLanguageLookup[71] = 'Oriya'; + $QuicktimeLanguageLookup[72] = 'Malayalam'; + $QuicktimeLanguageLookup[73] = 'Kannada'; + $QuicktimeLanguageLookup[74] = 'Tamil'; + $QuicktimeLanguageLookup[75] = 'Telugu'; + $QuicktimeLanguageLookup[76] = 'Sinhalese'; + $QuicktimeLanguageLookup[77] = 'Burmese'; + $QuicktimeLanguageLookup[78] = 'Khmer'; + $QuicktimeLanguageLookup[79] = 'Lao'; + $QuicktimeLanguageLookup[80] = 'Vietnamese'; + $QuicktimeLanguageLookup[81] = 'Indonesian'; + $QuicktimeLanguageLookup[82] = 'Tagalog'; + $QuicktimeLanguageLookup[83] = 'MalayRoman'; + $QuicktimeLanguageLookup[84] = 'MalayArabic'; + $QuicktimeLanguageLookup[85] = 'Amharic'; + $QuicktimeLanguageLookup[86] = 'Tigrinya'; + $QuicktimeLanguageLookup[87] = 'Galla'; + $QuicktimeLanguageLookup[87] = 'Oromo'; + $QuicktimeLanguageLookup[88] = 'Somali'; + $QuicktimeLanguageLookup[89] = 'Swahili'; + $QuicktimeLanguageLookup[90] = 'Ruanda'; + $QuicktimeLanguageLookup[91] = 'Rundi'; + $QuicktimeLanguageLookup[92] = 'Chewa'; + $QuicktimeLanguageLookup[93] = 'Malagasy'; + $QuicktimeLanguageLookup[94] = 'Esperanto'; + $QuicktimeLanguageLookup[128] = 'Welsh'; + $QuicktimeLanguageLookup[129] = 'Basque'; + $QuicktimeLanguageLookup[130] = 'Catalan'; + $QuicktimeLanguageLookup[131] = 'Latin'; + $QuicktimeLanguageLookup[132] = 'Quechua'; + $QuicktimeLanguageLookup[133] = 'Guarani'; + $QuicktimeLanguageLookup[134] = 'Aymara'; + $QuicktimeLanguageLookup[135] = 'Tatar'; + $QuicktimeLanguageLookup[136] = 'Uighur'; + $QuicktimeLanguageLookup[137] = 'Dzongkha'; + $QuicktimeLanguageLookup[138] = 'JavaneseRom'; + } + return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid'); + } + + function QuicktimeVideoCodecLookup($codecid) { + static $QuicktimeVideoCodecLookup = array(); + if (empty($QuicktimeVideoCodecLookup)) { + $QuicktimeVideoCodecLookup['.SGI'] = 'SGI'; + $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1'; + $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2'; + $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4'; + $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB'; + $QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC'; + $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG'; + $QuicktimeVideoCodecLookup['b16g'] = '16Gray'; + $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray'; + $QuicktimeVideoCodecLookup['b48r'] = '48RGB'; + $QuicktimeVideoCodecLookup['b64a'] = '64ARGB'; + $QuicktimeVideoCodecLookup['base'] = 'Base'; + $QuicktimeVideoCodecLookup['clou'] = 'Cloud'; + $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK'; + $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak'; + $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG'; + $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC'; + $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL'; + $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC'; + $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL'; + $QuicktimeVideoCodecLookup['fire'] = 'Fire'; + $QuicktimeVideoCodecLookup['flic'] = 'FLC'; + $QuicktimeVideoCodecLookup['gif '] = 'GIF'; + $QuicktimeVideoCodecLookup['h261'] = 'H261'; + $QuicktimeVideoCodecLookup['h263'] = 'H263'; + $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4'; + $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG'; + $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD'; + $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A'; + $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B'; + $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1'; + $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420'; + $QuicktimeVideoCodecLookup['path'] = 'Vector'; + $QuicktimeVideoCodecLookup['png '] = 'PNG'; + $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint'; + $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX'; + $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw'; + $QuicktimeVideoCodecLookup['raw '] = 'RAW'; + $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple'; + $QuicktimeVideoCodecLookup['rpza'] = 'Video'; + $QuicktimeVideoCodecLookup['smc '] = 'Graphics'; + $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1'; + $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3'; + $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9'; + $QuicktimeVideoCodecLookup['tga '] = 'Targa'; + $QuicktimeVideoCodecLookup['tiff'] = 'TIFF'; + $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW'; + $QuicktimeVideoCodecLookup['WRLE'] = 'BMP'; + $QuicktimeVideoCodecLookup['y420'] = 'YUV420'; + $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo'; + $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned'; + $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned'; + } + return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : ''); + } + + function QuicktimeAudioCodecLookup($codecid) { + static $QuicktimeAudioCodecLookup = array(); + if (empty($QuicktimeAudioCodecLookup)) { + $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias'; + $QuicktimeAudioCodecLookup['aac '] = 'ISO/IEC 14496-3 AAC'; + $QuicktimeAudioCodecLookup['agsm'] = 'Apple GSM 10:1'; + $QuicktimeAudioCodecLookup['alac'] = 'Apple Lossless Audio Codec'; + $QuicktimeAudioCodecLookup['alaw'] = 'A-law 2:1'; + $QuicktimeAudioCodecLookup['conv'] = 'Sample Format'; + $QuicktimeAudioCodecLookup['dvca'] = 'DV'; + $QuicktimeAudioCodecLookup['dvi '] = 'DV 4:1'; + $QuicktimeAudioCodecLookup['eqal'] = 'Frequency Equalizer'; + $QuicktimeAudioCodecLookup['fl32'] = '32-bit Floating Point'; + $QuicktimeAudioCodecLookup['fl64'] = '64-bit Floating Point'; + $QuicktimeAudioCodecLookup['ima4'] = 'Interactive Multimedia Association 4:1'; + $QuicktimeAudioCodecLookup['in24'] = '24-bit Integer'; + $QuicktimeAudioCodecLookup['in32'] = '32-bit Integer'; + $QuicktimeAudioCodecLookup['lpc '] = 'LPC 23:1'; + $QuicktimeAudioCodecLookup['MAC3'] = 'Macintosh Audio Compression/Expansion (MACE) 3:1'; + $QuicktimeAudioCodecLookup['MAC6'] = 'Macintosh Audio Compression/Expansion (MACE) 6:1'; + $QuicktimeAudioCodecLookup['mixb'] = '8-bit Mixer'; + $QuicktimeAudioCodecLookup['mixw'] = '16-bit Mixer'; + $QuicktimeAudioCodecLookup['mp4a'] = 'ISO/IEC 14496-3 AAC'; + $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM'; + $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA'; + $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III'; + $QuicktimeAudioCodecLookup['NONE'] = 'No Encoding'; + $QuicktimeAudioCodecLookup['Qclp'] = 'Qualcomm PureVoice'; + $QuicktimeAudioCodecLookup['QDM2'] = 'QDesign Music 2'; + $QuicktimeAudioCodecLookup['QDMC'] = 'QDesign Music 1'; + $QuicktimeAudioCodecLookup['ratb'] = '8-bit Rate'; + $QuicktimeAudioCodecLookup['ratw'] = '16-bit Rate'; + $QuicktimeAudioCodecLookup['raw '] = 'raw PCM'; + $QuicktimeAudioCodecLookup['sour'] = 'Sound Source'; + $QuicktimeAudioCodecLookup['sowt'] = 'signed/two\'s complement (Little Endian)'; + $QuicktimeAudioCodecLookup['str1'] = 'Iomega MPEG layer II'; + $QuicktimeAudioCodecLookup['str2'] = 'Iomega MPEG *layer II'; + $QuicktimeAudioCodecLookup['str3'] = 'Iomega MPEG **layer II'; + $QuicktimeAudioCodecLookup['str4'] = 'Iomega MPEG ***layer II'; + $QuicktimeAudioCodecLookup['twos'] = 'signed/two\'s complement (Big Endian)'; + $QuicktimeAudioCodecLookup['ulaw'] = 'mu-law 2:1'; + } + return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : ''); + } + + function QuicktimeDCOMLookup($compressionid) { + static $QuicktimeDCOMLookup = array(); + if (empty($QuicktimeDCOMLookup)) { + $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate'; + $QuicktimeDCOMLookup['adec'] = 'Apple Compression'; + } + return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : ''); + } + + function QuicktimeColorNameLookup($colordepthid) { + static $QuicktimeColorNameLookup = array(); + if (empty($QuicktimeColorNameLookup)) { + $QuicktimeColorNameLookup[1] = '2-color (monochrome)'; + $QuicktimeColorNameLookup[2] = '4-color'; + $QuicktimeColorNameLookup[4] = '16-color'; + $QuicktimeColorNameLookup[8] = '256-color'; + $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)'; + $QuicktimeColorNameLookup[24] = 'millions (24-bit color)'; + $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)'; + $QuicktimeColorNameLookup[33] = 'black & white'; + $QuicktimeColorNameLookup[34] = '4-gray'; + $QuicktimeColorNameLookup[36] = '16-gray'; + $QuicktimeColorNameLookup[40] = '256-gray'; + } + return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid'); + } + + function QuicktimeSTIKLookup($stik) { + static $QuicktimeSTIKLookup = array(); + if (empty($QuicktimeSTIKLookup)) { + $QuicktimeSTIKLookup[0] = 'Movie'; + $QuicktimeSTIKLookup[1] = 'Normal'; + $QuicktimeSTIKLookup[2] = 'Audiobook'; + $QuicktimeSTIKLookup[5] = 'Whacked Bookmark'; + $QuicktimeSTIKLookup[6] = 'Music Video'; + $QuicktimeSTIKLookup[9] = 'Short Film'; + $QuicktimeSTIKLookup[10] = 'TV Show'; + $QuicktimeSTIKLookup[11] = 'Booklet'; + $QuicktimeSTIKLookup[14] = 'Ringtone'; + $QuicktimeSTIKLookup[21] = 'Podcast'; + } + return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid'); + } + + function QuicktimeIODSaudioProfileName($audio_profile_id) { + static $QuicktimeIODSaudioProfileNameLookup = array(); + if (empty($QuicktimeIODSaudioProfileNameLookup)) { + $QuicktimeIODSaudioProfileNameLookup = array( + 0x00 => 'ISO Reserved (0x00)', + 0x01 => 'Main Audio Profile @ Level 1', + 0x02 => 'Main Audio Profile @ Level 2', + 0x03 => 'Main Audio Profile @ Level 3', + 0x04 => 'Main Audio Profile @ Level 4', + 0x05 => 'Scalable Audio Profile @ Level 1', + 0x06 => 'Scalable Audio Profile @ Level 2', + 0x07 => 'Scalable Audio Profile @ Level 3', + 0x08 => 'Scalable Audio Profile @ Level 4', + 0x09 => 'Speech Audio Profile @ Level 1', + 0x0A => 'Speech Audio Profile @ Level 2', + 0x0B => 'Synthetic Audio Profile @ Level 1', + 0x0C => 'Synthetic Audio Profile @ Level 2', + 0x0D => 'Synthetic Audio Profile @ Level 3', + 0x0E => 'High Quality Audio Profile @ Level 1', + 0x0F => 'High Quality Audio Profile @ Level 2', + 0x10 => 'High Quality Audio Profile @ Level 3', + 0x11 => 'High Quality Audio Profile @ Level 4', + 0x12 => 'High Quality Audio Profile @ Level 5', + 0x13 => 'High Quality Audio Profile @ Level 6', + 0x14 => 'High Quality Audio Profile @ Level 7', + 0x15 => 'High Quality Audio Profile @ Level 8', + 0x16 => 'Low Delay Audio Profile @ Level 1', + 0x17 => 'Low Delay Audio Profile @ Level 2', + 0x18 => 'Low Delay Audio Profile @ Level 3', + 0x19 => 'Low Delay Audio Profile @ Level 4', + 0x1A => 'Low Delay Audio Profile @ Level 5', + 0x1B => 'Low Delay Audio Profile @ Level 6', + 0x1C => 'Low Delay Audio Profile @ Level 7', + 0x1D => 'Low Delay Audio Profile @ Level 8', + 0x1E => 'Natural Audio Profile @ Level 1', + 0x1F => 'Natural Audio Profile @ Level 2', + 0x20 => 'Natural Audio Profile @ Level 3', + 0x21 => 'Natural Audio Profile @ Level 4', + 0x22 => 'Mobile Audio Internetworking Profile @ Level 1', + 0x23 => 'Mobile Audio Internetworking Profile @ Level 2', + 0x24 => 'Mobile Audio Internetworking Profile @ Level 3', + 0x25 => 'Mobile Audio Internetworking Profile @ Level 4', + 0x26 => 'Mobile Audio Internetworking Profile @ Level 5', + 0x27 => 'Mobile Audio Internetworking Profile @ Level 6', + 0x28 => 'AAC Profile @ Level 1', + 0x29 => 'AAC Profile @ Level 2', + 0x2A => 'AAC Profile @ Level 4', + 0x2B => 'AAC Profile @ Level 5', + 0x2C => 'High Efficiency AAC Profile @ Level 2', + 0x2D => 'High Efficiency AAC Profile @ Level 3', + 0x2E => 'High Efficiency AAC Profile @ Level 4', + 0x2F => 'High Efficiency AAC Profile @ Level 5', + 0xFE => 'Not part of MPEG-4 audio profiles', + 0xFF => 'No audio capability required', + ); + } + return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private'); + } + + + function QuicktimeIODSvideoProfileName($video_profile_id) { + static $QuicktimeIODSvideoProfileNameLookup = array(); + if (empty($QuicktimeIODSvideoProfileNameLookup)) { + $QuicktimeIODSvideoProfileNameLookup = array( + 0x00 => 'Reserved (0x00) Profile', + 0x01 => 'Simple Profile @ Level 1', + 0x02 => 'Simple Profile @ Level 2', + 0x03 => 'Simple Profile @ Level 3', + 0x08 => 'Simple Profile @ Level 0', + 0x10 => 'Simple Scalable Profile @ Level 0', + 0x11 => 'Simple Scalable Profile @ Level 1', + 0x12 => 'Simple Scalable Profile @ Level 2', + 0x15 => 'AVC/H264 Profile', + 0x21 => 'Core Profile @ Level 1', + 0x22 => 'Core Profile @ Level 2', + 0x32 => 'Main Profile @ Level 2', + 0x33 => 'Main Profile @ Level 3', + 0x34 => 'Main Profile @ Level 4', + 0x42 => 'N-bit Profile @ Level 2', + 0x51 => 'Scalable Texture Profile @ Level 1', + 0x61 => 'Simple Face Animation Profile @ Level 1', + 0x62 => 'Simple Face Animation Profile @ Level 2', + 0x63 => 'Simple FBA Profile @ Level 1', + 0x64 => 'Simple FBA Profile @ Level 2', + 0x71 => 'Basic Animated Texture Profile @ Level 1', + 0x72 => 'Basic Animated Texture Profile @ Level 2', + 0x81 => 'Hybrid Profile @ Level 1', + 0x82 => 'Hybrid Profile @ Level 2', + 0x91 => 'Advanced Real Time Simple Profile @ Level 1', + 0x92 => 'Advanced Real Time Simple Profile @ Level 2', + 0x93 => 'Advanced Real Time Simple Profile @ Level 3', + 0x94 => 'Advanced Real Time Simple Profile @ Level 4', + 0xA1 => 'Core Scalable Profile @ Level1', + 0xA2 => 'Core Scalable Profile @ Level2', + 0xA3 => 'Core Scalable Profile @ Level3', + 0xB1 => 'Advanced Coding Efficiency Profile @ Level 1', + 0xB2 => 'Advanced Coding Efficiency Profile @ Level 2', + 0xB3 => 'Advanced Coding Efficiency Profile @ Level 3', + 0xB4 => 'Advanced Coding Efficiency Profile @ Level 4', + 0xC1 => 'Advanced Core Profile @ Level 1', + 0xC2 => 'Advanced Core Profile @ Level 2', + 0xD1 => 'Advanced Scalable Texture @ Level1', + 0xD2 => 'Advanced Scalable Texture @ Level2', + 0xE1 => 'Simple Studio Profile @ Level 1', + 0xE2 => 'Simple Studio Profile @ Level 2', + 0xE3 => 'Simple Studio Profile @ Level 3', + 0xE4 => 'Simple Studio Profile @ Level 4', + 0xE5 => 'Core Studio Profile @ Level 1', + 0xE6 => 'Core Studio Profile @ Level 2', + 0xE7 => 'Core Studio Profile @ Level 3', + 0xE8 => 'Core Studio Profile @ Level 4', + 0xF0 => 'Advanced Simple Profile @ Level 0', + 0xF1 => 'Advanced Simple Profile @ Level 1', + 0xF2 => 'Advanced Simple Profile @ Level 2', + 0xF3 => 'Advanced Simple Profile @ Level 3', + 0xF4 => 'Advanced Simple Profile @ Level 4', + 0xF5 => 'Advanced Simple Profile @ Level 5', + 0xF7 => 'Advanced Simple Profile @ Level 3b', + 0xF8 => 'Fine Granularity Scalable Profile @ Level 0', + 0xF9 => 'Fine Granularity Scalable Profile @ Level 1', + 0xFA => 'Fine Granularity Scalable Profile @ Level 2', + 0xFB => 'Fine Granularity Scalable Profile @ Level 3', + 0xFC => 'Fine Granularity Scalable Profile @ Level 4', + 0xFD => 'Fine Granularity Scalable Profile @ Level 5', + 0xFE => 'Not part of MPEG-4 Visual profiles', + 0xFF => 'No visual capability required', + ); + } + return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile'); + } + + + function QuicktimeContentRatingLookup($rtng) { + static $QuicktimeContentRatingLookup = array(); + if (empty($QuicktimeContentRatingLookup)) { + $QuicktimeContentRatingLookup[0] = 'None'; + $QuicktimeContentRatingLookup[2] = 'Clean'; + $QuicktimeContentRatingLookup[4] = 'Explicit'; + } + return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid'); + } + + function QuicktimeStoreAccountTypeLookup($akid) { + static $QuicktimeStoreAccountTypeLookup = array(); + if (empty($QuicktimeStoreAccountTypeLookup)) { + $QuicktimeStoreAccountTypeLookup[0] = 'iTunes'; + $QuicktimeStoreAccountTypeLookup[1] = 'AOL'; + } + return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid'); + } + + function QuicktimeStoreFrontCodeLookup($sfid) { + static $QuicktimeStoreFrontCodeLookup = array(); + if (empty($QuicktimeStoreFrontCodeLookup)) { + $QuicktimeStoreFrontCodeLookup[143460] = 'Australia'; + $QuicktimeStoreFrontCodeLookup[143445] = 'Austria'; + $QuicktimeStoreFrontCodeLookup[143446] = 'Belgium'; + $QuicktimeStoreFrontCodeLookup[143455] = 'Canada'; + $QuicktimeStoreFrontCodeLookup[143458] = 'Denmark'; + $QuicktimeStoreFrontCodeLookup[143447] = 'Finland'; + $QuicktimeStoreFrontCodeLookup[143442] = 'France'; + $QuicktimeStoreFrontCodeLookup[143443] = 'Germany'; + $QuicktimeStoreFrontCodeLookup[143448] = 'Greece'; + $QuicktimeStoreFrontCodeLookup[143449] = 'Ireland'; + $QuicktimeStoreFrontCodeLookup[143450] = 'Italy'; + $QuicktimeStoreFrontCodeLookup[143462] = 'Japan'; + $QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg'; + $QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands'; + $QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand'; + $QuicktimeStoreFrontCodeLookup[143457] = 'Norway'; + $QuicktimeStoreFrontCodeLookup[143453] = 'Portugal'; + $QuicktimeStoreFrontCodeLookup[143454] = 'Spain'; + $QuicktimeStoreFrontCodeLookup[143456] = 'Sweden'; + $QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland'; + $QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom'; + $QuicktimeStoreFrontCodeLookup[143441] = 'United States'; + } + return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid'); + } + + function QuicktimeParseNikonNCTG($atom_data) { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG + // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 + // Data is stored as records of: + // * 4 bytes record type + // * 2 bytes size of data field type: + // 0x0001 = flag (size field *= 1-byte) + // 0x0002 = char (size field *= 1-byte) + // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB + // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD + // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together + // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? + // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? + // * 2 bytes data size field + // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15") + // all integers are stored BigEndian + + $NCTGtagName = array( + 0x00000001 => 'Make', + 0x00000002 => 'Model', + 0x00000003 => 'Software', + 0x00000011 => 'CreateDate', + 0x00000012 => 'DateTimeOriginal', + 0x00000013 => 'FrameCount', + 0x00000016 => 'FrameRate', + 0x00000022 => 'FrameWidth', + 0x00000023 => 'FrameHeight', + 0x00000032 => 'AudioChannels', + 0x00000033 => 'AudioBitsPerSample', + 0x00000034 => 'AudioSampleRate', + 0x02000001 => 'MakerNoteVersion', + 0x02000005 => 'WhiteBalance', + 0x0200000b => 'WhiteBalanceFineTune', + 0x0200001e => 'ColorSpace', + 0x02000023 => 'PictureControlData', + 0x02000024 => 'WorldTime', + 0x02000032 => 'UnknownInfo', + 0x02000083 => 'LensType', + 0x02000084 => 'Lens', + ); + + $offset = 0; + $datalength = strlen($atom_data); + $parsed = array(); + while ($offset < $datalength) { +//echo getid3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'
    '; + $record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; + $data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; + $data_size = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; + switch ($data_size_type) { + case 0x0001: // 0x0001 = flag (size field *= 1-byte) + $data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1)); + $offset += ($data_size * 1); + break; + case 0x0002: // 0x0002 = char (size field *= 1-byte) + $data = substr($atom_data, $offset, $data_size * 1); + $offset += ($data_size * 1); + $data = rtrim($data, "\x00"); + break; + case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB + $data = ''; + for ($i = $data_size - 1; $i >= 0; $i--) { + $data .= substr($atom_data, $offset + ($i * 2), 2); + } + $data = getid3_lib::BigEndian2Int($data); + $offset += ($data_size * 2); + break; + case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD + $data = ''; + for ($i = $data_size - 1; $i >= 0; $i--) { + $data .= substr($atom_data, $offset + ($i * 4), 4); + } + $data = getid3_lib::BigEndian2Int($data); + $offset += ($data_size * 4); + break; + case 0x0005: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together + $data = array(); + for ($i = 0; $i < $data_size; $i++) { + $numerator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4)); + $denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4)); + if ($denomninator == 0) { + $data[$i] = false; + } else { + $data[$i] = (double) $numerator / $denomninator; + } + } + $offset += (8 * $data_size); + if (count($data) == 1) { + $data = $data[0]; + } + break; + case 0x0007: // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? + $data = substr($atom_data, $offset, $data_size * 1); + $offset += ($data_size * 1); + break; + case 0x0008: // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? + $data = substr($atom_data, $offset, $data_size * 2); + $offset += ($data_size * 2); + break; + default: +echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
    '; + break 2; + } + + switch ($record_type) { + case 0x00000011: // CreateDate + case 0x00000012: // DateTimeOriginal + $data = strtotime($data); + break; + case 0x0200001e: // ColorSpace + switch ($data) { + case 1: + $data = 'sRGB'; + break; + case 2: + $data = 'Adobe RGB'; + break; + } + break; + case 0x02000023: // PictureControlData + $PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full'); + $FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange', 0x83=>'red', 0x84=>'green', 0xff=>'n/a'); + $ToningEffect = array(0x80=>'b&w', 0x81=>'sepia', 0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a'); + $data = array( + 'PictureControlVersion' => substr($data, 0, 4), + 'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"), + 'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"), + //'?' => substr($data, 44, 4), + 'PictureControlAdjust' => $PictureControlAdjust[ord(substr($data, 48, 1))], + 'PictureControlQuickAdjust' => ord(substr($data, 49, 1)), + 'Sharpness' => ord(substr($data, 50, 1)), + 'Contrast' => ord(substr($data, 51, 1)), + 'Brightness' => ord(substr($data, 52, 1)), + 'Saturation' => ord(substr($data, 53, 1)), + 'HueAdjustment' => ord(substr($data, 54, 1)), + 'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))], + 'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))], + 'ToningSaturation' => ord(substr($data, 57, 1)), + ); + break; + case 0x02000024: // WorldTime + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime + // timezone is stored as offset from GMT in minutes + $timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2)); + if ($timezone & 0x8000) { + $timezone = 0 - (0x10000 - $timezone); + } + $timezone /= 60; + + $dst = (bool) getid3_lib::BigEndian2Int(substr($data, 2, 1)); + switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) { + case 2: + $datedisplayformat = 'D/M/Y'; break; + case 1: + $datedisplayformat = 'M/D/Y'; break; + case 0: + default: + $datedisplayformat = 'Y/M/D'; break; + } + + $data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat); + break; + case 0x02000083: // LensType + $data = array( + //'_' => $data, + 'mf' => (bool) ($data & 0x01), + 'd' => (bool) ($data & 0x02), + 'g' => (bool) ($data & 0x04), + 'vr' => (bool) ($data & 0x08), + ); + break; + } + $tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT)); + $parsed[$tag_name] = $data; + } + return $parsed; + } + + + function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { + static $handyatomtranslatorarray = array(); + if (empty($handyatomtranslatorarray)) { + $handyatomtranslatorarray['©cpy'] = 'copyright'; + $handyatomtranslatorarray['©day'] = 'creation_date'; // iTunes 4.0 + $handyatomtranslatorarray['©dir'] = 'director'; + $handyatomtranslatorarray['©ed1'] = 'edit1'; + $handyatomtranslatorarray['©ed2'] = 'edit2'; + $handyatomtranslatorarray['©ed3'] = 'edit3'; + $handyatomtranslatorarray['©ed4'] = 'edit4'; + $handyatomtranslatorarray['©ed5'] = 'edit5'; + $handyatomtranslatorarray['©ed6'] = 'edit6'; + $handyatomtranslatorarray['©ed7'] = 'edit7'; + $handyatomtranslatorarray['©ed8'] = 'edit8'; + $handyatomtranslatorarray['©ed9'] = 'edit9'; + $handyatomtranslatorarray['©fmt'] = 'format'; + $handyatomtranslatorarray['©inf'] = 'information'; + $handyatomtranslatorarray['©prd'] = 'producer'; + $handyatomtranslatorarray['©prf'] = 'performers'; + $handyatomtranslatorarray['©req'] = 'system_requirements'; + $handyatomtranslatorarray['©src'] = 'source_credit'; + $handyatomtranslatorarray['©wrt'] = 'writer'; + + // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt + $handyatomtranslatorarray['©nam'] = 'title'; // iTunes 4.0 + $handyatomtranslatorarray['©cmt'] = 'comment'; // iTunes 4.0 + $handyatomtranslatorarray['©wrn'] = 'warning'; + $handyatomtranslatorarray['©hst'] = 'host_computer'; + $handyatomtranslatorarray['©mak'] = 'make'; + $handyatomtranslatorarray['©mod'] = 'model'; + $handyatomtranslatorarray['©PRD'] = 'product'; + $handyatomtranslatorarray['©swr'] = 'software'; + $handyatomtranslatorarray['©aut'] = 'author'; + $handyatomtranslatorarray['©ART'] = 'artist'; + $handyatomtranslatorarray['©trk'] = 'track'; + $handyatomtranslatorarray['©alb'] = 'album'; // iTunes 4.0 + $handyatomtranslatorarray['©com'] = 'comment'; + $handyatomtranslatorarray['©gen'] = 'genre'; // iTunes 4.0 + $handyatomtranslatorarray['©ope'] = 'composer'; + $handyatomtranslatorarray['©url'] = 'url'; + $handyatomtranslatorarray['©enc'] = 'encoder'; + + // http://atomicparsley.sourceforge.net/mpeg-4files.html + $handyatomtranslatorarray['©art'] = 'artist'; // iTunes 4.0 + $handyatomtranslatorarray['aART'] = 'album_artist'; + $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 + $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 + $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 + $handyatomtranslatorarray['©too'] = 'encoder'; // iTunes 4.0 + $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 + $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? + $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 + $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 + $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 + $handyatomtranslatorarray['©grp'] = 'grouping'; // iTunes 4.2 + $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 + $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 + $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 + $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 + $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 + $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 + $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 + $handyatomtranslatorarray['©lyr'] = 'lyrics'; // iTunes 5.0 + $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 + $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 + $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 + $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 + $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 + $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 + + // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt + + + + // boxnames: + $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB'; + $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM'; + $handyatomtranslatorarray['Encoding Params'] = 'Encoding Params'; + $handyatomtranslatorarray['replaygain_track_gain'] = 'replaygain_track_gain'; + $handyatomtranslatorarray['replaygain_track_peak'] = 'replaygain_track_peak'; + $handyatomtranslatorarray['replaygain_track_minmax'] = 'replaygain_track_minmax'; + $handyatomtranslatorarray['MusicIP PUID'] = 'MusicIP PUID'; + $handyatomtranslatorarray['MusicBrainz Artist Id'] = 'MusicBrainz Artist Id'; + $handyatomtranslatorarray['MusicBrainz Album Id'] = 'MusicBrainz Album Id'; + $handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id'; + $handyatomtranslatorarray['MusicBrainz Track Id'] = 'MusicBrainz Track Id'; + $handyatomtranslatorarray['MusicBrainz Disc Id'] = 'MusicBrainz Disc Id'; + } + $info = &$this->getid3->info; + $comment_key = ''; + if ($boxname && ($boxname != $keyname) && isset($handyatomtranslatorarray[$boxname])) { + $comment_key = $handyatomtranslatorarray[$boxname]; + } elseif (isset($handyatomtranslatorarray[$keyname])) { + $comment_key = $handyatomtranslatorarray[$keyname]; + } + if ($comment_key) { + if ($comment_key == 'picture') { + if (!is_array($data)) { + $image_mime = ''; + if (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $data)) { + $image_mime = 'image/png'; + } elseif (preg_match('#^\xFF\xD8\xFF#', $data)) { + $image_mime = 'image/jpeg'; + } elseif (preg_match('#^GIF#', $data)) { + $image_mime = 'image/gif'; + } elseif (preg_match('#^BM#', $data)) { + $image_mime = 'image/bmp'; + } + $data = array('data'=>$data, 'image_mime'=>$image_mime); + } + } + $info['quicktime']['comments'][$comment_key][] = $data; + } + return true; + } + + function NoNullString($nullterminatedstring) { + // remove the single null terminator on null terminated strings + if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") { + return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1); + } + return $nullterminatedstring; + } + + function Pascal2String($pascalstring) { + // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string + return substr($pascalstring, 1); + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio-video.real.php b/3rdparty/getid3/module.audio-video.real.php similarity index 80% rename from apps/media/getID3/getid3/module.audio-video.real.php rename to 3rdparty/getid3/module.audio-video.real.php index 013f4784bc..d716e7da3c 100644 --- a/apps/media/getID3/getid3/module.audio-video.real.php +++ b/3rdparty/getid3/module.audio-video.real.php @@ -15,67 +15,69 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); -class getid3_real +class getid3_real extends getid3_handler { - function getid3_real(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'real'; - $ThisFileInfo['bitrate'] = 0; - $ThisFileInfo['playtime_seconds'] = 0; + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $info['fileformat'] = 'real'; + $info['bitrate'] = 0; + $info['playtime_seconds'] = 0; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $ChunkCounter = 0; - while (ftell($fd) < $ThisFileInfo['avdataend']) { - $ChunkData = fread($fd, 8); + while (ftell($this->getid3->fp) < $info['avdataend']) { + $ChunkData = fread($this->getid3->fp, 8); $ChunkName = substr($ChunkData, 0, 4); $ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, 4, 4)); if ($ChunkName == '.ra'."\xFD") { - $ChunkData .= fread($fd, $ChunkSize - 8); - if ($this->ParseOldRAheader(substr($ChunkData, 0, 128), $ThisFileInfo['real']['old_ra_header'])) { - $ThisFileInfo['audio']['dataformat'] = 'real'; - $ThisFileInfo['audio']['lossless'] = false; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['real']['old_ra_header']['sample_rate']; - $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['real']['old_ra_header']['bits_per_sample']; - $ThisFileInfo['audio']['channels'] = $ThisFileInfo['real']['old_ra_header']['channels']; + $ChunkData .= fread($this->getid3->fp, $ChunkSize - 8); + if ($this->ParseOldRAheader(substr($ChunkData, 0, 128), $info['real']['old_ra_header'])) { + $info['audio']['dataformat'] = 'real'; + $info['audio']['lossless'] = false; + $info['audio']['sample_rate'] = $info['real']['old_ra_header']['sample_rate']; + $info['audio']['bits_per_sample'] = $info['real']['old_ra_header']['bits_per_sample']; + $info['audio']['channels'] = $info['real']['old_ra_header']['channels']; - $ThisFileInfo['playtime_seconds'] = 60 * ($ThisFileInfo['real']['old_ra_header']['audio_bytes'] / $ThisFileInfo['real']['old_ra_header']['bytes_per_minute']); - $ThisFileInfo['audio']['bitrate'] = 8 * ($ThisFileInfo['real']['old_ra_header']['audio_bytes'] / $ThisFileInfo['playtime_seconds']); - $ThisFileInfo['audio']['codec'] = $this->RealAudioCodecFourCClookup($ThisFileInfo['real']['old_ra_header']['fourcc'], $ThisFileInfo['audio']['bitrate']); + $info['playtime_seconds'] = 60 * ($info['real']['old_ra_header']['audio_bytes'] / $info['real']['old_ra_header']['bytes_per_minute']); + $info['audio']['bitrate'] = 8 * ($info['real']['old_ra_header']['audio_bytes'] / $info['playtime_seconds']); + $info['audio']['codec'] = $this->RealAudioCodecFourCClookup($info['real']['old_ra_header']['fourcc'], $info['audio']['bitrate']); - foreach ($ThisFileInfo['real']['old_ra_header']['comments'] as $key => $valuearray) { + foreach ($info['real']['old_ra_header']['comments'] as $key => $valuearray) { if (strlen(trim($valuearray[0])) > 0) { - $ThisFileInfo['real']['comments'][$key][] = trim($valuearray[0]); + $info['real']['comments'][$key][] = trim($valuearray[0]); } } return true; } - $ThisFileInfo['error'][] = 'There was a problem parsing this RealAudio file. Please submit it for analysis to http://www.getid3.org/upload/ or info@getid3.org'; - unset($ThisFileInfo['bitrate']); - unset($ThisFileInfo['playtime_seconds']); + $info['error'][] = 'There was a problem parsing this RealAudio file. Please submit it for analysis to info@getid3.org'; + unset($info['bitrate']); + unset($info['playtime_seconds']); return false; } // shortcut - $ThisFileInfo['real']['chunks'][$ChunkCounter] = array(); - $thisfile_real_chunks_currentchunk = &$ThisFileInfo['real']['chunks'][$ChunkCounter]; + $info['real']['chunks'][$ChunkCounter] = array(); + $thisfile_real_chunks_currentchunk = &$info['real']['chunks'][$ChunkCounter]; $thisfile_real_chunks_currentchunk['name'] = $ChunkName; - $thisfile_real_chunks_currentchunk['offset'] = ftell($fd) - 8; + $thisfile_real_chunks_currentchunk['offset'] = ftell($this->getid3->fp) - 8; $thisfile_real_chunks_currentchunk['length'] = $ChunkSize; - if (($thisfile_real_chunks_currentchunk['offset'] + $thisfile_real_chunks_currentchunk['length']) > $ThisFileInfo['avdataend']) { - $ThisFileInfo['warning'][] = 'Chunk "'.$thisfile_real_chunks_currentchunk['name'].'" at offset '.$thisfile_real_chunks_currentchunk['offset'].' claims to be '.$thisfile_real_chunks_currentchunk['length'].' bytes long, which is beyond end of file'; + if (($thisfile_real_chunks_currentchunk['offset'] + $thisfile_real_chunks_currentchunk['length']) > $info['avdataend']) { + $info['warning'][] = 'Chunk "'.$thisfile_real_chunks_currentchunk['name'].'" at offset '.$thisfile_real_chunks_currentchunk['offset'].' claims to be '.$thisfile_real_chunks_currentchunk['length'].' bytes long, which is beyond end of file'; return false; } - if ($ChunkSize > (GETID3_FREAD_BUFFER_SIZE + 8)) { + if ($ChunkSize > ($this->getid3->fread_buffer_size() + 8)) { - $ChunkData .= fread($fd, GETID3_FREAD_BUFFER_SIZE - 8); - fseek($fd, $thisfile_real_chunks_currentchunk['offset'] + $ChunkSize, SEEK_SET); + $ChunkData .= fread($this->getid3->fp, $this->getid3->fread_buffer_size() - 8); + fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['offset'] + $ChunkSize, SEEK_SET); } elseif(($ChunkSize - 8) > 0) { - - $ChunkData .= fread($fd, $ChunkSize - 8); + + $ChunkData .= fread($this->getid3->fp, $ChunkSize - 8); } $offset = 8; @@ -95,7 +97,7 @@ class getid3_real break; default: - //$ThisFileInfo['warning'][] = 'Expected .RMF-object_version to be "0", actual value is "'.$thisfile_real_chunks_currentchunk['object_version'].'" (should not be a problem)'; + //$info['warning'][] = 'Expected .RMF-object_version to be "0", actual value is "'.$thisfile_real_chunks_currentchunk['object_version'].'" (should not be a problem)'; break; } @@ -128,9 +130,9 @@ class getid3_real $offset += 2; $thisfile_real_chunks_currentchunk['flags_raw'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); $offset += 2; - $ThisFileInfo['playtime_seconds'] = $thisfile_real_chunks_currentchunk['duration'] / 1000; + $info['playtime_seconds'] = $thisfile_real_chunks_currentchunk['duration'] / 1000; if ($thisfile_real_chunks_currentchunk['duration'] > 0) { - $ThisFileInfo['bitrate'] += $thisfile_real_chunks_currentchunk['avg_bit_rate']; + $info['bitrate'] += $thisfile_real_chunks_currentchunk['avg_bit_rate']; } $thisfile_real_chunks_currentchunk['flags']['save_enabled'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0001); $thisfile_real_chunks_currentchunk['flags']['perfect_play'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0002); @@ -202,24 +204,24 @@ class getid3_real $thisfile_real_chunks_currentchunk_videoinfo['codec'] = getid3_riff::RIFFfourccLookup($thisfile_real_chunks_currentchunk_videoinfo['fourcc2']); - $ThisFileInfo['video']['resolution_x'] = $thisfile_real_chunks_currentchunk_videoinfo['width']; - $ThisFileInfo['video']['resolution_y'] = $thisfile_real_chunks_currentchunk_videoinfo['height']; - $ThisFileInfo['video']['frame_rate'] = (float) $thisfile_real_chunks_currentchunk_videoinfo['frames_per_second']; - $ThisFileInfo['video']['codec'] = $thisfile_real_chunks_currentchunk_videoinfo['codec']; - $ThisFileInfo['video']['bits_per_sample'] = $thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample']; + $info['video']['resolution_x'] = $thisfile_real_chunks_currentchunk_videoinfo['width']; + $info['video']['resolution_y'] = $thisfile_real_chunks_currentchunk_videoinfo['height']; + $info['video']['frame_rate'] = (float) $thisfile_real_chunks_currentchunk_videoinfo['frames_per_second']; + $info['video']['codec'] = $thisfile_real_chunks_currentchunk_videoinfo['codec']; + $info['video']['bits_per_sample'] = $thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample']; break; case 'audio/x-pn-realaudio': case 'audio/x-pn-multirate-realaudio': $this->ParseOldRAheader($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk['parsed_audio_data']); - $ThisFileInfo['audio']['sample_rate'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['sample_rate']; - $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['bits_per_sample']; - $ThisFileInfo['audio']['channels'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['channels']; - if (!empty($ThisFileInfo['audio']['dataformat'])) { - foreach ($ThisFileInfo['audio'] as $key => $value) { + $info['audio']['sample_rate'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['sample_rate']; + $info['audio']['bits_per_sample'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['bits_per_sample']; + $info['audio']['channels'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['channels']; + if (!empty($info['audio']['dataformat'])) { + foreach ($info['audio'] as $key => $value) { if ($key != 'streams') { - $ThisFileInfo['audio']['streams'][$thisfile_real_chunks_currentchunk['stream_number']][$key] = $value; + $info['audio']['streams'][$thisfile_real_chunks_currentchunk['stream_number']][$key] = $value; } } } @@ -255,36 +257,36 @@ class getid3_real } - if (empty($ThisFileInfo['playtime_seconds'])) { - $ThisFileInfo['playtime_seconds'] = max($ThisFileInfo['playtime_seconds'], ($thisfile_real_chunks_currentchunk['duration'] + $thisfile_real_chunks_currentchunk['start_time']) / 1000); + if (empty($info['playtime_seconds'])) { + $info['playtime_seconds'] = max($info['playtime_seconds'], ($thisfile_real_chunks_currentchunk['duration'] + $thisfile_real_chunks_currentchunk['start_time']) / 1000); } if ($thisfile_real_chunks_currentchunk['duration'] > 0) { switch ($thisfile_real_chunks_currentchunk['mime_type']) { case 'audio/x-pn-realaudio': case 'audio/x-pn-multirate-realaudio': - $ThisFileInfo['audio']['bitrate'] = (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; - $ThisFileInfo['audio']['codec'] = $this->RealAudioCodecFourCClookup($thisfile_real_chunks_currentchunk['parsed_audio_data']['fourcc'], $ThisFileInfo['audio']['bitrate']); - $ThisFileInfo['audio']['dataformat'] = 'real'; - $ThisFileInfo['audio']['lossless'] = false; + $info['audio']['bitrate'] = (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; + $info['audio']['codec'] = $this->RealAudioCodecFourCClookup($thisfile_real_chunks_currentchunk['parsed_audio_data']['fourcc'], $info['audio']['bitrate']); + $info['audio']['dataformat'] = 'real'; + $info['audio']['lossless'] = false; break; case 'video/x-pn-realvideo': case 'video/x-pn-multirate-realvideo': - $ThisFileInfo['video']['bitrate'] = (isset($ThisFileInfo['video']['bitrate']) ? $ThisFileInfo['video']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; - $ThisFileInfo['video']['bitrate_mode'] = 'cbr'; - $ThisFileInfo['video']['dataformat'] = 'real'; - $ThisFileInfo['video']['lossless'] = false; - $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + $info['video']['bitrate'] = (isset($info['video']['bitrate']) ? $info['video']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; + $info['video']['bitrate_mode'] = 'cbr'; + $info['video']['dataformat'] = 'real'; + $info['video']['lossless'] = false; + $info['video']['pixel_aspect_ratio'] = (float) 1; break; case 'audio/x-ralf-mpeg4-generic': - $ThisFileInfo['audio']['bitrate'] = (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; - $ThisFileInfo['audio']['codec'] = 'RealAudio Lossless'; - $ThisFileInfo['audio']['dataformat'] = 'real'; - $ThisFileInfo['audio']['lossless'] = true; + $info['audio']['bitrate'] = (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; + $info['audio']['codec'] = 'RealAudio Lossless'; + $info['audio']['dataformat'] = 'real'; + $info['audio']['lossless'] = true; break; } - $ThisFileInfo['bitrate'] = (isset($ThisFileInfo['video']['bitrate']) ? $ThisFileInfo['video']['bitrate'] : 0) + (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0); + $info['bitrate'] = (isset($info['video']['bitrate']) ? $info['video']['bitrate'] : 0) + (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0); } } break; @@ -317,7 +319,7 @@ class getid3_real $commentkeystocopy = array('title'=>'title', 'artist'=>'artist', 'copyright'=>'copyright', 'comment'=>'comment'); foreach ($commentkeystocopy as $key => $val) { if ($thisfile_real_chunks_currentchunk[$key]) { - $ThisFileInfo['real']['comments'][$val][] = trim($thisfile_real_chunks_currentchunk[$key]); + $info['real']['comments'][$val][] = trim($thisfile_real_chunks_currentchunk[$key]); } } @@ -345,22 +347,22 @@ class getid3_real break 2; } else { // non-last index chunk, seek to next index chunk (skipping actual index data) - fseek($fd, $thisfile_real_chunks_currentchunk['next_index_header'], SEEK_SET); + fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['next_index_header'], SEEK_SET); } } break; default: - $ThisFileInfo['warning'][] = 'Unhandled RealMedia chunk "'.$ChunkName.'" at offset '.$thisfile_real_chunks_currentchunk['offset']; + $info['warning'][] = 'Unhandled RealMedia chunk "'.$ChunkName.'" at offset '.$thisfile_real_chunks_currentchunk['offset']; break; } $ChunkCounter++; } - if (!empty($ThisFileInfo['audio']['streams'])) { - $ThisFileInfo['audio']['bitrate'] = 0; - foreach ($ThisFileInfo['audio']['streams'] as $key => $valuearray) { - $ThisFileInfo['audio']['bitrate'] += $valuearray['bitrate']; + if (!empty($info['audio']['streams'])) { + $info['audio']['bitrate'] = 0; + foreach ($info['audio']['streams'] as $key => $valuearray) { + $info['audio']['bitrate'] += $valuearray['bitrate']; } } diff --git a/apps/media/getID3/getid3/module.audio-video.riff.php b/3rdparty/getid3/module.audio-video.riff.php similarity index 61% rename from apps/media/getID3/getid3/module.audio-video.riff.php rename to 3rdparty/getid3/module.audio-video.riff.php index 74ea33966c..8e8f53a403 100644 --- a/apps/media/getID3/getid3/module.audio-video.riff.php +++ b/3rdparty/getid3/module.audio-video.riff.php @@ -19,65 +19,115 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); -class getid3_riff +class getid3_riff extends getid3_handler { - function getid3_riff(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; // initialize these values to an empty array, otherwise they default to NULL // and you can't append array values to a NULL value - $ThisFileInfo['riff'] = array('raw'=>array()); + $info['riff'] = array('raw'=>array()); // Shortcuts - $thisfile_riff = &$ThisFileInfo['riff']; + $thisfile_riff = &$info['riff']; $thisfile_riff_raw = &$thisfile_riff['raw']; - $thisfile_audio = &$ThisFileInfo['audio']; - $thisfile_video = &$ThisFileInfo['video']; - $thisfile_avdataoffset = &$ThisFileInfo['avdataoffset']; - $thisfile_avdataend = &$ThisFileInfo['avdataend']; + $thisfile_audio = &$info['audio']; + $thisfile_video = &$info['video']; $thisfile_audio_dataformat = &$thisfile_audio['dataformat']; $thisfile_riff_audio = &$thisfile_riff['audio']; $thisfile_riff_video = &$thisfile_riff['video']; - $Original['avdataoffset'] = $thisfile_avdataoffset; - $Original['avdataend'] = $thisfile_avdataend; + $Original['avdataoffset'] = $info['avdataoffset']; + $Original['avdataend'] = $info['avdataend']; - fseek($fd, $thisfile_avdataoffset, SEEK_SET); - $RIFFheader = fread($fd, 12); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $RIFFheader = fread($this->getid3->fp, 12); $RIFFsubtype = substr($RIFFheader, 8, 4); switch (substr($RIFFheader, 0, 4)) { case 'FORM': - $ThisFileInfo['fileformat'] = 'aiff'; - $RIFFheaderSize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($RIFFheader, 4, 4)); - $thisfile_riff[$RIFFsubtype] = getid3_riff::ParseRIFF($fd, $thisfile_avdataoffset + 12, $thisfile_avdataoffset + $RIFFheaderSize, $ThisFileInfo); - $thisfile_riff['header_size'] = $RIFFheaderSize; + $info['fileformat'] = 'aiff'; + $thisfile_riff['header_size'] = $this->EitherEndian2Int(substr($RIFFheader, 4, 4)); + $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($info['avdataoffset'] + 12, $info['avdataoffset'] + $thisfile_riff['header_size']); break; - case 'RIFF': + case 'RIFF': // AVI, WAV, etc case 'SDSS': // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com) case 'RMP3': // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s + $info['fileformat'] = 'riff'; + $thisfile_riff['header_size'] = $this->EitherEndian2Int(substr($RIFFheader, 4, 4)); if ($RIFFsubtype == 'RMP3') { // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s $RIFFsubtype = 'WAVE'; } + $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($info['avdataoffset'] + 12, $info['avdataoffset'] + $thisfile_riff['header_size']); + if (($info['avdataend'] - $info['filesize']) == 1) { + // LiteWave appears to incorrectly *not* pad actual output file + // to nearest WORD boundary so may appear to be short by one + // byte, in which case - skip warning + $info['avdataend'] = $info['filesize']; + } - $ThisFileInfo['fileformat'] = 'riff'; - $RIFFheaderSize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($RIFFheader, 4, 4)); - $thisfile_riff['header_size'] = $RIFFheaderSize; - $thisfile_riff[$RIFFsubtype] = getid3_riff::ParseRIFF($fd, $thisfile_avdataoffset + 12, $thisfile_avdataoffset + $RIFFheaderSize, $ThisFileInfo); + $nextRIFFoffset = $Original['avdataoffset'] + 8 + $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset + while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) { + if (!getid3_lib::intValueSupported($nextRIFFoffset + 1024)) { + $info['error'][] = 'AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; + $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; + break; + } else { + fseek($this->getid3->fp, $nextRIFFoffset, SEEK_SET); + $nextRIFFheader = fread($this->getid3->fp, 12); + if ($nextRIFFoffset == ($info['avdataend'] - 1)) { + if (substr($nextRIFFheader, 0, 1) == "\x00") { + // RIFF padded to WORD boundary, we're actually already at the end + break; + } + } + $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); + $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); + $nextRIFFtype = substr($nextRIFFheader, 8, 4); + $chunkdata = array(); + $chunkdata['offset'] = $nextRIFFoffset + 8; + $chunkdata['size'] = $nextRIFFsize; + $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; + switch ($nextRIFFheaderID) { + case 'RIFF': + $info['avdataend'] = $nextRIFFoffset; + if (!getid3_lib::intValueSupported($info['avdataend'])) { + $info['error'][] = 'AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; + $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; + } + $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $chunkdata['offset'] + $chunkdata['size']); - fseek($fd, $thisfile_avdataoffset + $RIFFheaderSize); - $nextRIFFheader = fread($fd, 20); - if (substr($nextRIFFheader, 8, 4) == 'RIFF') { - $nextRIFFsize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($nextRIFFheader, 12, 4)); - $nextRIFFtype = substr($nextRIFFheader, 16, 4).'
    '; - $thisfile_riff[$nextRIFFtype]['offset'] = ftell($fd) - 4; - $thisfile_riff[$nextRIFFtype]['size'] = $nextRIFFsize; - $ThisFileInfo['avdataend'] = $thisfile_riff[$nextRIFFtype]['offset'] + $thisfile_riff[$nextRIFFtype]['size']; - $ThisFileInfo['error'][] = 'AVI extends beyond 2GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; - $ThisFileInfo['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; - $thisfile_riff[$nextRIFFtype] = getid3_riff::ParseRIFF($fd, $thisfile_riff[$nextRIFFtype]['offset'] + 4, $thisfile_riff[$nextRIFFtype]['offset'] + $thisfile_riff[$nextRIFFtype]['size'], $ThisFileInfo); + if (!isset($thisfile_riff[$nextRIFFtype])) { + $thisfile_riff[$nextRIFFtype] = array(); + } + $thisfile_riff[$nextRIFFtype][] = $chunkdata; + break; + case 'JUNK': + // ignore + $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; + break; + default: + if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) { + $DIVXTAG = $nextRIFFheader.fread($this->getid3->fp, 128 - 12); + if (substr($DIVXTAG, -7) == 'DIVXTAG') { + // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file + $info['warning'][] = 'Found wrongly-structured DIVXTAG at offset '.(ftell($this->getid3->fp) - 128 + 12).', parsing anyway'; + $thisfile_riff['DIVXTAG'] = $this->ParseDIVXTAG($DIVXTAG); + foreach ($thisfile_riff['DIVXTAG'] as $key => $value) { + if ($value && !preg_match('#_id$#', $key)) { + $thisfile_riff['comments'][$key][] = $value; + } + } + break 2; + } + } + $info['warning'][] = 'expecting "RIFF" or "JUNK" at '.$nextRIFFoffset.', found '.getid3_lib::PrintHexBytes(substr($nextRIFFheader, 0, 4)).' - skipping rest of file'; + break 2; + } + } } if ($RIFFsubtype == 'WAVE') { $thisfile_riff_WAVE = &$thisfile_riff['WAVE']; @@ -85,8 +135,8 @@ class getid3_riff break; default: - $ThisFileInfo['error'][] = 'Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'; - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'; + unset($info['fileformat']); return false; break; } @@ -102,15 +152,15 @@ class getid3_riff } if (isset($thisfile_riff_WAVE['data'][0]['offset'])) { - $thisfile_avdataoffset = $thisfile_riff_WAVE['data'][0]['offset'] + 8; - $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff_WAVE['data'][0]['size']; + $info['avdataoffset'] = $thisfile_riff_WAVE['data'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff_WAVE['data'][0]['size']; } if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) { $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; - if (@$thisfile_riff_audio[$streamindex]['bitrate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; + if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || ($thisfile_riff_audio[$streamindex]['bitrate'] == 0)) { + $info['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; return false; } $thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw']; @@ -119,11 +169,11 @@ class getid3_riff $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') { - $ThisFileInfo['warning'][] = 'Audio codec = '.$thisfile_audio['codec']; + $info['warning'][] = 'Audio codec = '.$thisfile_audio['codec']; } $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; - $ThisFileInfo['playtime_seconds'] = (float) ((($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $thisfile_audio['bitrate']); + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); $thisfile_audio['lossless'] = false; if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { @@ -158,9 +208,9 @@ class getid3_riff $thisfile_riff_raw_rgad_track = &$thisfile_riff_raw_rgad['track']; $thisfile_riff_raw_rgad_album = &$thisfile_riff_raw_rgad['album']; - $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); - $thisfile_riff_raw_rgad['nRadioRgAdjust'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($rgadData, 4, 2)); - $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($rgadData, 6, 2)); + $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); + $thisfile_riff_raw_rgad['nRadioRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 4, 2)); + $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 6, 2)); $nRadioRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT); $nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT); @@ -187,14 +237,14 @@ class getid3_riff } if (isset($thisfile_riff_WAVE['fact'][0]['data'])) { - $thisfile_riff_raw['fact']['NumberOfSamples'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); + $thisfile_riff_raw['fact']['NumberOfSamples'] = $this->EitherEndian2Int(substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); // This should be a good way of calculating exact playtime, // but some sample files have had incorrect number of samples, // so cannot use this method // if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) { - // $ThisFileInfo['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; + // $info['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; // } } if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) { @@ -212,17 +262,19 @@ class getid3_riff $thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8); $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8)); $thisfile_riff_WAVE_bext_0['bwf_version'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346, 1)); - $thisfile_riff_WAVE_bext_0['reserved'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 347, 254)); + $thisfile_riff_WAVE_bext_0['reserved'] = substr($thisfile_riff_WAVE_bext_0['data'], 347, 254); $thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601))); - - $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime( - substr($thisfile_riff_WAVE_bext_0['origin_time'], 0, 2), - substr($thisfile_riff_WAVE_bext_0['origin_time'], 3, 2), - substr($thisfile_riff_WAVE_bext_0['origin_time'], 6, 2), - substr($thisfile_riff_WAVE_bext_0['origin_date'], 5, 2), - substr($thisfile_riff_WAVE_bext_0['origin_date'], 8, 2), - substr($thisfile_riff_WAVE_bext_0['origin_date'], 0, 4)); - + if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) { + if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) { + list($dummy, $bext_timestamp['year'], $bext_timestamp['month'], $bext_timestamp['day']) = $matches_bext_date; + list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time; + $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']); + } else { + $info['warning'][] = 'RIFF.WAVE.BEXT.origin_time is invalid'; + } + } else { + $info['warning'][] = 'RIFF.WAVE.BEXT.origin_date is invalid'; + } $thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author']; $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_bext_0['title']; } @@ -278,40 +330,132 @@ class getid3_riff $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_cart_0['title']; } - if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { - $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; - $ThisFileInfo['playtime_seconds'] = (float) ((($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $thisfile_audio['bitrate']); + if (isset($thisfile_riff_WAVE['SNDM'][0]['data'])) { + // SoundMiner metadata + + // shortcuts + $thisfile_riff_WAVE_SNDM_0 = &$thisfile_riff_WAVE['SNDM'][0]; + $thisfile_riff_WAVE_SNDM_0_data = &$thisfile_riff_WAVE_SNDM_0['data']; + $SNDM_startoffset = 0; + $SNDM_endoffset = $thisfile_riff_WAVE_SNDM_0['size']; + + while ($SNDM_startoffset < $SNDM_endoffset) { + $SNDM_thisTagOffset = 0; + $SNDM_thisTagSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4)); + $SNDM_thisTagOffset += 4; + $SNDM_thisTagKey = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4); + $SNDM_thisTagOffset += 4; + $SNDM_thisTagDataSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); + $SNDM_thisTagOffset += 2; + $SNDM_thisTagDataFlags = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); + $SNDM_thisTagOffset += 2; + $SNDM_thisTagDataText = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, $SNDM_thisTagDataSize); + $SNDM_thisTagOffset += $SNDM_thisTagDataSize; + + if ($SNDM_thisTagSize != (4 + 4 + 2 + 2 + $SNDM_thisTagDataSize)) { + $info['warning'][] = 'RIFF.WAVE.SNDM.data contains tag not expected length (expected: '.$SNDM_thisTagSize.', found: '.(4 + 4 + 2 + 2 + $SNDM_thisTagDataSize).') at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + break; + } elseif ($SNDM_thisTagSize <= 0) { + $info['warning'][] = 'RIFF.WAVE.SNDM.data contains zero-size tag at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + break; + } + $SNDM_startoffset += $SNDM_thisTagSize; + + $thisfile_riff_WAVE_SNDM_0['parsed_raw'][$SNDM_thisTagKey] = $SNDM_thisTagDataText; + if ($parsedkey = $this->RIFFwaveSNDMtagLookup($SNDM_thisTagKey)) { + $thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText; + } else { + $info['warning'][] = 'RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + } + } + + $tagmapping = array( + 'tracktitle'=>'title', + 'category' =>'genre', + 'cdtitle' =>'album', + 'tracktitle'=>'title', + ); + foreach ($tagmapping as $fromkey => $tokey) { + if (isset($thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey])) { + $thisfile_riff['comments'][$tokey][] = $thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey]; + } + } } - if (!empty($ThisFileInfo['wavpack'])) { + if (isset($thisfile_riff_WAVE['iXML'][0]['data'])) { + // requires functions simplexml_load_string and get_object_vars + if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) { + $thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML; + if (isset($parsedXML['SPEED']['MASTER_SPEED'])) { + @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED']); + $thisfile_riff_WAVE['iXML'][0]['master_speed'] = $numerator / ($denominator ? $denominator : 1000); + } + if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) { + @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE']); + $thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = $numerator / ($denominator ? $denominator : 1000); + } + if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) { + $samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'].$parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0')); + $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']; + $h = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] / 3600); + $m = floor(($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600)) / 60); + $s = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60)); + $f = ($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60) - $s) * $thisfile_riff_WAVE['iXML'][0]['timecode_rate']; + $thisfile_riff_WAVE['iXML'][0]['timecode_string'] = sprintf('%02d:%02d:%02d:%05.2f', $h, $m, $s, $f); + $thisfile_riff_WAVE['iXML'][0]['timecode_string_round'] = sprintf('%02d:%02d:%02d:%02d', $h, $m, $s, round($f)); + } + unset($parsedXML); + } + } + + + + if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { + $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); + } + + if (!empty($info['wavpack'])) { $thisfile_audio_dataformat = 'wavpack'; $thisfile_audio['bitrate_mode'] = 'vbr'; - $thisfile_audio['encoder'] = 'WavPack v'.$ThisFileInfo['wavpack']['version']; + $thisfile_audio['encoder'] = 'WavPack v'.$info['wavpack']['version']; // Reset to the way it was - RIFF parsing will have messed this up - $thisfile_avdataend = $Original['avdataend']; - $thisfile_audio['bitrate'] = (($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $ThisFileInfo['playtime_seconds']; + $info['avdataend'] = $Original['avdataend']; + $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; - fseek($fd, $thisfile_avdataoffset - 44, SEEK_SET); - $RIFFdata = fread($fd, 44); + fseek($this->getid3->fp, $info['avdataoffset'] - 44, SEEK_SET); + $RIFFdata = fread($this->getid3->fp, 44); $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { - $thisfile_avdataend -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); - fseek($fd, $thisfile_avdataend, SEEK_SET); - $RIFFdata .= fread($fd, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); + $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); + fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); + $RIFFdata .= fread($this->getid3->fp, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); } // move the data chunk after all other chunks (if any) // so that the RIFF parser doesn't see EOF when trying // to skip over the data chunk $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); - getid3_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); + $getid3_riff = new getid3_riff($this->getid3); + $getid3_riff->ParseRIFFdata($RIFFdata); + unset($getid3_riff); } if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { + case 0x0001: // PCM + if (!empty($info['ac3'])) { + // Dolby Digital WAV files masquerade as PCM-WAV, but they're not + $thisfile_audio['wformattag'] = 0x2000; + $thisfile_audio['codec'] = $this->RIFFwFormatTagLookup($thisfile_audio['wformattag']); + $thisfile_audio['lossless'] = false; + $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; + $thisfile_audio['sample_rate'] = $info['ac3']['sample_rate']; + } + break; case 0x08AE: // ClearJump LiteWave $thisfile_audio['bitrate_mode'] = 'vbr'; $thisfile_audio_dataformat = 'litewave'; @@ -363,8 +507,8 @@ class getid3_riff break; } } - if ($thisfile_avdataend > $ThisFileInfo['filesize']) { - switch (@$thisfile_audio_dataformat) { + if ($info['avdataend'] > $info['filesize']) { + switch (!empty($thisfile_audio_dataformat) ? $thisfile_audio_dataformat : '') { case 'wavpack': // WavPack case 'lpac': // LPAC case 'ofr': // OptimFROG @@ -373,41 +517,41 @@ class getid3_riff break; case 'litewave': - if (($thisfile_avdataend - $ThisFileInfo['filesize']) == 1) { + if (($info['avdataend'] - $info['filesize']) == 1) { // LiteWave appears to incorrectly *not* pad actual output file // to nearest WORD boundary so may appear to be short by one // byte, in which case - skip warning } else { // Short by more than one byte, throw warning - $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; - $thisfile_avdataend = $ThisFileInfo['filesize']; + $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; } break; default: - if ((($thisfile_avdataend - $ThisFileInfo['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($ThisFileInfo['filesize'] - $thisfile_avdataoffset) % 2) == 1)) { + if ((($info['avdataend'] - $info['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($info['filesize'] - $info['avdataoffset']) % 2) == 1)) { // output file appears to be incorrectly *not* padded to nearest WORD boundary // Output less severe warning - $ThisFileInfo['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; - $thisfile_avdataend = $ThisFileInfo['filesize']; - break; + $info['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; + } else { + // Short by more than one byte, throw warning + $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; } - // Short by more than one byte, throw warning - $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; - $thisfile_avdataend = $ThisFileInfo['filesize']; break; } } - if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'])) { - if ((($thisfile_avdataend - $thisfile_avdataoffset) - $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes']) == 1) { - $thisfile_avdataend--; - $ThisFileInfo['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; + if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) { + if ((($info['avdataend'] - $info['avdataoffset']) - $info['mpeg']['audio']['LAME']['audio_bytes']) == 1) { + $info['avdataend']--; + $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; } } - if (@$thisfile_audio_dataformat == 'ac3') { + if (isset($thisfile_audio_dataformat) && ($thisfile_audio_dataformat == 'ac3')) { unset($thisfile_audio['bits_per_sample']); - if (!empty($ThisFileInfo['ac3']['bitrate']) && ($ThisFileInfo['ac3']['bitrate'] != $thisfile_audio['bitrate'])) { - $thisfile_audio['bitrate'] = $ThisFileInfo['ac3']['bitrate']; + if (!empty($info['ac3']['bitrate']) && ($info['ac3']['bitrate'] != $thisfile_audio['bitrate'])) { + $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; } } break; @@ -415,14 +559,18 @@ class getid3_riff case 'AVI ': $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably $thisfile_video['dataformat'] = 'avi'; - $ThisFileInfo['mime_type'] = 'video/avi'; + $info['mime_type'] = 'video/avi'; if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) { - $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; - $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['movi']['size']; - if ($thisfile_avdataend > $ThisFileInfo['filesize']) { - $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['movi']['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['movi']['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; - $thisfile_avdataend = $ThisFileInfo['filesize']; + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; + if (isset($thisfile_riff['AVIX'])) { + $info['avdataend'] = $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['offset'] + $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['size']; + } else { + $info['avdataend'] = $thisfile_riff['AVI ']['movi']['offset'] + $thisfile_riff['AVI ']['movi']['size']; + } + if ($info['avdataend'] > $info['filesize']) { + $info['warning'][] = 'Probably truncated file - expecting '.($info['avdataend'] - $info['avdataoffset']).' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($info['avdataend'] - $info['filesize']).' bytes)'; + $info['avdataend'] = $info['filesize']; } } @@ -440,15 +588,15 @@ class getid3_riff foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) { $thisfile_riff_avi_hdrl_strl_indx_stream_data = &$thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data']; - $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 0, 2)); - $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 2, 1)); - $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 3, 1)); - $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 4, 4)); - $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 8, 4); - $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 12, 4)); + $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 0, 2)); + $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 2, 1)); + $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 3, 1)); + $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 4, 4)); + $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 8, 4); + $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 12, 4)); - //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = @$bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; - //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = @$bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; + //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = $bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; + //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = $bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; unset($thisfile_riff_avi_hdrl_strl_indx_stream_data); } @@ -460,24 +608,24 @@ class getid3_riff $thisfile_riff_raw['avih'] = array(); $thisfile_riff_raw_avih = &$thisfile_riff_raw['avih']; - $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 0, 4)); // frame display rate (or 0L) + $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = $this->EitherEndian2Int(substr($avihData, 0, 4)); // frame display rate (or 0L) if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; + $info['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; return false; } - $thisfile_riff_raw_avih['dwMaxBytesPerSec'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 4, 4)); // max. transfer rate - $thisfile_riff_raw_avih['dwPaddingGranularity'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 8, 4)); // pad to multiples of this size; normally 2K. - $thisfile_riff_raw_avih['dwFlags'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 12, 4)); // the ever-present flags - $thisfile_riff_raw_avih['dwTotalFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 16, 4)); // # frames in file - $thisfile_riff_raw_avih['dwInitialFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 20, 4)); - $thisfile_riff_raw_avih['dwStreams'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 24, 4)); - $thisfile_riff_raw_avih['dwSuggestedBufferSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 28, 4)); - $thisfile_riff_raw_avih['dwWidth'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 32, 4)); - $thisfile_riff_raw_avih['dwHeight'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 36, 4)); - $thisfile_riff_raw_avih['dwScale'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 40, 4)); - $thisfile_riff_raw_avih['dwRate'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 44, 4)); - $thisfile_riff_raw_avih['dwStart'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 48, 4)); - $thisfile_riff_raw_avih['dwLength'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 52, 4)); + $thisfile_riff_raw_avih['dwMaxBytesPerSec'] = $this->EitherEndian2Int(substr($avihData, 4, 4)); // max. transfer rate + $thisfile_riff_raw_avih['dwPaddingGranularity'] = $this->EitherEndian2Int(substr($avihData, 8, 4)); // pad to multiples of this size; normally 2K. + $thisfile_riff_raw_avih['dwFlags'] = $this->EitherEndian2Int(substr($avihData, 12, 4)); // the ever-present flags + $thisfile_riff_raw_avih['dwTotalFrames'] = $this->EitherEndian2Int(substr($avihData, 16, 4)); // # frames in file + $thisfile_riff_raw_avih['dwInitialFrames'] = $this->EitherEndian2Int(substr($avihData, 20, 4)); + $thisfile_riff_raw_avih['dwStreams'] = $this->EitherEndian2Int(substr($avihData, 24, 4)); + $thisfile_riff_raw_avih['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($avihData, 28, 4)); + $thisfile_riff_raw_avih['dwWidth'] = $this->EitherEndian2Int(substr($avihData, 32, 4)); + $thisfile_riff_raw_avih['dwHeight'] = $this->EitherEndian2Int(substr($avihData, 36, 4)); + $thisfile_riff_raw_avih['dwScale'] = $this->EitherEndian2Int(substr($avihData, 40, 4)); + $thisfile_riff_raw_avih['dwRate'] = $this->EitherEndian2Int(substr($avihData, 44, 4)); + $thisfile_riff_raw_avih['dwStart'] = $this->EitherEndian2Int(substr($avihData, 48, 4)); + $thisfile_riff_raw_avih['dwLength'] = $this->EitherEndian2Int(substr($avihData, 52, 4)); $thisfile_riff_raw_avih['flags']['hasindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000010); $thisfile_riff_raw_avih['flags']['mustuseindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000020); @@ -595,20 +743,20 @@ class getid3_riff $thisfile_riff_raw['strh'][$i] = array(); $thisfile_riff_raw_strh_current = &$thisfile_riff_raw['strh'][$i]; - $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; - $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); - $thisfile_riff_raw_strh_current['dwFlags'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 8, 4)); // Contains AVITF_* flags - $thisfile_riff_raw_strh_current['wPriority'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 12, 2)); - $thisfile_riff_raw_strh_current['wLanguage'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 14, 2)); - $thisfile_riff_raw_strh_current['dwInitialFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 16, 4)); - $thisfile_riff_raw_strh_current['dwScale'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 20, 4)); - $thisfile_riff_raw_strh_current['dwRate'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 24, 4)); - $thisfile_riff_raw_strh_current['dwStart'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 28, 4)); - $thisfile_riff_raw_strh_current['dwLength'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 32, 4)); - $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 36, 4)); - $thisfile_riff_raw_strh_current['dwQuality'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 40, 4)); - $thisfile_riff_raw_strh_current['dwSampleSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 44, 4)); - $thisfile_riff_raw_strh_current['rcFrame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 48, 4)); + $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; + $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); + $thisfile_riff_raw_strh_current['dwFlags'] = $this->EitherEndian2Int(substr($strhData, 8, 4)); // Contains AVITF_* flags + $thisfile_riff_raw_strh_current['wPriority'] = $this->EitherEndian2Int(substr($strhData, 12, 2)); + $thisfile_riff_raw_strh_current['wLanguage'] = $this->EitherEndian2Int(substr($strhData, 14, 2)); + $thisfile_riff_raw_strh_current['dwInitialFrames'] = $this->EitherEndian2Int(substr($strhData, 16, 4)); + $thisfile_riff_raw_strh_current['dwScale'] = $this->EitherEndian2Int(substr($strhData, 20, 4)); + $thisfile_riff_raw_strh_current['dwRate'] = $this->EitherEndian2Int(substr($strhData, 24, 4)); + $thisfile_riff_raw_strh_current['dwStart'] = $this->EitherEndian2Int(substr($strhData, 28, 4)); + $thisfile_riff_raw_strh_current['dwLength'] = $this->EitherEndian2Int(substr($strhData, 32, 4)); + $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($strhData, 36, 4)); + $thisfile_riff_raw_strh_current['dwQuality'] = $this->EitherEndian2Int(substr($strhData, 40, 4)); + $thisfile_riff_raw_strh_current['dwSampleSize'] = $this->EitherEndian2Int(substr($strhData, 44, 4)); + $thisfile_riff_raw_strh_current['rcFrame'] = $this->EitherEndian2Int(substr($strhData, 48, 4)); $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strh_current['fccHandler']); $thisfile_video['fourcc'] = $thisfile_riff_raw_strh_current['fccHandler']; @@ -632,19 +780,8 @@ class getid3_riff switch ($strhfccType) { case 'vids': - $thisfile_riff_raw_strf_strhfccType_streamindex = getid3_riff::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($ThisFileInfo['fileformat'] == 'riff')); - //$thisfile_riff_raw_strf_strhfccType_streamindex['biSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure - //$thisfile_riff_raw_strf_strhfccType_streamindex['biWidth'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 4, 4)); // width of the bitmap in pixels - //$thisfile_riff_raw_strf_strhfccType_streamindex['biHeight'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner - //$thisfile_riff_raw_strf_strhfccType_streamindex['biPlanes'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 - //$thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 14, 2)); // Specifies the number of bits per pixels - //$thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'] = substr($strfData, 16, 4); // - //$thisfile_riff_raw_strf_strhfccType_streamindex['biSizeImage'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) - //$thisfile_riff_raw_strf_strhfccType_streamindex['biXPelsPerMeter'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 24, 4)); // horizontal resolution, in pixels per metre, of the target device - //$thisfile_riff_raw_strf_strhfccType_streamindex['biYPelsPerMeter'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 28, 4)); // vertical resolution, in pixels per metre, of the target device - //$thisfile_riff_raw_strf_strhfccType_streamindex['biClrUsed'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression - //$thisfile_riff_raw_strf_strhfccType_streamindex['biClrImportant'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important - + $thisfile_riff_raw_strf_strhfccType_streamindex = getid3_riff::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($info['fileformat'] == 'riff')); +//echo '
    '.print_r($thisfile_riff_raw_strf_strhfccType_streamindex, true).'
    '; $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount']; if ($thisfile_riff_video_current['codec'] == 'DV') { @@ -659,30 +796,32 @@ class getid3_riff break; default: - $ThisFileInfo['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"'; + $info['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"'; break; } } } - if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { + if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { - $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); - $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; - $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; + $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; + if (getid3_riff::RIFFfourccLookup($thisfile_video['fourcc'])) { + $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_video['fourcc']); + $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; + } switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) { case 'HFYU': // Huffman Lossless Codec case 'IRAW': // Intel YUV Uncompressed case 'YUY2': // Uncompressed YUV 4:2:2 $thisfile_video['lossless'] = true; - $thisfile_video['bits_per_sample'] = 24; + //$thisfile_video['bits_per_sample'] = 24; break; default: $thisfile_video['lossless'] = false; - $thisfile_video['bits_per_sample'] = 24; + //$thisfile_video['bits_per_sample'] = 24; break; } @@ -696,26 +835,26 @@ class getid3_riff $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'cda'; $thisfile_audio['lossless'] = true; - unset($ThisFileInfo['mime_type']); + unset($info['mime_type']); - $thisfile_avdataoffset = 44; + $info['avdataoffset'] = 44; if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) { // shortcut $thisfile_riff_CDDA_fmt_0 = &$thisfile_riff['CDDA']['fmt '][0]; - $thisfile_riff_CDDA_fmt_0['unknown1'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); - $thisfile_riff_CDDA_fmt_0['track_num'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); - $thisfile_riff_CDDA_fmt_0['disc_id'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); - $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); - $thisfile_riff_CDDA_fmt_0['playtime_frames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); - $thisfile_riff_CDDA_fmt_0['unknown6'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); - $thisfile_riff_CDDA_fmt_0['unknown7'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); + $thisfile_riff_CDDA_fmt_0['unknown1'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); + $thisfile_riff_CDDA_fmt_0['track_num'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); + $thisfile_riff_CDDA_fmt_0['disc_id'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); + $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); + $thisfile_riff_CDDA_fmt_0['playtime_frames'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); + $thisfile_riff_CDDA_fmt_0['unknown6'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); + $thisfile_riff_CDDA_fmt_0['unknown7'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75; $thisfile_riff_CDDA_fmt_0['playtime_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75; - $ThisFileInfo['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; - $ThisFileInfo['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; + $info['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; + $info['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; // hardcoded data for CD-audio $thisfile_audio['sample_rate'] = 44100; @@ -732,19 +871,19 @@ class getid3_riff $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'aiff'; $thisfile_audio['lossless'] = true; - $ThisFileInfo['mime_type'] = 'audio/x-aiff'; + $info['mime_type'] = 'audio/x-aiff'; if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) { - $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; - $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; - if ($thisfile_avdataend > $ThisFileInfo['filesize']) { - if (($thisfile_avdataend == ($ThisFileInfo['filesize'] + 1)) && (($ThisFileInfo['filesize'] % 2) == 1)) { + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; + if ($info['avdataend'] > $info['filesize']) { + if (($info['avdataend'] == ($info['filesize'] + 1)) && (($info['filesize'] % 2) == 1)) { // structures rounded to 2-byte boundary, but dumb encoders // forget to pad end of file to make this actually work } else { - $ThisFileInfo['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' bytes found'; + $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found'; } - $thisfile_avdataend = $ThisFileInfo['filesize']; + $info['avdataend'] = $info['filesize']; } } @@ -799,28 +938,28 @@ class getid3_riff } $thisfile_audio['sample_rate'] = $thisfile_riff_audio['sample_rate']; if ($thisfile_audio['sample_rate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupted AIFF file: sample_rate == zero'; + $info['error'][] = 'Corrupted AIFF file: sample_rate == zero'; return false; } - $ThisFileInfo['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; + $info['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; } if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) { $offset = 0; - $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); + $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); $offset += 2; for ($i = 0; $i < $CommentCount; $i++) { - $ThisFileInfo['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); + $info['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); $offset += 4; - $ThisFileInfo['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); + $info['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); $offset += 2; - $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); + $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); $offset += 2; - $ThisFileInfo['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); + $info['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); $offset += $CommentLength; - $ThisFileInfo['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($ThisFileInfo['comments_raw'][$i]['timestamp']); - $thisfile_riff['comments']['comment'][] = $ThisFileInfo['comments_raw'][$i]['comment']; + $info['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($info['comments_raw'][$i]['timestamp']); + $thisfile_riff['comments']['comment'][] = $info['comments_raw'][$i]['comment']; } } @@ -830,6 +969,18 @@ class getid3_riff $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; } } + + if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) { + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_id3v2 = new getid3_id3v2($getid3_temp); + $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8; + if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) { + $info['id3v2'] = $getid3_temp->info['id3v2']; + } + unset($getid3_temp, $getid3_id3v2); + } break; case '8SVX': @@ -837,13 +988,13 @@ class getid3_riff $thisfile_audio_dataformat = '8svx'; $thisfile_audio['bits_per_sample'] = 8; $thisfile_audio['channels'] = 1; // overridden below, if need be - $ThisFileInfo['mime_type'] = 'audio/x-aiff'; + $info['mime_type'] = 'audio/x-aiff'; if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) { - $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; - $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; - if ($thisfile_avdataend > $ThisFileInfo['filesize']) { - $ThisFileInfo['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' bytes found'; + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; + if ($info['avdataend'] > $info['filesize']) { + $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found'; } } @@ -865,17 +1016,17 @@ class getid3_riff case 0: $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; $thisfile_audio['lossless'] = true; - $ActualBitsPerSample = 8; + $ActualBitsPerSample = 8; break; case 1: $thisfile_audio['codec'] = 'Fibonacci-delta encoding'; $thisfile_audio['lossless'] = false; - $ActualBitsPerSample = 4; + $ActualBitsPerSample = 4; break; default: - $ThisFileInfo['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"'; + $info['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"'; break; } } @@ -893,7 +1044,7 @@ class getid3_riff break; default: - $ThisFileInfo['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"'; + $info['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"'; break; } @@ -908,41 +1059,41 @@ class getid3_riff $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels']; if (!empty($thisfile_audio['bitrate'])) { - $ThisFileInfo['playtime_seconds'] = ($thisfile_avdataend - $thisfile_avdataoffset) / ($thisfile_audio['bitrate'] / 8); + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($thisfile_audio['bitrate'] / 8); } break; case 'CDXA': - $ThisFileInfo['mime_type'] = 'video/mpeg'; + $info['mime_type'] = 'video/mpeg'; if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) { - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, false)) { - $dummy = $ThisFileInfo; - $dummy['error'] = array(); - $mpeg_scanner = new getid3_mpeg($fd, $dummy); - if (empty($dummy['error'])) { - $ThisFileInfo['audio'] = $dummy['audio']; - $ThisFileInfo['video'] = $dummy['video']; - $ThisFileInfo['mpeg'] = $dummy['mpeg']; - $ThisFileInfo['warning'] = $dummy['warning']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_mpeg = new getid3_mpeg($getid3_temp); + $getid3_mpeg->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['video'] = $getid3_temp->info['video']; + $info['mpeg'] = $getid3_temp->info['mpeg']; + $info['warning'] = $getid3_temp->info['warning']; } - unset($mpeg_scanner); + unset($getid3_temp, $getid3_mpeg); } } break; default: - $ThisFileInfo['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead'; - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead'; + unset($info['fileformat']); break; } - if (@$thisfile_riff_raw['fmt ']['wFormatTag'] == 1) { + if (isset($thisfile_riff_raw['fmt ']['wFormatTag']) && ($thisfile_riff_raw['fmt ']['wFormatTag'] == 1)) { // http://www.mega-nerd.com/erikd/Blog/Windiots/dts.html - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $FirstFourBytes = fread($fd, 4); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $FirstFourBytes = fread($this->getid3->fp, 4); if (preg_match('/^\xFF\x1F\x00\xE8/s', $FirstFourBytes)) { // DTSWAV $thisfile_audio_dataformat = 'dts'; @@ -959,46 +1110,52 @@ class getid3_riff if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) { $this->RIFFcommentsParse($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); } - - if (empty($thisfile_audio['encoder']) && !empty($ThisFileInfo['mpeg']['audio']['LAME']['short_version'])) { - $thisfile_audio['encoder'] = $ThisFileInfo['mpeg']['audio']['LAME']['short_version']; + if (isset($thisfile_riff['AVI ']['INFO']) && is_array($thisfile_riff['AVI ']['INFO'])) { + $this->RIFFcommentsParse($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']); } - if (!isset($ThisFileInfo['playtime_seconds'])) { - $ThisFileInfo['playtime_seconds'] = 0; - } - if (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { - $ThisFileInfo['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + if (empty($thisfile_audio['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version'])) { + $thisfile_audio['encoder'] = $info['mpeg']['audio']['LAME']['short_version']; } - if ($ThisFileInfo['playtime_seconds'] > 0) { + if (!isset($info['playtime_seconds'])) { + $info['playtime_seconds'] = 0; + } + if (isset($thisfile_riff_raw['strh'][0]['dwLength']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { + // needed for >2GB AVIs where 'avih' chunk only lists number of frames in that chunk, not entire movie + $info['playtime_seconds'] = $thisfile_riff_raw['strh'][0]['dwLength'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + } elseif (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { + $info['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + } + + if ($info['playtime_seconds'] > 0) { if (isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { - if (!isset($ThisFileInfo['bitrate'])) { - $ThisFileInfo['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + if (!isset($info['bitrate'])) { + $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); } } elseif (isset($thisfile_riff_audio) && !isset($thisfile_riff_video)) { if (!isset($thisfile_audio['bitrate'])) { - $thisfile_audio['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + $thisfile_audio['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); } } elseif (!isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { if (!isset($thisfile_video['bitrate'])) { - $thisfile_video['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + $thisfile_video['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); } } } - if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($ThisFileInfo['playtime_seconds'] > 0)) { + if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($info['playtime_seconds'] > 0)) { - $ThisFileInfo['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); $thisfile_audio['bitrate'] = 0; - $thisfile_video['bitrate'] = $ThisFileInfo['bitrate']; + $thisfile_video['bitrate'] = $info['bitrate']; foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) { $thisfile_video['bitrate'] -= $audioinfoarray['bitrate']; $thisfile_audio['bitrate'] += $audioinfoarray['bitrate']; @@ -1011,14 +1168,14 @@ class getid3_riff } } - if (isset($ThisFileInfo['mpeg']['audio'])) { - $thisfile_audio_dataformat = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; - $thisfile_audio['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; - $thisfile_audio['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; - $thisfile_audio['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; - $thisfile_audio['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); - if (!empty($ThisFileInfo['mpeg']['audio']['codec'])) { - $thisfile_audio['codec'] = $ThisFileInfo['mpeg']['audio']['codec'].' '.$thisfile_audio['codec']; + if (isset($info['mpeg']['audio'])) { + $thisfile_audio_dataformat = 'mp'.$info['mpeg']['audio']['layer']; + $thisfile_audio['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $thisfile_audio['channels'] = $info['mpeg']['audio']['channels']; + $thisfile_audio['bitrate'] = $info['mpeg']['audio']['bitrate']; + $thisfile_audio['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + if (!empty($info['mpeg']['audio']['codec'])) { + $thisfile_audio['codec'] = $info['mpeg']['audio']['codec'].' '.$thisfile_audio['codec']; } if (!empty($thisfile_audio['streams'])) { foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) { @@ -1031,7 +1188,9 @@ class getid3_riff } } } - $thisfile_audio['encoder_options'] = getid3_mp3::GuessEncoderOptions($ThisFileInfo); + $getid3_mp3 = new getid3_mp3($this->getid3); + $thisfile_audio['encoder_options'] = $getid3_mp3->GuessEncoderOptions(); + unset($getid3_mp3); } @@ -1062,7 +1221,7 @@ class getid3_riff } - function RIFFcommentsParse(&$RIFFinfoArray, &$CommentsTargetArray) { + static function RIFFcommentsParse(&$RIFFinfoArray, &$CommentsTargetArray) { $RIFFinfoKeyLookup = array( 'IARL'=>'archivallocation', 'IART'=>'artist', @@ -1108,7 +1267,11 @@ class getid3_riff if (isset($RIFFinfoArray[$key])) { foreach ($RIFFinfoArray[$key] as $commentid => $commentdata) { if (trim($commentdata['data']) != '') { - @$CommentsTargetArray[$value][] = trim($commentdata['data']); + if (isset($CommentsTargetArray[$value])) { + $CommentsTargetArray[$value][] = trim($commentdata['data']); + } else { + $CommentsTargetArray[$value] = array(trim($commentdata['data'])); + } } } } @@ -1116,29 +1279,39 @@ class getid3_riff return true; } - function ParseRIFF(&$fd, $startoffset, $maxoffset, &$ThisFileInfo) { - $maxoffset = min($maxoffset, $ThisFileInfo['avdataend']); + function ParseRIFF($startoffset, $maxoffset) { + $info = &$this->getid3->info; + + $maxoffset = min($maxoffset, $info['avdataend']); $RIFFchunk = false; $FoundAllChunksWeNeed = false; - if (($startoffset < 0) || ($startoffset >= pow(2, 31))) { - $ThisFileInfo['warning'][] = 'Unable to ParseRIFF() at '.$startoffset.' because beyond 2GB limit of PHP filesystem functions'; + if (($startoffset < 0) || !getid3_lib::intValueSupported($startoffset)) { + $info['warning'][] = 'Unable to ParseRIFF() at '.$startoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; return false; } - fseek($fd, $startoffset, SEEK_SET); + $max_usable_offset = min(PHP_INT_MAX - 1024, $maxoffset); + if ($maxoffset > $max_usable_offset) { + $info['warning'][] = 'ParseRIFF() may return incomplete data for chunk starting at '.$startoffset.' because beyond it extends to '.$maxoffset.', which is beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; + } + fseek($this->getid3->fp, $startoffset, SEEK_SET); - while (ftell($fd) < $maxoffset) { - $chunkname = fread($fd, 4); + while (ftell($this->getid3->fp) < $max_usable_offset) { + $chunknamesize = fread($this->getid3->fp, 8); + $chunkname = substr($chunknamesize, 0, 4); + $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); if (strlen($chunkname) < 4) { - $ThisFileInfo['error'][] = 'Expecting chunk name at offset '.(ftell($fd) - 4).' but found nothing. Aborting RIFF parsing.'; + $info['error'][] = 'Expecting chunk name at offset '.(ftell($this->getid3->fp) - 4).' but found nothing. Aborting RIFF parsing.'; break; } - - $chunksize = getid3_riff::EitherEndian2Int($ThisFileInfo, fread($fd, 4)); if ($chunksize == 0) { - $ThisFileInfo['warning'][] = 'Chunk size at offset '.(ftell($fd) - 4).' is zero. Aborting RIFF parsing.'; - continue; + if ($chunkname == 'JUNK') { + // we'll allow zero-size JUNK frames + } else { + $info['warning'][] = 'Chunk size at offset '.(ftell($this->getid3->fp) - 4).' is zero. Aborting RIFF parsing.'; + break; + } } if (($chunksize % 2) != 0) { // all structures are packed on word boundaries @@ -1147,9 +1320,9 @@ class getid3_riff switch ($chunkname) { case 'LIST': - $listname = fread($fd, 4); - if (eregi('^(movi|rec )$', $listname)) { - $RIFFchunk[$listname]['offset'] = ftell($fd) - 4; + $listname = fread($this->getid3->fp, 4); + if (preg_match('#^(movi|rec )$#i', $listname)) { + $RIFFchunk[$listname]['offset'] = ftell($this->getid3->fp) - 4; $RIFFchunk[$listname]['size'] = $chunksize; if ($FoundAllChunksWeNeed) { @@ -1158,8 +1331,8 @@ class getid3_riff } else { - $WhereWeWere = ftell($fd); - $AudioChunkHeader = fread($fd, 12); + $WhereWeWere = ftell($this->getid3->fp); + $AudioChunkHeader = fread($this->getid3->fp, 12); $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); @@ -1169,40 +1342,45 @@ class getid3_riff if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) { // MP3 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { - $dummy = $ThisFileInfo; - $dummy['avdataoffset'] = ftell($fd) - 4; - $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; - getid3_mp3::getOnlyMPEGaudioInfo($fd, $dummy, $dummy['avdataoffset'], false); - if (isset($dummy['mpeg']['audio'])) { - $ThisFileInfo = $dummy; - $ThisFileInfo['audio']['dataformat'] = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; - $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; - $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; - $ThisFileInfo['bitrate'] = $ThisFileInfo['audio']['bitrate']; - $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - 4; + $getid3_temp->info['avdataend'] = ftell($this->getid3->fp) + $AudioChunkSize; + $getid3_mp3 = new getid3_mp3($getid3_temp); + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (isset($getid3_temp->info['mpeg']['audio'])) { + $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; + $info['audio'] = $getid3_temp->info['audio']; + $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + //$info['bitrate'] = $info['audio']['bitrate']; } - unset($dummy); + unset($getid3_temp, $getid3_mp3); } } elseif (preg_match('/^\x0B\x77/s', $FirstFourBytes)) { // AC3 - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - - $dummy = $ThisFileInfo; - $dummy['avdataoffset'] = ftell($fd) - 4; - $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; - $dummy['error'] = array(); - $ac3_tag = new getid3_ac3($fd, $dummy); - if (empty($dummy['error'])) { - $ThisFileInfo['audio'] = $dummy['audio']; - $ThisFileInfo['ac3'] = $dummy['ac3']; - $ThisFileInfo['warning'] = $dummy['warning']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - 4; + $getid3_temp->info['avdataend'] = ftell($this->getid3->fp) + $AudioChunkSize; + $getid3_ac3 = new getid3_ac3($getid3_temp); + $getid3_ac3->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $key => $value) { + $info['warning'][] = $value; + } + } } - unset($ac3_tag); - + unset($getid3_temp, $getid3_ac3); } } @@ -1210,23 +1388,23 @@ class getid3_riff } $FoundAllChunksWeNeed = true; - fseek($fd, $WhereWeWere, SEEK_SET); + fseek($this->getid3->fp, $WhereWeWere, SEEK_SET); } - fseek($fd, $chunksize - 4, SEEK_CUR); + fseek($this->getid3->fp, $chunksize - 4, SEEK_CUR); - //} elseif (ereg('^[0-9]{2}(wb|pc|dc|db)$', $listname)) { - // + //} elseif (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#i', $listname)) { + // // // data chunk, ignore - // + // } else { if (!isset($RIFFchunk[$listname])) { $RIFFchunk[$listname] = array(); } $LISTchunkParent = $listname; - $LISTchunkMaxOffset = ftell($fd) - 4 + $chunksize; - if ($parsedChunk = getid3_riff::ParseRIFF($fd, ftell($fd), ftell($fd) + $chunksize - 4, $ThisFileInfo)) { + $LISTchunkMaxOffset = ftell($this->getid3->fp) - 4 + $chunksize; + if ($parsedChunk = $this->ParseRIFF(ftell($this->getid3->fp), ftell($this->getid3->fp) + $chunksize - 4)) { $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); } @@ -1234,63 +1412,61 @@ class getid3_riff break; default: - if (eregi('^[0-9]{2}(wb|pc|dc|db)$', $chunkname)) { - $nextoffset = ftell($fd) + $chunksize; - if (($nextoffset < 0) || ($nextoffset >= pow(2, 31))) { - $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond 2GB limit of PHP filesystem functions'; + if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { + $nextoffset = ftell($this->getid3->fp) + $chunksize; + if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { + $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; break 2; } - fseek($fd, $nextoffset, SEEK_SET); + fseek($this->getid3->fp, $nextoffset, SEEK_SET); break; } $thisindex = 0; if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { $thisindex = count($RIFFchunk[$chunkname]); } - $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($fd) - 8; + $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($this->getid3->fp) - 8; $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; switch ($chunkname) { case 'data': - $ThisFileInfo['avdataoffset'] = ftell($fd); - $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataoffset'] + $chunksize; + $info['avdataoffset'] = ftell($this->getid3->fp); + $info['avdataend'] = $info['avdataoffset'] + $chunksize; - $RIFFdataChunkContentsTest = fread($fd, 36); + $RIFFdataChunkContentsTest = fread($this->getid3->fp, 36); if ((strlen($RIFFdataChunkContentsTest) > 0) && preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { // Probably is MP3 data if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { - - // copy info array - $dummy = $ThisFileInfo; - - getid3_mp3::getOnlyMPEGaudioInfo($fd, $dummy, $RIFFchunk[$chunkname][$thisindex]['offset'], false); - - // use dummy array unless error - if (empty($dummy['error'])) { - $ThisFileInfo = $dummy; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; + $getid3_mp3 = new getid3_mp3($getid3_temp); + $getid3_mp3->getOnlyMPEGaudioInfo($RIFFchunk[$chunkname][$thisindex]['offset'], false); + if (empty($getid3_temp->info['error'])) { + $info['mpeg'] = $getid3_temp->info['mpeg']; + $info['audio'] = $getid3_temp->info['audio']; } + unset($getid3_temp, $getid3_mp3); } } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 2) == "\x0B\x77")) { // This is probably AC-3 data - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - - $dummy = $ThisFileInfo; - $dummy['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $dummy['avdataend'] = $dummy['avdataoffset'] + $RIFFchunk[$chunkname][$thisindex]['size']; - $dummy['error'] = array(); - - $ac3_tag = new getid3_ac3($fd, $dummy); - if (empty($dummy['error'])) { - $ThisFileInfo['audio'] = $dummy['audio']; - $ThisFileInfo['ac3'] = $dummy['ac3']; - $ThisFileInfo['warning'] = $dummy['warning']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; + $getid3_ac3 = new getid3_ac3($getid3_temp); + $getid3_ac3->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + $info['warning'] = $getid3_temp->info['warning']; } - unset($ac3_tag); - + unset($getid3_temp, $getid3_ac3); } } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 8, 2) == "\x77\x0B")) { @@ -1299,37 +1475,38 @@ class getid3_riff // AC-3 content, but not encoded in same format as normal AC-3 file // For one thing, byte order is swapped - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { // ok to use tmpfile here - only 56 bytes - if ($fd_temp = tmpfile()) { + if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) { + if ($fd_temp = fopen($RIFFtempfilename, 'wb')) { + for ($i = 0; $i < 28; $i += 2) { + // swap byte order + fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); + fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); + } + fclose($fd_temp); - for ($i = 0; $i < 28; $i += 2) { - // swap byte order - fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); - fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); - } - - $dummy = $ThisFileInfo; - $dummy['avdataoffset'] = 0; - $dummy['avdataend'] = 20; - $dummy['error'] = array(); - $ac3_tag = new getid3_ac3($fd_temp, $dummy); - fclose($fd_temp); - if (empty($dummy['error'])) { - $ThisFileInfo['audio'] = $dummy['audio']; - $ThisFileInfo['ac3'] = $dummy['ac3']; - $ThisFileInfo['warning'] = $dummy['warning']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($RIFFtempfilename); + $getid3_temp->info['avdataend'] = 20; + $getid3_ac3 = new getid3_ac3($getid3_temp); + $getid3_ac3->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + $info['warning'] = $getid3_temp->info['warning']; + } else { + $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): '.implode(';', $getid3_temp->info['error']); + } + unset($getid3_ac3, $getid3_temp); } else { - $ThisFileInfo['error'][] = 'Errors parsing DolbyDigital WAV: '.explode(';', $dummy['error']); + $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } - unset($ac3_tag); + unlink($RIFFtempfilename); } else { - - $ThisFileInfo['error'][] = 'Could not create temporary file to analyze DolbyDigital WAV'; - + $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } } @@ -1337,9 +1514,9 @@ class getid3_riff } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk')) { // This is WavPack data - $ThisFileInfo['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $ThisFileInfo['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); - getid3_riff::RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28), $ThisFileInfo); + $info['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); + $this->RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28)); } else { @@ -1348,13 +1525,14 @@ class getid3_riff } $nextoffset = $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize; - if (($nextoffset < 0) || ($nextoffset >= pow(2, 31))) { - $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond 2GB limit of PHP filesystem functions'; + if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { + $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; break 3; } - fseek($fd, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); + fseek($this->getid3->fp, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); break; + case 'iXML': case 'bext': case 'cart': case 'fmt ': @@ -1364,11 +1542,35 @@ class getid3_riff case 'MEXT': case 'DISP': // always read data in - $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); + case 'JUNK': + // should be: never read data in + // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) + if ($chunksize < 1048576) { + if ($chunksize > 0) { + $RIFFchunk[$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); + if ($chunkname == 'JUNK') { + if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { + // only keep text characters [chr(32)-chr(127)] + $info['riff']['comments']['junk'][] = trim($matches[1]); + } + // but if nothing there, ignore + // remove the key in either case + unset($RIFFchunk[$chunkname][$thisindex]['data']); + } + } + } else { + $info['warning'][] = 'chunk "'.$chunkname.'" at offset '.ftell($this->getid3->fp).' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data'; + $nextoffset = ftell($this->getid3->fp) + $chunksize; + if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { + $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; + break 3; + } + fseek($this->getid3->fp, $nextoffset, SEEK_SET); + } break; default: - if (!ereg('^[0-9]{2}(wb|pc|dc|db)$', $chunkname) && !empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { + if (!preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname) && !empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; unset($RIFFchunk[$chunkname][$thisindex]['offset']); @@ -1379,17 +1581,18 @@ class getid3_riff if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { unset($RIFFchunk[$chunkname]); } - $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($fd, $chunksize); - } elseif ($chunksize < 2048) { + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); + //} elseif (in_array($chunkname, array('ID3 ')) || (($chunksize > 0) && ($chunksize < 2048))) { + } elseif (($chunksize > 0) && ($chunksize < 2048)) { // only read data in if smaller than 2kB - $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); + $RIFFchunk[$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); } else { - $nextoffset = ftell($fd) + $chunksize; - if (($nextoffset < 0) || ($nextoffset >= pow(2, 31))) { - $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond 2GB limit of PHP filesystem functions'; + $nextoffset = ftell($this->getid3->fp) + $chunksize; + if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { + $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; break 3; } - fseek($fd, $nextoffset, SEEK_SET); + fseek($this->getid3->fp, $nextoffset, SEEK_SET); } break; } @@ -1403,11 +1606,11 @@ class getid3_riff } - function ParseRIFFdata(&$RIFFdata, &$ThisFileInfo) { + function ParseRIFFdata(&$RIFFdata) { + $info = &$this->getid3->info; if ($RIFFdata) { - - $tempfile = tempnam('*', 'getID3'); - $fp_temp = fopen($tempfile, "wb"); + $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); + $fp_temp = fopen($tempfile, 'wb'); $RIFFdataLength = strlen($RIFFdata); $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4); for ($i = 0; $i < 4; $i++) { @@ -1416,24 +1619,32 @@ class getid3_riff fwrite($fp_temp, $RIFFdata); fclose($fp_temp); - $fp_temp = fopen($tempfile, "rb"); - $dummy = array('filesize'=>$RIFFdataLength, 'filenamepath'=>$ThisFileInfo['filenamepath'], 'tags'=>$ThisFileInfo['tags'], 'avdataoffset'=>0, 'avdataend'=>$RIFFdataLength, 'warning'=>$ThisFileInfo['warning'], 'error'=>$ThisFileInfo['error'], 'comments'=>$ThisFileInfo['comments'], 'audio'=>(isset($ThisFileInfo['audio']) ? $ThisFileInfo['audio'] : array()), 'video'=>(isset($ThisFileInfo['video']) ? $ThisFileInfo['video'] : array())); - $riff = new getid3_riff($fp_temp, $dummy); - $ThisFileInfo['riff'] = $dummy['riff']; - $ThisFileInfo['warning'] = $dummy['warning']; - $ThisFileInfo['error'] = $dummy['error']; - $ThisFileInfo['tags'] = $dummy['tags']; - $ThisFileInfo['comments'] = $dummy['comments']; - unset($riff); - fclose($fp_temp); + $getid3_temp = new getID3(); + $getid3_temp->openfile($tempfile); + $getid3_temp->info['filesize'] = $RIFFdataLength; + $getid3_temp->info['filenamepath'] = $info['filenamepath']; + $getid3_temp->info['tags'] = $info['tags']; + $getid3_temp->info['warning'] = $info['warning']; + $getid3_temp->info['error'] = $info['error']; + $getid3_temp->info['comments'] = $info['comments']; + $getid3_temp->info['audio'] = (isset($info['audio']) ? $info['audio'] : array()); + $getid3_temp->info['video'] = (isset($info['video']) ? $info['video'] : array()); + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->Analyze(); + + $info['riff'] = $getid3_temp->info['riff']; + $info['warning'] = $getid3_temp->info['warning']; + $info['error'] = $getid3_temp->info['error']; + $info['tags'] = $getid3_temp->info['tags']; + $info['comments'] = $getid3_temp->info['comments']; + unset($getid3_riff, $getid3_temp); unlink($tempfile); - return true; } return false; } - function RIFFparseWAVEFORMATex($WaveFormatExData) { + public static function RIFFparseWAVEFORMATex($WaveFormatExData) { // shortcut $WaveFormatEx['raw'] = array(); $WaveFormatEx_raw = &$WaveFormatEx['raw']; @@ -1458,7 +1669,7 @@ class getid3_riff } - function RIFFparseWavPackHeader($WavPackChunkData, &$ThisFileInfo) { + function RIFFparseWavPackHeader($WavPackChunkData) { // typedef struct { // char ckID [4]; // long ckSize; @@ -1470,8 +1681,9 @@ class getid3_riff // } WavpackHeader; // shortcut - $ThisFileInfo['wavpack'] = array(); - $thisfile_wavpack = &$ThisFileInfo['wavpack']; + $info = &$this->getid3->info; + $info['wavpack'] = array(); + $thisfile_wavpack = &$info['wavpack']; $thisfile_wavpack['version'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 0, 2)); if ($thisfile_wavpack['version'] >= 2) { @@ -1518,25 +1730,108 @@ class getid3_riff return true; } - function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { - // yes it's ugly to instantiate a getid3_lib object here, suggested alternative please? - $getid3_lib = new getid3_lib(); + public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { + $functionname = ($littleEndian ? 'LittleEndian2Int' : 'BigEndian2Int'); - $parsed['biSize'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure - $parsed['biWidth'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 4, 4)); // width of the bitmap in pixels - $parsed['biHeight'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner - $parsed['biPlanes'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 - $parsed['biBitCount'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 14, 2)); // Specifies the number of bits per pixels + $parsed['biSize'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure + $parsed['biWidth'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 4, 4)); // width of the bitmap in pixels + $parsed['biHeight'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner + $parsed['biPlanes'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 + $parsed['biBitCount'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 14, 2)); // Specifies the number of bits per pixels $parsed['fourcc'] = substr($BITMAPINFOHEADER, 16, 4); // compression identifier - $parsed['biSizeImage'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) - $parsed['biXPelsPerMeter'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 24, 4)); // horizontal resolution, in pixels per metre, of the target device - $parsed['biYPelsPerMeter'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 28, 4)); // vertical resolution, in pixels per metre, of the target device - $parsed['biClrUsed'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression - $parsed['biClrImportant'] = $getid3_lib->$functionname(substr($BITMAPINFOHEADER, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important + $parsed['biSizeImage'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) + $parsed['biXPelsPerMeter'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 24, 4)); // horizontal resolution, in pixels per metre, of the target device + $parsed['biYPelsPerMeter'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 28, 4)); // vertical resolution, in pixels per metre, of the target device + $parsed['biClrUsed'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression + $parsed['biClrImportant'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important + return $parsed; } - function RIFFwFormatTagLookup($wFormatTag) { + static function ParseDIVXTAG($DIVXTAG) { + // structure from "IDivX" source, Form1.frm, by "Greg Frazier of Daemonic Software Group", email: gfrazier@icestorm.net, web: http://dsg.cjb.net/ + // source available at http://files.divx-digest.com/download/c663efe7ef8ad2e90bf4af4d3ea6188a/on0SWN2r/edit/IDivX.zip + // 'Byte Layout: '1111111111111111 + // '32 for Movie - 1 '1111111111111111 + // '28 for Author - 6 '6666666666666666 + // '4 for year - 2 '6666666666662222 + // '3 for genre - 3 '7777777777777777 + // '48 for Comments - 7 '7777777777777777 + // '1 for Rating - 4 '7777777777777777 + // '5 for Future Additions - 0 '333400000DIVXTAG + // '128 bytes total + + static $DIVXTAGgenre = array( + 0 => 'Action', + 1 => 'Action/Adventure', + 2 => 'Adventure', + 3 => 'Adult', + 4 => 'Anime', + 5 => 'Cartoon', + 6 => 'Claymation', + 7 => 'Comedy', + 8 => 'Commercial', + 9 => 'Documentary', + 10 => 'Drama', + 11 => 'Home Video', + 12 => 'Horror', + 13 => 'Infomercial', + 14 => 'Interactive', + 15 => 'Mystery', + 16 => 'Music Video', + 17 => 'Other', + 18 => 'Religion', + 19 => 'Sci Fi', + 20 => 'Thriller', + 21 => 'Western', + ); + static $DIVXTAGrating = array( + 0=>'Unrated', + 1=>'G', + 2=>'PG', + 3=>'PG-13', + 4=>'R', + 5=>'NC-17' + ); + + $parsed['title'] = trim(substr($DIVXTAG, 0, 32)); + $parsed['artist'] = trim(substr($DIVXTAG, 32, 28)); + $parsed['year'] = intval(trim(substr($DIVXTAG, 60, 4))); + $parsed['comment'] = trim(substr($DIVXTAG, 64, 48)); + $parsed['genre_id'] = intval(trim(substr($DIVXTAG, 112, 3))); + $parsed['rating_id'] = ord(substr($DIVXTAG, 115, 1)); + //$parsed['padding'] = substr($DIVXTAG, 116, 5); // 5-byte null + //$parsed['magic'] = substr($DIVXTAG, 121, 7); // "DIVXTAG" + + $parsed['genre'] = (isset($DIVXTAGgenre[$parsed['genre_id']]) ? $DIVXTAGgenre[$parsed['genre_id']] : $parsed['genre_id']); + $parsed['rating'] = (isset($DIVXTAGrating[$parsed['rating_id']]) ? $DIVXTAGrating[$parsed['rating_id']] : $parsed['rating_id']); + return $parsed; + } + + static function RIFFwaveSNDMtagLookup($tagshortname) { + $begin = __LINE__; + + /** This is not a comment! + + ©kwd keywords + ©BPM bpm + ©trt tracktitle + ©des description + ©gen category + ©fin featuredinstrument + ©LID longid + ©bex bwdescription + ©pub publisher + ©cdt cdtitle + ©alb library + ©com composer + + */ + + return getid3_lib::EmbeddedLookup($tagshortname, $begin, __LINE__, __FILE__, 'riff-sndm'); + } + + static function RIFFwFormatTagLookup($wFormatTag) { $begin = __LINE__; @@ -1707,7 +2002,7 @@ class getid3_riff } - function RIFFfourccLookup($fourcc) { + public static function RIFFfourccLookup($fourcc) { $begin = __LINE__; @@ -1811,9 +2106,12 @@ class getid3_riff ETV2 eTreppid Video ETV2 ETVC eTreppid Video ETVC FLIC Autodesk FLI/FLC Animation + FLV1 Sorenson Spark + FLV4 On2 TrueMotion VP6 FRWT Darim Vision Forward Motion JPEG (www.darvision.com) FRWU Darim Vision Forward Uncompressed (www.darvision.com) FLJP D-Vision Field Encoded Motion JPEG + FPS1 FRAPS v1 FRWA SoftLab-Nsk Forward Motion JPEG w/ alpha channel FRWD SoftLab-Nsk Forward Motion JPEG FVF1 Iterated Systems Fractal Video Frame @@ -2051,6 +2349,7 @@ class getid3_riff VLV1 VideoLogic/PURE Digital Videologic Capture VP30 On2 VP3.0 VP31 On2 VP3.1 + VP6F On2 TrueMotion VP6 VX1K Lucent VX1000S Video Codec VX2K Lucent VX2000S Video Codec VXSP Lucent VX1000SP Video Codec @@ -2098,8 +2397,8 @@ class getid3_riff } - function EitherEndian2Int(&$ThisFileInfo, $byteword, $signed=false) { - if ($ThisFileInfo['fileformat'] == 'riff') { + function EitherEndian2Int($byteword, $signed=false) { + if ($this->getid3->info['fileformat'] == 'riff') { return getid3_lib::LittleEndian2Int($byteword, $signed); } return getid3_lib::BigEndian2Int($byteword, false, $signed); diff --git a/apps/media/getID3/getid3/module.audio-video.swf.php b/3rdparty/getid3/module.audio-video.swf.php similarity index 53% rename from apps/media/getID3/getid3/module.audio-video.swf.php rename to 3rdparty/getid3/module.audio-video.swf.php index c3dbb366bc..a3d49f9506 100644 --- a/apps/media/getID3/getid3/module.audio-video.swf.php +++ b/3rdparty/getid3/module.audio-video.swf.php @@ -14,59 +14,52 @@ ///////////////////////////////////////////////////////////////// -class getid3_swf +class getid3_swf extends getid3_handler { + var $ReturnAllTagData = false; - function getid3_swf(&$fd, &$ThisFileInfo, $ReturnAllTagData=false) { -//$start_time = microtime(true); - $ThisFileInfo['fileformat'] = 'swf'; - $ThisFileInfo['video']['dataformat'] = 'swf'; + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'swf'; + $info['video']['dataformat'] = 'swf'; // http://www.openswf.org/spec/SWFfileformat.html - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $SWFfileData = fread($fd, $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data + $SWFfileData = fread($this->getid3->fp, $info['avdataend'] - $info['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data - $ThisFileInfo['swf']['header']['signature'] = substr($SWFfileData, 0, 3); - switch ($ThisFileInfo['swf']['header']['signature']) { + $info['swf']['header']['signature'] = substr($SWFfileData, 0, 3); + switch ($info['swf']['header']['signature']) { case 'FWS': - $ThisFileInfo['swf']['header']['compressed'] = false; + $info['swf']['header']['compressed'] = false; break; case 'CWS': - $ThisFileInfo['swf']['header']['compressed'] = true; + $info['swf']['header']['compressed'] = true; break; default: - $ThisFileInfo['error'][] = 'Expecting "FWS" or "CWS" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['swf']['header']['signature'].'"'; - unset($ThisFileInfo['swf']); - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'Expecting "FWS" or "CWS" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['swf']['header']['signature']).'"'; + unset($info['swf']); + unset($info['fileformat']); return false; break; } - $ThisFileInfo['swf']['header']['version'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 3, 1)); - $ThisFileInfo['swf']['header']['length'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 4, 4)); - -//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'
    '; - - if ($ThisFileInfo['swf']['header']['compressed']) { + $info['swf']['header']['version'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 3, 1)); + $info['swf']['header']['length'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 4, 4)); + if ($info['swf']['header']['compressed']) { $SWFHead = substr($SWFfileData, 0, 8); $SWFfileData = substr($SWFfileData, 8); if ($decompressed = @gzuncompress($SWFfileData)) { - $SWFfileData = $SWFHead.$decompressed; - } else { - - $ThisFileInfo['error'][] = 'Error decompressing compressed SWF data ('.strlen($SWFfileData).' bytes compressed, should be '.($ThisFileInfo['swf']['header']['length'] - 8).' bytes uncompressed)'; + $info['error'][] = 'Error decompressing compressed SWF data ('.strlen($SWFfileData).' bytes compressed, should be '.($info['swf']['header']['length'] - 8).' bytes uncompressed)'; return false; - } - } -//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'
    '; $FrameSizeBitsPerValue = (ord(substr($SWFfileData, 8, 1)) & 0xF8) >> 3; $FrameSizeDataLength = ceil((5 + (4 * $FrameSizeBitsPerValue)) / 8); @@ -75,8 +68,8 @@ class getid3_swf $FrameSizeDataString .= str_pad(decbin(ord(substr($SWFfileData, 8 + $i, 1))), 8, '0', STR_PAD_LEFT); } list($X1, $X2, $Y1, $Y2) = explode("\n", wordwrap($FrameSizeDataString, $FrameSizeBitsPerValue, "\n", 1)); - $ThisFileInfo['swf']['header']['frame_width'] = getid3_lib::Bin2Dec($X2); - $ThisFileInfo['swf']['header']['frame_height'] = getid3_lib::Bin2Dec($Y2); + $info['swf']['header']['frame_width'] = getid3_lib::Bin2Dec($X2); + $info['swf']['header']['frame_height'] = getid3_lib::Bin2Dec($Y2); // http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm // Next in the header is the frame rate, which is kind of weird. @@ -85,16 +78,16 @@ class getid3_swf // Example: 0x000C -> 0x0C -> 12 So the frame rate is 12 fps. // Byte at (8 + $FrameSizeDataLength) is always zero and ignored - $ThisFileInfo['swf']['header']['frame_rate'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 9 + $FrameSizeDataLength, 1)); - $ThisFileInfo['swf']['header']['frame_count'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 10 + $FrameSizeDataLength, 2)); + $info['swf']['header']['frame_rate'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 9 + $FrameSizeDataLength, 1)); + $info['swf']['header']['frame_count'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 10 + $FrameSizeDataLength, 2)); - $ThisFileInfo['video']['frame_rate'] = $ThisFileInfo['swf']['header']['frame_rate']; - $ThisFileInfo['video']['resolution_x'] = intval(round($ThisFileInfo['swf']['header']['frame_width'] / 20)); - $ThisFileInfo['video']['resolution_y'] = intval(round($ThisFileInfo['swf']['header']['frame_height'] / 20)); - $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + $info['video']['frame_rate'] = $info['swf']['header']['frame_rate']; + $info['video']['resolution_x'] = intval(round($info['swf']['header']['frame_width'] / 20)); + $info['video']['resolution_y'] = intval(round($info['swf']['header']['frame_height'] / 20)); + $info['video']['pixel_aspect_ratio'] = (float) 1; - if (($ThisFileInfo['swf']['header']['frame_count'] > 0) && ($ThisFileInfo['swf']['header']['frame_rate'] > 0)) { - $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['swf']['header']['frame_count'] / $ThisFileInfo['swf']['header']['frame_rate']; + if (($info['swf']['header']['frame_count'] > 0) && ($info['swf']['header']['frame_rate'] > 0)) { + $info['playtime_seconds'] = $info['swf']['header']['frame_count'] / $info['swf']['header']['frame_rate']; } //echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'
    '; @@ -126,13 +119,13 @@ class getid3_swf break 2; case 9: // Set background color - //$ThisFileInfo['swf']['tags'][] = $TagData; - $ThisFileInfo['swf']['bgcolor'] = strtoupper(str_pad(dechex(getid3_lib::BigEndian2Int($TagData['data'])), 6, '0', STR_PAD_LEFT)); + //$info['swf']['tags'][] = $TagData; + $info['swf']['bgcolor'] = strtoupper(str_pad(dechex(getid3_lib::BigEndian2Int($TagData['data'])), 6, '0', STR_PAD_LEFT)); break; default: - if ($ReturnAllTagData) { - $ThisFileInfo['swf']['tags'][] = $TagData; + if ($this->ReturnAllTagData) { + $info['swf']['tags'][] = $TagData; } break; } diff --git a/3rdparty/getid3/module.audio.aa.php b/3rdparty/getid3/module.audio.aa.php new file mode 100644 index 0000000000..39cb77c859 --- /dev/null +++ b/3rdparty/getid3/module.audio.aa.php @@ -0,0 +1,59 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.aa.php // +// module for analyzing Audible Audiobook files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_aa extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $AAheader = fread($this->getid3->fp, 8); + + $magic = "\x57\x90\x75\x36"; + if (substr($AAheader, 4, 4) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($AAheader, 4, 4)).'"'; + return false; + } + + // shortcut + $info['aa'] = array(); + $thisfile_au = &$info['aa']; + + $info['fileformat'] = 'aa'; + $info['audio']['dataformat'] = 'aa'; + $info['audio']['bitrate_mode'] = 'cbr'; // is it? + $thisfile_au['encoding'] = 'ISO-8859-1'; + + $thisfile_au['filesize'] = getid3_lib::BigEndian2Int(substr($AUheader, 0, 4)); + if ($thisfile_au['filesize'] > ($info['avdataend'] - $info['avdataoffset'])) { + $info['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['filesize'].'" bytes of data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"'; + } + + $info['audio']['bits_per_sample'] = 16; // is it? + $info['audio']['sample_rate'] = $thisfile_au['sample_rate']; + $info['audio']['channels'] = $thisfile_au['channels']; + + //$info['playtime_seconds'] = 0; + //$info['audio']['bitrate'] = 0; + + return true; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.aac.php b/3rdparty/getid3/module.audio.aac.php new file mode 100644 index 0000000000..d573e11d78 --- /dev/null +++ b/3rdparty/getid3/module.audio.aac.php @@ -0,0 +1,515 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.aac.php // +// module for analyzing AAC Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_aac extends getid3_handler +{ + function Analyze() { + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + if (fread($this->getid3->fp, 4) == 'ADIF') { + $this->getAACADIFheaderFilepointer(); + } else { + $this->getAACADTSheaderFilepointer(); + } + return true; + } + + + + function getAACADIFheaderFilepointer() { + $info = &$this->getid3->info; + $info['fileformat'] = 'aac'; + $info['audio']['dataformat'] = 'aac'; + $info['audio']['lossless'] = false; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $AACheader = fread($this->getid3->fp, 1024); + $offset = 0; + + if (substr($AACheader, 0, 4) == 'ADIF') { + + // http://faac.sourceforge.net/wiki/index.php?page=ADIF + + // http://libmpeg.org/mpeg4/doc/w2203tfs.pdf + // adif_header() { + // adif_id 32 + // copyright_id_present 1 + // if( copyright_id_present ) + // copyright_id 72 + // original_copy 1 + // home 1 + // bitstream_type 1 + // bitrate 23 + // num_program_config_elements 4 + // for (i = 0; i < num_program_config_elements + 1; i++ ) { + // if( bitstream_type == '0' ) + // adif_buffer_fullness 20 + // program_config_element() + // } + // } + + $AACheaderBitstream = getid3_lib::BigEndian2Bin($AACheader); + $bitoffset = 0; + + $info['aac']['header_type'] = 'ADIF'; + $bitoffset += 32; + $info['aac']['header']['mpeg_version'] = 4; + + $info['aac']['header']['copyright'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1'); + $bitoffset += 1; + if ($info['aac']['header']['copyright']) { + $info['aac']['header']['copyright_id'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 72)); + $bitoffset += 72; + } + $info['aac']['header']['original_copy'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1'); + $bitoffset += 1; + $info['aac']['header']['home'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1'); + $bitoffset += 1; + $info['aac']['header']['is_vbr'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1'); + $bitoffset += 1; + if ($info['aac']['header']['is_vbr']) { + $info['audio']['bitrate_mode'] = 'vbr'; + $info['aac']['header']['bitrate_max'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23)); + $bitoffset += 23; + } else { + $info['audio']['bitrate_mode'] = 'cbr'; + $info['aac']['header']['bitrate'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23)); + $bitoffset += 23; + $info['audio']['bitrate'] = $info['aac']['header']['bitrate']; + } + if ($info['audio']['bitrate'] == 0) { + $info['error'][] = 'Corrupt AAC file: bitrate_audio == zero'; + return false; + } + $info['aac']['header']['num_program_configs'] = 1 + getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + + for ($i = 0; $i < $info['aac']['header']['num_program_configs']; $i++) { + // http://www.audiocoding.com/wiki/index.php?page=program_config_element + + // buffer_fullness 20 + + // element_instance_tag 4 + // object_type 2 + // sampling_frequency_index 4 + // num_front_channel_elements 4 + // num_side_channel_elements 4 + // num_back_channel_elements 4 + // num_lfe_channel_elements 2 + // num_assoc_data_elements 3 + // num_valid_cc_elements 4 + // mono_mixdown_present 1 + // mono_mixdown_element_number 4 if mono_mixdown_present == 1 + // stereo_mixdown_present 1 + // stereo_mixdown_element_number 4 if stereo_mixdown_present == 1 + // matrix_mixdown_idx_present 1 + // matrix_mixdown_idx 2 if matrix_mixdown_idx_present == 1 + // pseudo_surround_enable 1 if matrix_mixdown_idx_present == 1 + // for (i = 0; i < num_front_channel_elements; i++) { + // front_element_is_cpe[i] 1 + // front_element_tag_select[i] 4 + // } + // for (i = 0; i < num_side_channel_elements; i++) { + // side_element_is_cpe[i] 1 + // side_element_tag_select[i] 4 + // } + // for (i = 0; i < num_back_channel_elements; i++) { + // back_element_is_cpe[i] 1 + // back_element_tag_select[i] 4 + // } + // for (i = 0; i < num_lfe_channel_elements; i++) { + // lfe_element_tag_select[i] 4 + // } + // for (i = 0; i < num_assoc_data_elements; i++) { + // assoc_data_element_tag_select[i] 4 + // } + // for (i = 0; i < num_valid_cc_elements; i++) { + // cc_element_is_ind_sw[i] 1 + // valid_cc_element_tag_select[i] 4 + // } + // byte_alignment() VAR + // comment_field_bytes 8 + // for (i = 0; i < comment_field_bytes; i++) { + // comment_field_data[i] 8 + // } + + if (!$info['aac']['header']['is_vbr']) { + $info['aac']['program_configs'][$i]['buffer_fullness'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 20)); + $bitoffset += 20; + } + $info['aac']['program_configs'][$i]['element_instance_tag'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $info['aac']['program_configs'][$i]['object_type'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + $info['aac']['program_configs'][$i]['sampling_frequency_index'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $info['aac']['program_configs'][$i]['num_front_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $info['aac']['program_configs'][$i]['num_side_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $info['aac']['program_configs'][$i]['num_back_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $info['aac']['program_configs'][$i]['num_lfe_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + $info['aac']['program_configs'][$i]['num_assoc_data_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 3)); + $bitoffset += 3; + $info['aac']['program_configs'][$i]['num_valid_cc_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $info['aac']['program_configs'][$i]['mono_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + if ($info['aac']['program_configs'][$i]['mono_mixdown_present']) { + $info['aac']['program_configs'][$i]['mono_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + $info['aac']['program_configs'][$i]['stereo_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + if ($info['aac']['program_configs'][$i]['stereo_mixdown_present']) { + $info['aac']['program_configs'][$i]['stereo_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + $info['aac']['program_configs'][$i]['matrix_mixdown_idx_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + if ($info['aac']['program_configs'][$i]['matrix_mixdown_idx_present']) { + $info['aac']['program_configs'][$i]['matrix_mixdown_idx'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + $info['aac']['program_configs'][$i]['pseudo_surround_enable'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + } + for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_front_channel_elements']; $j++) { + $info['aac']['program_configs'][$i]['front_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $info['aac']['program_configs'][$i]['front_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_side_channel_elements']; $j++) { + $info['aac']['program_configs'][$i]['side_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $info['aac']['program_configs'][$i]['side_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_back_channel_elements']; $j++) { + $info['aac']['program_configs'][$i]['back_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $info['aac']['program_configs'][$i]['back_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_lfe_channel_elements']; $j++) { + $info['aac']['program_configs'][$i]['lfe_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_assoc_data_elements']; $j++) { + $info['aac']['program_configs'][$i]['assoc_data_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_valid_cc_elements']; $j++) { + $info['aac']['program_configs'][$i]['cc_element_is_ind_sw'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $info['aac']['program_configs'][$i]['valid_cc_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + + $bitoffset = ceil($bitoffset / 8) * 8; + + $info['aac']['program_configs'][$i]['comment_field_bytes'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 8)); + $bitoffset += 8; + $info['aac']['program_configs'][$i]['comment_field'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 8 * $info['aac']['program_configs'][$i]['comment_field_bytes'])); + $bitoffset += 8 * $info['aac']['program_configs'][$i]['comment_field_bytes']; + + + $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['program_configs'][$i]['object_type'], $info['aac']['header']['mpeg_version']); + $info['aac']['program_configs'][$i]['sampling_frequency'] = self::AACsampleRateLookup($info['aac']['program_configs'][$i]['sampling_frequency_index']); + $info['audio']['sample_rate'] = $info['aac']['program_configs'][$i]['sampling_frequency']; + $info['audio']['channels'] = self::AACchannelCountCalculate($info['aac']['program_configs'][$i]); + if ($info['aac']['program_configs'][$i]['comment_field']) { + $info['aac']['comments'][] = $info['aac']['program_configs'][$i]['comment_field']; + } + } + $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']; + + $info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile']; + + + + return true; + + } else { + + unset($info['fileformat']); + unset($info['aac']); + $info['error'][] = 'AAC-ADIF synch not found at offset '.$info['avdataoffset'].' (expected "ADIF", found "'.substr($AACheader, 0, 4).'" instead)'; + return false; + + } + + } + + + function getAACADTSheaderFilepointer($MaxFramesToScan=1000000, $ReturnExtendedInfo=false) { + $info = &$this->getid3->info; + + // based loosely on code from AACfile by Jurgen Faul + // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html + + + // http://faac.sourceforge.net/wiki/index.php?page=ADTS // dead link + // http://wiki.multimedia.cx/index.php?title=ADTS + + // * ADTS Fixed Header: these don't change from frame to frame + // syncword 12 always: '111111111111' + // ID 1 0: MPEG-4, 1: MPEG-2 + // MPEG layer 2 If you send AAC in MPEG-TS, set to 0 + // protection_absent 1 0: CRC present; 1: no CRC + // profile 2 0: AAC Main; 1: AAC LC (Low Complexity); 2: AAC SSR (Scalable Sample Rate); 3: AAC LTP (Long Term Prediction) + // sampling_frequency_index 4 15 not allowed + // private_bit 1 usually 0 + // channel_configuration 3 + // original/copy 1 0: original; 1: copy + // home 1 usually 0 + // emphasis 2 only if ID == 0 (ie MPEG-4) // not present in some documentation? + + // * ADTS Variable Header: these can change from frame to frame + // copyright_identification_bit 1 + // copyright_identification_start 1 + // aac_frame_length 13 length of the frame including header (in bytes) + // adts_buffer_fullness 11 0x7FF indicates VBR + // no_raw_data_blocks_in_frame 2 + + // * ADTS Error check + // crc_check 16 only if protection_absent == 0 + + $byteoffset = $info['avdataoffset']; + $framenumber = 0; + + // Init bit pattern array + static $decbin = array(); + + // Populate $bindec + for ($i = 0; $i < 256; $i++) { + $decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT); + } + + // used to calculate bitrate below + $BitrateCache = array(); + + + while (true) { + // breaks out when end-of-file encountered, or invalid data found, + // or MaxFramesToScan frames have been scanned + + if (!getid3_lib::intValueSupported($byteoffset)) { + $info['warning'][] = 'Unable to parse AAC file beyond '.ftell($this->getid3->fp).' (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'; + return false; + } + fseek($this->getid3->fp, $byteoffset, SEEK_SET); + + // First get substring + $substring = fread($this->getid3->fp, 9); // header is 7 bytes (or 9 if CRC is present) + $substringlength = strlen($substring); + if ($substringlength != 9) { + $info['error'][] = 'Failed to read 7 bytes at offset '.(ftell($this->getid3->fp) - $substringlength).' (only read '.$substringlength.' bytes)'; + return false; + } + // this would be easier with 64-bit math, but split it up to allow for 32-bit: + $header1 = getid3_lib::BigEndian2Int(substr($substring, 0, 2)); + $header2 = getid3_lib::BigEndian2Int(substr($substring, 2, 4)); + $header3 = getid3_lib::BigEndian2Int(substr($substring, 6, 1)); + + $info['aac']['header']['raw']['syncword'] = ($header1 & 0xFFF0) >> 4; + if ($info['aac']['header']['raw']['syncword'] != 0x0FFF) { + $info['error'][] = 'Synch pattern (0x0FFF) not found at offset '.(ftell($this->getid3->fp) - $substringlength).' (found 0x0'.strtoupper(dechex($info['aac']['header']['raw']['syncword'])).' instead)'; + //if ($info['fileformat'] == 'aac') { + // return true; + //} + unset($info['aac']); + return false; + } + + // Gather info for first frame only - this takes time to do 1000 times! + if ($framenumber == 0) { + $info['aac']['header_type'] = 'ADTS'; + $info['fileformat'] = 'aac'; + $info['audio']['dataformat'] = 'aac'; + + $info['aac']['header']['raw']['mpeg_version'] = ($header1 & 0x0008) >> 3; + $info['aac']['header']['raw']['mpeg_layer'] = ($header1 & 0x0006) >> 1; + $info['aac']['header']['raw']['protection_absent'] = ($header1 & 0x0001) >> 0; + + $info['aac']['header']['raw']['profile_code'] = ($header2 & 0xC0000000) >> 30; + $info['aac']['header']['raw']['sample_rate_code'] = ($header2 & 0x3C000000) >> 26; + $info['aac']['header']['raw']['private_stream'] = ($header2 & 0x02000000) >> 25; + $info['aac']['header']['raw']['channels_code'] = ($header2 & 0x01C00000) >> 22; + $info['aac']['header']['raw']['original'] = ($header2 & 0x00200000) >> 21; + $info['aac']['header']['raw']['home'] = ($header2 & 0x00100000) >> 20; + $info['aac']['header']['raw']['copyright_stream'] = ($header2 & 0x00080000) >> 19; + $info['aac']['header']['raw']['copyright_start'] = ($header2 & 0x00040000) >> 18; + $info['aac']['header']['raw']['frame_length'] = ($header2 & 0x0003FFE0) >> 5; + + $info['aac']['header']['mpeg_version'] = ($info['aac']['header']['raw']['mpeg_version'] ? 2 : 4); + $info['aac']['header']['crc_present'] = ($info['aac']['header']['raw']['protection_absent'] ? false: true); + $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['header']['raw']['profile_code'], $info['aac']['header']['mpeg_version']); + $info['aac']['header']['sample_frequency'] = self::AACsampleRateLookup($info['aac']['header']['raw']['sample_rate_code']); + $info['aac']['header']['private'] = (bool) $info['aac']['header']['raw']['private_stream']; + $info['aac']['header']['original'] = (bool) $info['aac']['header']['raw']['original']; + $info['aac']['header']['home'] = (bool) $info['aac']['header']['raw']['home']; + $info['aac']['header']['channels'] = (($info['aac']['header']['raw']['channels_code'] == 7) ? 8 : $info['aac']['header']['raw']['channels_code']); + if ($ReturnExtendedInfo) { + $info['aac'][$framenumber]['copyright_id_bit'] = (bool) $info['aac']['header']['raw']['copyright_stream']; + $info['aac'][$framenumber]['copyright_id_start'] = (bool) $info['aac']['header']['raw']['copyright_start']; + } + + if ($info['aac']['header']['raw']['mpeg_layer'] != 0) { + $info['warning'][] = 'Layer error - expected "0", found "'.$info['aac']['header']['raw']['mpeg_layer'].'" instead'; + } + if ($info['aac']['header']['sample_frequency'] == 0) { + $info['error'][] = 'Corrupt AAC file: sample_frequency == zero'; + return false; + } + + $info['audio']['sample_rate'] = $info['aac']['header']['sample_frequency']; + $info['audio']['channels'] = $info['aac']['header']['channels']; + } + + $FrameLength = ($header2 & 0x0003FFE0) >> 5; + + if (!isset($BitrateCache[$FrameLength])) { + $BitrateCache[$FrameLength] = ($info['aac']['header']['sample_frequency'] / 1024) * $FrameLength * 8; + } + getid3_lib::safe_inc($info['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]], 1); + + $info['aac'][$framenumber]['aac_frame_length'] = $FrameLength; + + $info['aac'][$framenumber]['adts_buffer_fullness'] = (($header2 & 0x0000001F) << 6) & (($header3 & 0xFC) >> 2); + if ($info['aac'][$framenumber]['adts_buffer_fullness'] == 0x07FF) { + $info['audio']['bitrate_mode'] = 'vbr'; + } else { + $info['audio']['bitrate_mode'] = 'cbr'; + } + $info['aac'][$framenumber]['num_raw_data_blocks'] = (($header3 & 0x03) >> 0); + + if ($info['aac']['header']['crc_present']) { + //$info['aac'][$framenumber]['crc'] = getid3_lib::BigEndian2Int(substr($substring, 7, 2); + } + + if (!$ReturnExtendedInfo) { + unset($info['aac'][$framenumber]); + } + + /* + $rounded_precision = 5000; + $info['aac']['bitrate_distribution_rounded'] = array(); + foreach ($info['aac']['bitrate_distribution'] as $bitrate => $count) { + $rounded_bitrate = round($bitrate / $rounded_precision) * $rounded_precision; + getid3_lib::safe_inc($info['aac']['bitrate_distribution_rounded'][$rounded_bitrate], $count); + } + ksort($info['aac']['bitrate_distribution_rounded']); + */ + + $byteoffset += $FrameLength; + if ((++$framenumber < $MaxFramesToScan) && (($byteoffset + 10) < $info['avdataend'])) { + + // keep scanning + + } else { + + $info['aac']['frames'] = $framenumber; + $info['playtime_seconds'] = ($info['avdataend'] / $byteoffset) * (($framenumber * 1024) / $info['aac']['header']['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds + if ($info['playtime_seconds'] == 0) { + $info['error'][] = 'Corrupt AAC file: playtime_seconds == zero'; + return false; + } + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + ksort($info['aac']['bitrate_distribution']); + + $info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile']; + + return true; + + } + } + // should never get here. + } + + public static function AACsampleRateLookup($samplerateid) { + static $AACsampleRateLookup = array(); + if (empty($AACsampleRateLookup)) { + $AACsampleRateLookup[0] = 96000; + $AACsampleRateLookup[1] = 88200; + $AACsampleRateLookup[2] = 64000; + $AACsampleRateLookup[3] = 48000; + $AACsampleRateLookup[4] = 44100; + $AACsampleRateLookup[5] = 32000; + $AACsampleRateLookup[6] = 24000; + $AACsampleRateLookup[7] = 22050; + $AACsampleRateLookup[8] = 16000; + $AACsampleRateLookup[9] = 12000; + $AACsampleRateLookup[10] = 11025; + $AACsampleRateLookup[11] = 8000; + $AACsampleRateLookup[12] = 0; + $AACsampleRateLookup[13] = 0; + $AACsampleRateLookup[14] = 0; + $AACsampleRateLookup[15] = 0; + } + return (isset($AACsampleRateLookup[$samplerateid]) ? $AACsampleRateLookup[$samplerateid] : 'invalid'); + } + + public static function AACprofileLookup($profileid, $mpegversion) { + static $AACprofileLookup = array(); + if (empty($AACprofileLookup)) { + $AACprofileLookup[2][0] = 'Main profile'; + $AACprofileLookup[2][1] = 'Low Complexity profile (LC)'; + $AACprofileLookup[2][2] = 'Scalable Sample Rate profile (SSR)'; + $AACprofileLookup[2][3] = '(reserved)'; + $AACprofileLookup[4][0] = 'AAC_MAIN'; + $AACprofileLookup[4][1] = 'AAC_LC'; + $AACprofileLookup[4][2] = 'AAC_SSR'; + $AACprofileLookup[4][3] = 'AAC_LTP'; + } + return (isset($AACprofileLookup[$mpegversion][$profileid]) ? $AACprofileLookup[$mpegversion][$profileid] : 'invalid'); + } + + public static function AACchannelCountCalculate($program_configs) { + $channels = 0; + for ($i = 0; $i < $program_configs['num_front_channel_elements']; $i++) { + $channels++; + if ($program_configs['front_element_is_cpe'][$i]) { + // each front element is channel pair (CPE = Channel Pair Element) + $channels++; + } + } + for ($i = 0; $i < $program_configs['num_side_channel_elements']; $i++) { + $channels++; + if ($program_configs['side_element_is_cpe'][$i]) { + // each side element is channel pair (CPE = Channel Pair Element) + $channels++; + } + } + for ($i = 0; $i < $program_configs['num_back_channel_elements']; $i++) { + $channels++; + if ($program_configs['back_element_is_cpe'][$i]) { + // each back element is channel pair (CPE = Channel Pair Element) + $channels++; + } + } + for ($i = 0; $i < $program_configs['num_lfe_channel_elements']; $i++) { + $channels++; + } + return $channels; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.ac3.php b/3rdparty/getid3/module.audio.ac3.php new file mode 100644 index 0000000000..ffe0174689 --- /dev/null +++ b/3rdparty/getid3/module.audio.ac3.php @@ -0,0 +1,473 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.ac3.php // +// module for analyzing AC-3 (aka Dolby Digital) audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_ac3 extends getid3_handler +{ + private $AC3header = ''; + private $BSIoffset = 0; + + + public function Analyze() { + $info = &$this->getid3->info; + + ///AH + $info['ac3']['raw']['bsi'] = array(); + $thisfile_ac3 = &$info['ac3']; + $thisfile_ac3_raw = &$thisfile_ac3['raw']; + $thisfile_ac3_raw_bsi = &$thisfile_ac3_raw['bsi']; + + + // http://www.atsc.org/standards/a_52a.pdf + + $info['fileformat'] = 'ac3'; + + // An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames + // Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256 + // new audio samples per channel. A synchronization information (SI) header at the beginning + // of each frame contains information needed to acquire and maintain synchronization. A + // bit stream information (BSI) header follows SI, and contains parameters describing the coded + // audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the + // end of each frame is an error check field that includes a CRC word for error detection. An + // additional CRC word is located in the SI header, the use of which, by a decoder, is optional. + // + // syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->AC3header['syncinfo'] = fread($this->getid3->fp, 5); + $thisfile_ac3_raw['synchinfo']['synchword'] = substr($this->AC3header['syncinfo'], 0, 2); + + $magic = "\x0B\x77"; + if ($thisfile_ac3_raw['synchinfo']['synchword'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_ac3_raw['synchinfo']['synchword']).'"'; + unset($info['fileformat'], $info['ac3']); + return false; + } + + $info['audio']['dataformat'] = 'ac3'; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; + + // syncinfo() { + // syncword 16 + // crc1 16 + // fscod 2 + // frmsizecod 6 + // } /* end of syncinfo */ + + $thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 2, 2)); + $ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 4, 1)); + $thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6; + $thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F); + + $thisfile_ac3['sample_rate'] = $this->AC3sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']); + if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) { + $info['audio']['sample_rate'] = $thisfile_ac3['sample_rate']; + } + + $thisfile_ac3['frame_length'] = $this->AC3frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']); + $thisfile_ac3['bitrate'] = $this->AC3bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']); + $info['audio']['bitrate'] = $thisfile_ac3['bitrate']; + + $this->AC3header['bsi'] = getid3_lib::BigEndian2Bin(fread($this->getid3->fp, 15)); + $ac3_bsi_offset = 0; + + $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5); + if ($thisfile_ac3_raw_bsi['bsid'] > 8) { + // Decoders which can decode version 8 will thus be able to decode version numbers less than 8. + // If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used. + // Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8. + $info['error'][] = 'Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8'; + unset($thisfile_ac3); + return false; + } + + $thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3); + $thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3); + + $thisfile_ac3['service_type'] = $this->AC3serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); + $ac3_coding_mode = $this->AC3audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); + foreach($ac3_coding_mode as $key => $value) { + $thisfile_ac3[$key] = $value; + } + switch ($thisfile_ac3_raw_bsi['acmod']) { + case 0: + case 1: + $info['audio']['channelmode'] = 'mono'; + break; + case 3: + case 4: + $info['audio']['channelmode'] = 'stereo'; + break; + default: + $info['audio']['channelmode'] = 'surround'; + break; + } + $info['audio']['channels'] = $thisfile_ac3['num_channels']; + + if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) { + // If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream. + $thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2); + $thisfile_ac3['center_mix_level'] = $this->AC3centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) { + // If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream. + $thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2); + $thisfile_ac3['surround_mix_level'] = $this->AC3surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) { + // When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround. + $thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2); + $thisfile_ac3['dolby_surround_mode'] = $this->AC3dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']); + } + + $thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1); + $thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon']; + if ($thisfile_ac3_raw_bsi['lfeon']) { + //$info['audio']['channels']++; + $info['audio']['channels'] .= '.1'; + } + + $thisfile_ac3['channels_enabled'] = $this->AC3channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']); + + // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1–31. + // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. + $thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5); + $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB'; + + $thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['compre_flag']) { + $thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8); + $thisfile_ac3['heavy_compression'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr']); + } + + $thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['langcode_flag']) { + $thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8); + } + + $thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['audprodie']) { + $thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5); + $thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2); + + $thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB'; + $thisfile_ac3['room_type'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) { + // If acmod is 0, then two completely independent program channels (dual mono) + // are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case, + // a number of additional items are present in BSI or audblk to fully describe Ch2. + + // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1–31. + // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. + $thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5); + $thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB'; + + $thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['compre_flag2']) { + $thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8); + $thisfile_ac3['heavy_compression2'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr2']); + } + + $thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['langcode_flag2']) { + $thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8); + } + + $thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['audprodie2']) { + $thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5); + $thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2); + + $thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB'; + $thisfile_ac3['room_type2'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']); + } + + } + + $thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1); + + $thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1); + + $thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['timecode1_flag']) { + $thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14); + } + + $thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['timecode2_flag']) { + $thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14); + } + + $thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['addbsi_flag']) { + $thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6); + + $this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin(fread($this->getid3->fp, $thisfile_ac3_raw_bsi['addbsi_length'])); + + $thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8); + $this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8; + } + + return true; + } + + private function readHeaderBSI($length) { + $data = substr($this->AC3header['bsi'], $this->BSIoffset, $length); + $this->BSIoffset += $length; + + return bindec($data); + } + + public static function AC3sampleRateCodeLookup($fscod) { + static $AC3sampleRateCodeLookup = array( + 0 => 48000, + 1 => 44100, + 2 => 32000, + 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. + ); + return (isset($AC3sampleRateCodeLookup[$fscod]) ? $AC3sampleRateCodeLookup[$fscod] : false); + } + + public static function AC3serviceTypeLookup($bsmod, $acmod) { + static $AC3serviceTypeLookup = array(); + if (empty($AC3serviceTypeLookup)) { + for ($i = 0; $i <= 7; $i++) { + $AC3serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; + $AC3serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)'; + $AC3serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)'; + $AC3serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)'; + $AC3serviceTypeLookup[4][$i] = 'associated service: dialogue (D)'; + $AC3serviceTypeLookup[5][$i] = 'associated service: commentary (C)'; + $AC3serviceTypeLookup[6][$i] = 'associated service: emergency (E)'; + } + + $AC3serviceTypeLookup[7][1] = 'associated service: voice over (VO)'; + for ($i = 2; $i <= 7; $i++) { + $AC3serviceTypeLookup[7][$i] = 'main audio service: karaoke'; + } + } + return (isset($AC3serviceTypeLookup[$bsmod][$acmod]) ? $AC3serviceTypeLookup[$bsmod][$acmod] : false); + } + + public static function AC3audioCodingModeLookup($acmod) { + static $AC3audioCodingModeLookup = array(); + if (empty($AC3audioCodingModeLookup)) { + // array(channel configuration, # channels (not incl LFE), channel order) + $AC3audioCodingModeLookup = array ( + 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), + 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), + 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'), + 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'), + 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'), + 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'), + 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'), + 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR') + ); + } + return (isset($AC3audioCodingModeLookup[$acmod]) ? $AC3audioCodingModeLookup[$acmod] : false); + } + + public static function AC3centerMixLevelLookup($cmixlev) { + static $AC3centerMixLevelLookup; + if (empty($AC3centerMixLevelLookup)) { + $AC3centerMixLevelLookup = array( + 0 => pow(2, -3.0 / 6), // 0.707 (–3.0 dB) + 1 => pow(2, -4.5 / 6), // 0.595 (–4.5 dB) + 2 => pow(2, -6.0 / 6), // 0.500 (–6.0 dB) + 3 => 'reserved' + ); + } + return (isset($AC3centerMixLevelLookup[$cmixlev]) ? $AC3centerMixLevelLookup[$cmixlev] : false); + } + + public static function AC3surroundMixLevelLookup($surmixlev) { + static $AC3surroundMixLevelLookup; + if (empty($AC3surroundMixLevelLookup)) { + $AC3surroundMixLevelLookup = array( + 0 => pow(2, -3.0 / 6), + 1 => pow(2, -6.0 / 6), + 2 => 0, + 3 => 'reserved' + ); + } + return (isset($AC3surroundMixLevelLookup[$surmixlev]) ? $AC3surroundMixLevelLookup[$surmixlev] : false); + } + + public static function AC3dolbySurroundModeLookup($dsurmod) { + static $AC3dolbySurroundModeLookup = array( + 0 => 'not indicated', + 1 => 'Not Dolby Surround encoded', + 2 => 'Dolby Surround encoded', + 3 => 'reserved' + ); + return (isset($AC3dolbySurroundModeLookup[$dsurmod]) ? $AC3dolbySurroundModeLookup[$dsurmod] : false); + } + + public static function AC3channelsEnabledLookup($acmod, $lfeon) { + $AC3channelsEnabledLookup = array( + 'ch1'=>(bool) ($acmod == 0), + 'ch2'=>(bool) ($acmod == 0), + 'left'=>(bool) ($acmod > 1), + 'right'=>(bool) ($acmod > 1), + 'center'=>(bool) ($acmod & 0x01), + 'surround_mono'=>false, + 'surround_left'=>false, + 'surround_right'=>false, + 'lfe'=>$lfeon); + switch ($acmod) { + case 4: + case 5: + $AC3channelsEnabledLookup['surround_mono'] = true; + break; + case 6: + case 7: + $AC3channelsEnabledLookup['surround_left'] = true; + $AC3channelsEnabledLookup['surround_right'] = true; + break; + } + return $AC3channelsEnabledLookup; + } + + public static function AC3heavyCompression($compre) { + // The first four bits indicate gain changes in 6.02dB increments which can be + // implemented with an arithmetic shift operation. The following four bits + // indicate linear gain changes, and require a 5-bit multiply. + // We will represent the two 4-bit fields of compr as follows: + // X0 X1 X2 X3 . Y4 Y5 Y6 Y7 + // The meaning of the X values is most simply described by considering X to represent a 4-bit + // signed integer with values from –8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The + // following table shows this in detail. + + // Meaning of 4 msb of compr + // 7 +48.16 dB + // 6 +42.14 dB + // 5 +36.12 dB + // 4 +30.10 dB + // 3 +24.08 dB + // 2 +18.06 dB + // 1 +12.04 dB + // 0 +6.02 dB + // -1 0 dB + // -2 –6.02 dB + // -3 –12.04 dB + // -4 –18.06 dB + // -5 –24.08 dB + // -6 –30.10 dB + // -7 –36.12 dB + // -8 –42.14 dB + + $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT); + if ($fourbit{0} == '1') { + $log_gain = -8 + bindec(substr($fourbit, 1)); + } else { + $log_gain = bindec(substr($fourbit, 1)); + } + $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2); + + // The value of Y is a linear representation of a gain change of up to –6 dB. Y is considered to + // be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can + // represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain + // changes from –0.28 dB to –6.02 dB. + + $lin_gain = (16 + ($compre & 0x0F)) / 32; + + // The combination of X and Y values allows compr to indicate gain changes from + // 48.16 – 0.28 = +47.89 dB, to + // –42.14 – 6.02 = –48.16 dB. + + return $log_gain - $lin_gain; + } + + public static function AC3roomTypeLookup($roomtyp) { + static $AC3roomTypeLookup = array( + 0 => 'not indicated', + 1 => 'large room, X curve monitor', + 2 => 'small room, flat monitor', + 3 => 'reserved' + ); + return (isset($AC3roomTypeLookup[$roomtyp]) ? $AC3roomTypeLookup[$roomtyp] : false); + } + + public static function AC3frameSizeLookup($frmsizecod, $fscod) { + $padding = (bool) ($frmsizecod % 2); + $framesizeid = floor($frmsizecod / 2); + + static $AC3frameSizeLookup = array(); + if (empty($AC3frameSizeLookup)) { + $AC3frameSizeLookup = array ( + 0 => array(128, 138, 192), + 1 => array(40, 160, 174, 240), + 2 => array(48, 192, 208, 288), + 3 => array(56, 224, 242, 336), + 4 => array(64, 256, 278, 384), + 5 => array(80, 320, 348, 480), + 6 => array(96, 384, 416, 576), + 7 => array(112, 448, 486, 672), + 8 => array(128, 512, 556, 768), + 9 => array(160, 640, 696, 960), + 10 => array(192, 768, 834, 1152), + 11 => array(224, 896, 974, 1344), + 12 => array(256, 1024, 1114, 1536), + 13 => array(320, 1280, 1392, 1920), + 14 => array(384, 1536, 1670, 2304), + 15 => array(448, 1792, 1950, 2688), + 16 => array(512, 2048, 2228, 3072), + 17 => array(576, 2304, 2506, 3456), + 18 => array(640, 2560, 2786, 3840) + ); + } + if (($fscod == 1) && $padding) { + // frame lengths are padded by 1 word (16 bits) at 44100 + $AC3frameSizeLookup[$frmsizecod] += 2; + } + return (isset($AC3frameSizeLookup[$framesizeid][$fscod]) ? $AC3frameSizeLookup[$framesizeid][$fscod] : false); + } + + public static function AC3bitrateLookup($frmsizecod) { + $framesizeid = floor($frmsizecod / 2); + + static $AC3bitrateLookup = array( + 0 => 32000, + 1 => 40000, + 2 => 48000, + 3 => 56000, + 4 => 64000, + 5 => 80000, + 6 => 96000, + 7 => 112000, + 8 => 128000, + 9 => 160000, + 10 => 192000, + 11 => 224000, + 12 => 256000, + 13 => 320000, + 14 => 384000, + 15 => 448000, + 16 => 512000, + 17 => 576000, + 18 => 640000 + ); + return (isset($AC3bitrateLookup[$framesizeid]) ? $AC3bitrateLookup[$framesizeid] : false); + } + + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.au.php b/3rdparty/getid3/module.audio.au.php similarity index 72% rename from apps/media/getID3/getid3/module.audio.au.php rename to 3rdparty/getid3/module.audio.au.php index afbc75d671..a1094dbcda 100644 --- a/apps/media/getID3/getid3/module.audio.au.php +++ b/3rdparty/getid3/module.audio.au.php @@ -14,31 +14,33 @@ ///////////////////////////////////////////////////////////////// -class getid3_au +class getid3_au extends getid3_handler { - function getid3_au(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $AUheader = fread($fd, 8); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $AUheader = fread($this->getid3->fp, 8); - if (substr($AUheader, 0, 4) != '.snd') { - $ThisFileInfo['error'][] = 'Expecting ".snd" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($AUheader, 0, 4).'"'; + $magic = '.snd'; + if (substr($AUheader, 0, 4) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" (".snd") at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($AUheader, 0, 4)).'"'; return false; } // shortcut - $ThisFileInfo['au'] = array(); - $thisfile_au = &$ThisFileInfo['au']; + $info['au'] = array(); + $thisfile_au = &$info['au']; - $ThisFileInfo['fileformat'] = 'au'; - $ThisFileInfo['audio']['dataformat'] = 'au'; - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $info['fileformat'] = 'au'; + $info['audio']['dataformat'] = 'au'; + $info['audio']['bitrate_mode'] = 'cbr'; $thisfile_au['encoding'] = 'ISO-8859-1'; $thisfile_au['header_length'] = getid3_lib::BigEndian2Int(substr($AUheader, 4, 4)); - $AUheader .= fread($fd, $thisfile_au['header_length'] - 8); - $ThisFileInfo['avdataoffset'] += $thisfile_au['header_length']; + $AUheader .= fread($this->getid3->fp, $thisfile_au['header_length'] - 8); + $info['avdataoffset'] += $thisfile_au['header_length']; $thisfile_au['data_size'] = getid3_lib::BigEndian2Int(substr($AUheader, 8, 4)); $thisfile_au['data_format_id'] = getid3_lib::BigEndian2Int(substr($AUheader, 12, 4)); @@ -49,20 +51,20 @@ class getid3_au $thisfile_au['data_format'] = $this->AUdataFormatNameLookup($thisfile_au['data_format_id']); $thisfile_au['used_bits_per_sample'] = $this->AUdataFormatUsedBitsPerSampleLookup($thisfile_au['data_format_id']); if ($thisfile_au['bits_per_sample'] = $this->AUdataFormatBitsPerSampleLookup($thisfile_au['data_format_id'])) { - $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_au['bits_per_sample']; + $info['audio']['bits_per_sample'] = $thisfile_au['bits_per_sample']; } else { unset($thisfile_au['bits_per_sample']); } - $ThisFileInfo['audio']['sample_rate'] = $thisfile_au['sample_rate']; - $ThisFileInfo['audio']['channels'] = $thisfile_au['channels']; + $info['audio']['sample_rate'] = $thisfile_au['sample_rate']; + $info['audio']['channels'] = $thisfile_au['channels']; - if (($ThisFileInfo['avdataoffset'] + $thisfile_au['data_size']) > $ThisFileInfo['avdataend']) { - $ThisFileInfo['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['data_size'].'" bytes of audio data, only found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' bytes"'; + if (($info['avdataoffset'] + $thisfile_au['data_size']) > $info['avdataend']) { + $info['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['data_size'].'" bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"'; } - $ThisFileInfo['playtime_seconds'] = $thisfile_au['data_size'] / ($thisfile_au['sample_rate'] * $thisfile_au['channels'] * ($thisfile_au['used_bits_per_sample'] / 8)); - $ThisFileInfo['audio']['bitrate'] = ($thisfile_au['data_size'] * 8) / $ThisFileInfo['playtime_seconds']; + $info['playtime_seconds'] = $thisfile_au['data_size'] / ($thisfile_au['sample_rate'] * $thisfile_au['channels'] * ($thisfile_au['used_bits_per_sample'] / 8)); + $info['audio']['bitrate'] = ($thisfile_au['data_size'] * 8) / $info['playtime_seconds']; return true; } diff --git a/3rdparty/getid3/module.audio.avr.php b/3rdparty/getid3/module.audio.avr.php new file mode 100644 index 0000000000..9c6d665078 --- /dev/null +++ b/3rdparty/getid3/module.audio.avr.php @@ -0,0 +1,127 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.avr.php // +// module for analyzing AVR Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_avr extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + // http://cui.unige.ch/OSG/info/AudioFormats/ap11.html + // http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html + // offset type length name comments + // --------------------------------------------------------------------- + // 0 char 4 ID format ID == "2BIT" + // 4 char 8 name sample name (unused space filled with 0) + // 12 short 1 mono/stereo 0=mono, -1 (0xFFFF)=stereo + // With stereo, samples are alternated, + // the first voice is the left : + // (LRLRLRLRLRLRLRLRLR...) + // 14 short 1 resolution 8, 12 or 16 (bits) + // 16 short 1 signed or not 0=unsigned, -1 (0xFFFF)=signed + // 18 short 1 loop or not 0=no loop, -1 (0xFFFF)=loop on + // 20 short 1 MIDI note 0xFFnn, where 0 <= nn <= 127 + // 0xFFFF means "no MIDI note defined" + // 22 byte 1 Replay speed Frequence in the Replay software + // 0=5.485 Khz, 1=8.084 Khz, 2=10.971 Khz, + // 3=16.168 Khz, 4=21.942 Khz, 5=32.336 Khz + // 6=43.885 Khz, 7=47.261 Khz + // -1 (0xFF)=no defined Frequence + // 23 byte 3 sample rate in Hertz + // 26 long 1 size in bytes (2 * bytes in stereo) + // 30 long 1 loop begin 0 for no loop + // 34 long 1 loop size equal to 'size' for no loop + // 38 short 2 Reserved, MIDI keyboard split */ + // 40 short 2 Reserved, sample compression */ + // 42 short 2 Reserved */ + // 44 char 20; Additional filename space, used if (name[7] != 0) + // 64 byte 64 user data + // 128 bytes ? sample data (12 bits samples are coded on 16 bits: + // 0000 xxxx xxxx xxxx) + // --------------------------------------------------------------------- + + // Note that all values are in motorola (big-endian) format, and that long is + // assumed to be 4 bytes, and short 2 bytes. + // When reading the samples, you should handle both signed and unsigned data, + // and be prepared to convert 16->8 bit, or mono->stereo if needed. To convert + // 8-bit data between signed/unsigned just add 127 to the sample values. + // Simularly for 16-bit data you should add 32769 + + $info['fileformat'] = 'avr'; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $AVRheader = fread($this->getid3->fp, 128); + + $info['avr']['raw']['magic'] = substr($AVRheader, 0, 4); + $magic = '2BIT'; + if ($info['avr']['raw']['magic'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['avr']['raw']['magic']).'"'; + unset($info['fileformat']); + unset($info['avr']); + return false; + } + $info['avdataoffset'] += 128; + + $info['avr']['sample_name'] = rtrim(substr($AVRheader, 4, 8)); + $info['avr']['raw']['mono'] = getid3_lib::BigEndian2Int(substr($AVRheader, 12, 2)); + $info['avr']['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($AVRheader, 14, 2)); + $info['avr']['raw']['signed'] = getid3_lib::BigEndian2Int(substr($AVRheader, 16, 2)); + $info['avr']['raw']['loop'] = getid3_lib::BigEndian2Int(substr($AVRheader, 18, 2)); + $info['avr']['raw']['midi'] = getid3_lib::BigEndian2Int(substr($AVRheader, 20, 2)); + $info['avr']['raw']['replay_freq'] = getid3_lib::BigEndian2Int(substr($AVRheader, 22, 1)); + $info['avr']['sample_rate'] = getid3_lib::BigEndian2Int(substr($AVRheader, 23, 3)); + $info['avr']['sample_length'] = getid3_lib::BigEndian2Int(substr($AVRheader, 26, 4)); + $info['avr']['loop_start'] = getid3_lib::BigEndian2Int(substr($AVRheader, 30, 4)); + $info['avr']['loop_end'] = getid3_lib::BigEndian2Int(substr($AVRheader, 34, 4)); + $info['avr']['midi_split'] = getid3_lib::BigEndian2Int(substr($AVRheader, 38, 2)); + $info['avr']['sample_compression'] = getid3_lib::BigEndian2Int(substr($AVRheader, 40, 2)); + $info['avr']['reserved'] = getid3_lib::BigEndian2Int(substr($AVRheader, 42, 2)); + $info['avr']['sample_name_extra'] = rtrim(substr($AVRheader, 44, 20)); + $info['avr']['comment'] = rtrim(substr($AVRheader, 64, 64)); + + $info['avr']['flags']['stereo'] = (($info['avr']['raw']['mono'] == 0) ? false : true); + $info['avr']['flags']['signed'] = (($info['avr']['raw']['signed'] == 0) ? false : true); + $info['avr']['flags']['loop'] = (($info['avr']['raw']['loop'] == 0) ? false : true); + + $info['avr']['midi_notes'] = array(); + if (($info['avr']['raw']['midi'] & 0xFF00) != 0xFF00) { + $info['avr']['midi_notes'][] = ($info['avr']['raw']['midi'] & 0xFF00) >> 8; + } + if (($info['avr']['raw']['midi'] & 0x00FF) != 0x00FF) { + $info['avr']['midi_notes'][] = ($info['avr']['raw']['midi'] & 0x00FF); + } + + if (($info['avdataend'] - $info['avdataoffset']) != ($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 1 : 2))) { + $info['warning'][] = 'Probable truncated file: expecting '.($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 1 : 2)).' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']); + } + + $info['audio']['dataformat'] = 'avr'; + $info['audio']['lossless'] = true; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['bits_per_sample'] = $info['avr']['bits_per_sample']; + $info['audio']['sample_rate'] = $info['avr']['sample_rate']; + $info['audio']['channels'] = ($info['avr']['flags']['stereo'] ? 2 : 1); + $info['playtime_seconds'] = ($info['avr']['sample_length'] / $info['audio']['channels']) / $info['avr']['sample_rate']; + $info['audio']['bitrate'] = ($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 8 : 16)) / $info['playtime_seconds']; + + + return true; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.bonk.php b/3rdparty/getid3/module.audio.bonk.php similarity index 53% rename from apps/media/getID3/getid3/module.audio.bonk.php rename to 3rdparty/getid3/module.audio.bonk.php index fef9782cfc..9f5187e3c0 100644 --- a/apps/media/getID3/getid3/module.audio.bonk.php +++ b/3rdparty/getid3/module.audio.bonk.php @@ -14,67 +14,68 @@ ///////////////////////////////////////////////////////////////// -class getid3_bonk +class getid3_bonk extends getid3_handler { - function getid3_bonk(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; // shortcut - $ThisFileInfo['bonk'] = array(); - $thisfile_bonk = &$ThisFileInfo['bonk']; + $info['bonk'] = array(); + $thisfile_bonk = &$info['bonk']; - $thisfile_bonk['dataoffset'] = $ThisFileInfo['avdataoffset']; - $thisfile_bonk['dataend'] = $ThisFileInfo['avdataend']; + $thisfile_bonk['dataoffset'] = $info['avdataoffset']; + $thisfile_bonk['dataend'] = $info['avdataend']; - if ($thisfile_bonk['dataend'] >= pow(2, 31)) { + if (!getid3_lib::intValueSupported($thisfile_bonk['dataend'])) { - $ThisFileInfo['warning'][] = 'Unable to parse BONK file from end (v0.6+ preferred method) because PHP filesystem functions only support up to 2GB'; + $info['warning'][] = 'Unable to parse BONK file from end (v0.6+ preferred method) because PHP filesystem functions only support up to '.round(PHP_INT_MAX / 1073741824).'GB'; } else { // scan-from-end method, for v0.6 and higher - fseek($fd, $thisfile_bonk['dataend'] - 8, SEEK_SET); - $PossibleBonkTag = fread($fd, 8); + fseek($this->getid3->fp, $thisfile_bonk['dataend'] - 8, SEEK_SET); + $PossibleBonkTag = fread($this->getid3->fp, 8); while ($this->BonkIsValidTagName(substr($PossibleBonkTag, 4, 4), true)) { $BonkTagSize = getid3_lib::LittleEndian2Int(substr($PossibleBonkTag, 0, 4)); - fseek($fd, 0 - $BonkTagSize, SEEK_CUR); - $BonkTagOffset = ftell($fd); - $TagHeaderTest = fread($fd, 5); + fseek($this->getid3->fp, 0 - $BonkTagSize, SEEK_CUR); + $BonkTagOffset = ftell($this->getid3->fp); + $TagHeaderTest = fread($this->getid3->fp, 5); if (($TagHeaderTest{0} != "\x00") || (substr($PossibleBonkTag, 4, 4) != strtolower(substr($PossibleBonkTag, 4, 4)))) { - $ThisFileInfo['error'][] = 'Expecting "Ø'.strtoupper(substr($PossibleBonkTag, 4, 4)).'" at offset '.$BonkTagOffset.', found "'.$TagHeaderTest.'"'; + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes("\x00".strtoupper(substr($PossibleBonkTag, 4, 4))).'" at offset '.$BonkTagOffset.', found "'.getid3_lib::PrintHexBytes($TagHeaderTest).'"'; return false; } $BonkTagName = substr($TagHeaderTest, 1, 4); $thisfile_bonk[$BonkTagName]['size'] = $BonkTagSize; $thisfile_bonk[$BonkTagName]['offset'] = $BonkTagOffset; - $this->HandleBonkTags($fd, $BonkTagName, $ThisFileInfo); + $this->HandleBonkTags($BonkTagName); $NextTagEndOffset = $BonkTagOffset - 8; if ($NextTagEndOffset < $thisfile_bonk['dataoffset']) { - if (empty($ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + if (empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = 'Extended BONK v0.9+'; } return true; } - fseek($fd, $NextTagEndOffset, SEEK_SET); - $PossibleBonkTag = fread($fd, 8); + fseek($this->getid3->fp, $NextTagEndOffset, SEEK_SET); + $PossibleBonkTag = fread($this->getid3->fp, 8); } } // seek-from-beginning method for v0.4 and v0.5 if (empty($thisfile_bonk['BONK'])) { - fseek($fd, $thisfile_bonk['dataoffset'], SEEK_SET); + fseek($this->getid3->fp, $thisfile_bonk['dataoffset'], SEEK_SET); do { - $TagHeaderTest = fread($fd, 5); + $TagHeaderTest = fread($this->getid3->fp, 5); switch ($TagHeaderTest) { case "\x00".'BONK': - if (empty($ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = 'BONK v0.4'; + if (empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = 'BONK v0.4'; } break; case "\x00".'INFO': - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.5'; + $info['audio']['encoder'] = 'Extended BONK v0.5'; break; default: @@ -83,43 +84,43 @@ class getid3_bonk $BonkTagName = substr($TagHeaderTest, 1, 4); $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset']; $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset']; - $this->HandleBonkTags($fd, $BonkTagName, $ThisFileInfo); + $this->HandleBonkTags($BonkTagName); } while (true); } // parse META block for v0.6 - v0.8 if (empty($thisfile_bonk['INFO']) && isset($thisfile_bonk['META']['tags']['info'])) { - fseek($fd, $thisfile_bonk['META']['tags']['info'], SEEK_SET); - $TagHeaderTest = fread($fd, 5); + fseek($this->getid3->fp, $thisfile_bonk['META']['tags']['info'], SEEK_SET); + $TagHeaderTest = fread($this->getid3->fp, 5); if ($TagHeaderTest == "\x00".'INFO') { - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.6 - v0.8'; + $info['audio']['encoder'] = 'Extended BONK v0.6 - v0.8'; $BonkTagName = substr($TagHeaderTest, 1, 4); $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset']; $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset']; - $this->HandleBonkTags($fd, $BonkTagName, $ThisFileInfo); + $this->HandleBonkTags($BonkTagName); } } - if (empty($ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + if (empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = 'Extended BONK v0.9+'; } if (empty($thisfile_bonk['BONK'])) { - unset($ThisFileInfo['bonk']); + unset($info['bonk']); } return true; } - function HandleBonkTags(&$fd, &$BonkTagName, &$ThisFileInfo) { - + function HandleBonkTags($BonkTagName) { + $info = &$this->getid3->info; switch ($BonkTagName) { case 'BONK': // shortcut - $thisfile_bonk_BONK = &$ThisFileInfo['bonk']['BONK']; + $thisfile_bonk_BONK = &$info['bonk']['BONK']; - $BonkData = "\x00".'BONK'.fread($fd, 17); + $BonkData = "\x00".'BONK'.fread($this->getid3->fp, 17); $thisfile_bonk_BONK['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); $thisfile_bonk_BONK['number_samples'] = getid3_lib::LittleEndian2Int(substr($BonkData, 6, 4)); $thisfile_bonk_BONK['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BonkData, 10, 4)); @@ -131,40 +132,40 @@ class getid3_bonk $thisfile_bonk_BONK['downsampling_ratio'] = getid3_lib::LittleEndian2Int(substr($BonkData, 19, 1)); $thisfile_bonk_BONK['samples_per_packet'] = getid3_lib::LittleEndian2Int(substr($BonkData, 20, 2)); - $ThisFileInfo['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17; - $ThisFileInfo['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size']; + $info['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17; + $info['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size']; - $ThisFileInfo['fileformat'] = 'bonk'; - $ThisFileInfo['audio']['dataformat'] = 'bonk'; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; // assumed - $ThisFileInfo['audio']['channels'] = $thisfile_bonk_BONK['channels']; - $ThisFileInfo['audio']['sample_rate'] = $thisfile_bonk_BONK['sample_rate']; - $ThisFileInfo['audio']['channelmode'] = ($thisfile_bonk_BONK['joint_stereo'] ? 'joint stereo' : 'stereo'); - $ThisFileInfo['audio']['lossless'] = $thisfile_bonk_BONK['lossless']; - $ThisFileInfo['audio']['codec'] = 'bonk'; + $info['fileformat'] = 'bonk'; + $info['audio']['dataformat'] = 'bonk'; + $info['audio']['bitrate_mode'] = 'vbr'; // assumed + $info['audio']['channels'] = $thisfile_bonk_BONK['channels']; + $info['audio']['sample_rate'] = $thisfile_bonk_BONK['sample_rate']; + $info['audio']['channelmode'] = ($thisfile_bonk_BONK['joint_stereo'] ? 'joint stereo' : 'stereo'); + $info['audio']['lossless'] = $thisfile_bonk_BONK['lossless']; + $info['audio']['codec'] = 'bonk'; - $ThisFileInfo['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']); - if ($ThisFileInfo['playtime_seconds'] > 0) { - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['bonk']['dataend'] - $ThisFileInfo['bonk']['dataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $info['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']); + if ($info['playtime_seconds'] > 0) { + $info['audio']['bitrate'] = (($info['bonk']['dataend'] - $info['bonk']['dataoffset']) * 8) / $info['playtime_seconds']; } break; case 'INFO': // shortcut - $thisfile_bonk_INFO = &$ThisFileInfo['bonk']['INFO']; + $thisfile_bonk_INFO = &$info['bonk']['INFO']; - $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($fd, 1)); + $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1)); $thisfile_bonk_INFO['entries_count'] = 0; - $NextInfoDataPair = fread($fd, 5); + $NextInfoDataPair = fread($this->getid3->fp, 5); if (!$this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { - while (!feof($fd)) { + while (!feof($this->getid3->fp)) { //$CurrentSeekInfo['offset'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 0, 4)); //$CurrentSeekInfo['nextbit'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 4, 1)); //$thisfile_bonk_INFO[] = $CurrentSeekInfo; - $NextInfoDataPair = fread($fd, 5); + $NextInfoDataPair = fread($this->getid3->fp, 5); if ($this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { - fseek($fd, -5, SEEK_CUR); + fseek($this->getid3->fp, -5, SEEK_CUR); break; } $thisfile_bonk_INFO['entries_count']++; @@ -173,37 +174,45 @@ class getid3_bonk break; case 'META': - $BonkData = "\x00".'META'.fread($fd, $ThisFileInfo['bonk']['META']['size'] - 5); - $ThisFileInfo['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); + $BonkData = "\x00".'META'.fread($this->getid3->fp, $info['bonk']['META']['size'] - 5); + $info['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); $MetaTagEntries = floor(((strlen($BonkData) - 8) - 6) / 8); // BonkData - xxxxmeta - ØMETA $offset = 6; for ($i = 0; $i < $MetaTagEntries; $i++) { - $MetaEntryTagName = substr($BonkData, $offset, 4); + $MetaEntryTagName = substr($BonkData, $offset, 4); $offset += 4; $MetaEntryTagOffset = getid3_lib::LittleEndian2Int(substr($BonkData, $offset, 4)); $offset += 4; - $ThisFileInfo['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset; + $info['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset; } break; case ' ID3': - $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + $info['audio']['encoder'] = 'Extended BONK v0.9+'; // ID3v2 checking is optional if (class_exists('getid3_id3v2')) { - $ThisFileInfo['bonk'][' ID3']['valid'] = new getid3_id3v2($fd, $ThisFileInfo, $ThisFileInfo['bonk'][' ID3']['offset'] + 2); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_id3v2 = new getid3_id3v2($getid3_temp); + $getid3_id3v2->StartingOffset = $info['bonk'][' ID3']['offset'] + 2; + $info['bonk'][' ID3']['valid'] = $getid3_id3v2->Analyze(); + if ($info['bonk'][' ID3']['valid']) { + $info['id3v2'] = $getid3_temp->info['id3v2']; + } + unset($getid3_temp, $getid3_id3v2); } break; default: - $ThisFileInfo['warning'][] = 'Unexpected Bonk tag "'.$BonkTagName.'" at offset '.$ThisFileInfo['bonk'][$BonkTagName]['offset']; + $info['warning'][] = 'Unexpected Bonk tag "'.$BonkTagName.'" at offset '.$info['bonk'][$BonkTagName]['offset']; break; } } - function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) { + static function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) { static $BonkIsValidTagName = array('BONK', 'INFO', ' ID3', 'META'); foreach ($BonkIsValidTagName as $validtagname) { if ($validtagname == $PossibleBonkTag) { diff --git a/apps/media/getID3/getid3/module.audio.dss.php b/3rdparty/getid3/module.audio.dss.php similarity index 57% rename from apps/media/getID3/getid3/module.audio.dss.php rename to 3rdparty/getid3/module.audio.dss.php index b0887395c4..b7b4367629 100644 --- a/apps/media/getID3/getid3/module.audio.dss.php +++ b/3rdparty/getid3/module.audio.dss.php @@ -7,50 +7,53 @@ // See readme.txt for more details // ///////////////////////////////////////////////////////////////// // // -// module.audio.au.php // +// module.audio.dss.php // // module for analyzing Digital Speech Standard (DSS) files // // dependencies: NONE // // /// ///////////////////////////////////////////////////////////////// -class getid3_dss +class getid3_dss extends getid3_handler { - function getid3_dss(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $DSSheader = fread($fd, 1256); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $DSSheader = fread($this->getid3->fp, 1256); - if (substr($DSSheader, 0, 4) != "\x02".'dss') { - $ThisFileInfo['error'][] = 'Expecting "[x02]dss" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($DSSheader, 0, 4).'"'; + if (!preg_match('#^(\x02|\x03)dss#', $DSSheader)) { + $info['error'][] = 'Expecting "[02-03] 64 73 73" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"'; return false; } // some structure information taken from http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm // shortcut - $ThisFileInfo['dss'] = array(); - $thisfile_dss = &$ThisFileInfo['dss']; + $info['dss'] = array(); + $thisfile_dss = &$info['dss']; - $ThisFileInfo['fileformat'] = 'dss'; - $ThisFileInfo['audio']['dataformat'] = 'dss'; - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $info['fileformat'] = 'dss'; + $info['audio']['dataformat'] = 'dss'; + $info['audio']['bitrate_mode'] = 'cbr'; //$thisfile_dss['encoding'] = 'ISO-8859-1'; + $thisfile_dss['version'] = ord(substr($DSSheader, 0, 1)); $thisfile_dss['date_create'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 38, 12)); $thisfile_dss['date_complete'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 50, 12)); - $thisfile_dss['length'] = intval(substr($DSSheader, 62, 6)); + //$thisfile_dss['length'] = intval(substr($DSSheader, 62, 6)); // I thought time was in seconds, it's actually HHMMSS + $thisfile_dss['length'] = intval((substr($DSSheader, 62, 2) * 3600) + (substr($DSSheader, 64, 2) * 60) + substr($DSSheader, 66, 2)); $thisfile_dss['priority'] = ord(substr($DSSheader, 793, 1)); $thisfile_dss['comments'] = trim(substr($DSSheader, 798, 100)); - //$ThisFileInfo['audio']['bits_per_sample'] = ?; - //$ThisFileInfo['audio']['sample_rate'] = ?; - $ThisFileInfo['audio']['channels'] = 1; + //$info['audio']['bits_per_sample'] = ?; + //$info['audio']['sample_rate'] = ?; + $info['audio']['channels'] = 1; - $ThisFileInfo['playtime_seconds'] = $thisfile_dss['length']; - $ThisFileInfo['audio']['bitrate'] = ($ThisFileInfo['filesize'] * 8) / $ThisFileInfo['playtime_seconds']; + $info['playtime_seconds'] = $thisfile_dss['length']; + $info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds']; return true; } diff --git a/3rdparty/getid3/module.audio.dts.php b/3rdparty/getid3/module.audio.dts.php new file mode 100644 index 0000000000..8102ba8bf5 --- /dev/null +++ b/3rdparty/getid3/module.audio.dts.php @@ -0,0 +1,246 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.dts.php // +// module for analyzing DTS Audio files // +// dependencies: NONE // +// // +///////////////////////////////////////////////////////////////// + + +class getid3_dts extends getid3_handler +{ + + public function Analyze() { + $info = &$this->getid3->info; + + // Specs taken from "DTS Coherent Acoustics;Core and Extensions, ETSI TS 102 114 V1.2.1 (2002-12)" + // (http://pda.etsi.org/pda/queryform.asp) + // With thanks to Gambit http://mac.sourceforge.net/atl/ + + $info['fileformat'] = 'dts'; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $DTSheader = fread($this->getid3->fp, 16); + $info['dts']['raw']['magic'] = substr($DTSheader, 0, 4); + + $magic = "\x7F\xFE\x80\x01"; + if ($info['dts']['raw']['magic'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['dts']['raw']['magic']).'"'; + unset($info['fileformat'], $info['dts']); + return false; + } + + $fhBS = getid3_lib::BigEndian2Bin(substr($DTSheader, 4, 12)); + $bsOffset = 0; + $info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, $bsOffset, 5); + $info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, $bsOffset, 7); + $info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, $bsOffset, 14); + $info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, $bsOffset, 6); + $info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, $bsOffset, 4); + $info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, $bsOffset, 5); + $info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, $bsOffset, 3); + $info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, $bsOffset, 2); + $info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + if ($info['dts']['flags']['crc_present']) { + $info['dts']['raw']['crc16'] = $this->readBinData($fhBS, $bsOffset, 16); + } + $info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, $bsOffset, 4); + $info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, $bsOffset, 2); + $info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, $bsOffset, 2); + $info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + $info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, $bsOffset, 4); + + + $info['dts']['bitrate'] = self::DTSbitrateLookup($info['dts']['raw']['bitrate']); + $info['dts']['bits_per_sample'] = self::DTSbitPerSampleLookup($info['dts']['raw']['bits_per_sample']); + $info['dts']['sample_rate'] = self::DTSsampleRateLookup($info['dts']['raw']['sample_frequency']); + $info['dts']['dialog_normalization'] = self::DTSdialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']); + $info['dts']['flags']['lossless'] = (($info['dts']['raw']['bitrate'] == 31) ? true : false); + $info['dts']['bitrate_mode'] = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr'); + $info['dts']['channels'] = self::DTSnumChannelsLookup($info['dts']['raw']['channel_arrangement']); + $info['dts']['channel_arrangement'] = self::DTSchannelArrangementLookup($info['dts']['raw']['channel_arrangement']); + + $info['audio']['dataformat'] = 'dts'; + $info['audio']['lossless'] = $info['dts']['flags']['lossless']; + $info['audio']['bitrate_mode'] = $info['dts']['bitrate_mode']; + $info['audio']['bits_per_sample'] = $info['dts']['bits_per_sample']; + $info['audio']['sample_rate'] = $info['dts']['sample_rate']; + $info['audio']['channels'] = $info['dts']['channels']; + $info['audio']['bitrate'] = $info['dts']['bitrate']; + if (isset($info['avdataend'])) { + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8); + } + + return true; + } + + private function readBinData($bin, &$offset, $length) { + $data = substr($bin, $offset, $length); + $offset += $length; + return bindec($data); + } + + private static function DTSbitrateLookup($index) { + $DTSbitrateLookup = array( + 0 => 32000, + 1 => 56000, + 2 => 64000, + 3 => 96000, + 4 => 112000, + 5 => 128000, + 6 => 192000, + 7 => 224000, + 8 => 256000, + 9 => 320000, + 10 => 384000, + 11 => 448000, + 12 => 512000, + 13 => 576000, + 14 => 640000, + 15 => 768000, + 16 => 960000, + 17 => 1024000, + 18 => 1152000, + 19 => 1280000, + 20 => 1344000, + 21 => 1408000, + 22 => 1411200, + 23 => 1472000, + 24 => 1536000, + 25 => 1920000, + 26 => 2048000, + 27 => 3072000, + 28 => 3840000, + 29 => 'open', + 30 => 'variable', + 31 => 'lossless' + ); + return (isset($DTSbitrateLookup[$index]) ? $DTSbitrateLookup[$index] : false); + } + + private static function DTSsampleRateLookup($index) { + $DTSsampleRateLookup = array( + 0 => 'invalid', + 1 => 8000, + 2 => 16000, + 3 => 32000, + 4 => 'invalid', + 5 => 'invalid', + 6 => 11025, + 7 => 22050, + 8 => 44100, + 9 => 'invalid', + 10 => 'invalid', + 11 => 12000, + 12 => 24000, + 13 => 48000, + 14 => 'invalid', + 15 => 'invalid' + ); + return (isset($DTSsampleRateLookup[$index]) ? $DTSsampleRateLookup[$index] : false); + } + + private static function DTSbitPerSampleLookup($index) { + $DTSbitPerSampleLookup = array( + 0 => 16, + 1 => 20, + 2 => 24, + 3 => 24, + ); + return (isset($DTSbitPerSampleLookup[$index]) ? $DTSbitPerSampleLookup[$index] : false); + } + + private static function DTSnumChannelsLookup($index) { + switch ($index) { + case 0: + return 1; + break; + case 1: + case 2: + case 3: + case 4: + return 2; + break; + case 5: + case 6: + return 3; + break; + case 7: + case 8: + return 4; + break; + case 9: + return 5; + break; + case 10: + case 11: + case 12: + return 6; + break; + case 13: + return 7; + break; + case 14: + case 15: + return 8; + break; + } + return false; + } + + private static function DTSchannelArrangementLookup($index) { + $DTSchannelArrangementLookup = array( + 0 => 'A', + 1 => 'A + B (dual mono)', + 2 => 'L + R (stereo)', + 3 => '(L+R) + (L-R) (sum-difference)', + 4 => 'LT + RT (left and right total)', + 5 => 'C + L + R', + 6 => 'L + R + S', + 7 => 'C + L + R + S', + 8 => 'L + R + SL + SR', + 9 => 'C + L + R + SL + SR', + 10 => 'CL + CR + L + R + SL + SR', + 11 => 'C + L + R+ LR + RR + OV', + 12 => 'CF + CR + LF + RF + LR + RR', + 13 => 'CL + C + CR + L + R + SL + SR', + 14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2', + 15 => 'CL + C+ CR + L + R + SL + S + SR', + ); + return (isset($DTSchannelArrangementLookup[$index]) ? $DTSchannelArrangementLookup[$index] : 'user-defined'); + } + + private static function DTSdialogNormalization($index, $version) { + switch ($version) { + case 7: + return 0 - $index; + break; + case 6: + return 0 - 16 - $index; + break; + } + return false; + } + +} + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.flac.php b/3rdparty/getid3/module.audio.flac.php new file mode 100644 index 0000000000..98daec0fd9 --- /dev/null +++ b/3rdparty/getid3/module.audio.flac.php @@ -0,0 +1,480 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.flac.php // +// module for analyzing FLAC and OggFLAC audio files // +// dependencies: module.audio.ogg.php // +// /// +///////////////////////////////////////////////////////////////// + + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); + +class getid3_flac extends getid3_handler +{ + var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory + + function Analyze() { + $info = &$this->getid3->info; + + // http://flac.sourceforge.net/format.html + + $this->fseek($info['avdataoffset'], SEEK_SET); + $StreamMarker = $this->fread(4); + $magic = 'fLaC'; + if ($StreamMarker != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"'; + return false; + } + $info['fileformat'] = 'flac'; + $info['audio']['dataformat'] = 'flac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; + + return $this->FLACparseMETAdata(); + } + + + function FLACparseMETAdata() { + $info = &$this->getid3->info; + do { + $METAdataBlockOffset = $this->ftell(); + $METAdataBlockHeader = $this->fread(4); + $METAdataLastBlockFlag = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x80); + $METAdataBlockType = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x7F; + $METAdataBlockLength = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 1, 3)); + $METAdataBlockTypeText = getid3_flac::FLACmetaBlockTypeLookup($METAdataBlockType); + + if ($METAdataBlockLength < 0) { + $info['error'][] = 'corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; + break; + } + + $info['flac'][$METAdataBlockTypeText]['raw'] = array(); + $ThisFileInfo_flac_METAdataBlockTypeText_raw = &$info['flac'][$METAdataBlockTypeText]['raw']; + + $ThisFileInfo_flac_METAdataBlockTypeText_raw['offset'] = $METAdataBlockOffset; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['last_meta_block'] = $METAdataLastBlockFlag; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type'] = $METAdataBlockType; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type_text'] = $METAdataBlockTypeText; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_length'] = $METAdataBlockLength; + if (($METAdataBlockOffset + 4 + $METAdataBlockLength) > $info['avdataend']) { + $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset.' extends beyond end of file'; + break; + } + if ($METAdataBlockLength < 1) { + $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$METAdataBlockLength.') at offset '.$METAdataBlockOffset.' is invalid'; + break; + } + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'] = $this->fread($METAdataBlockLength); + $info['avdataoffset'] = $this->ftell(); + + switch ($METAdataBlockTypeText) { + case 'STREAMINFO': // 0x00 + if (!$this->FLACparseSTREAMINFO($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'PADDING': // 0x01 + // ignore + break; + + case 'APPLICATION': // 0x02 + if (!$this->FLACparseAPPLICATION($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'SEEKTABLE': // 0x03 + if (!$this->FLACparseSEEKTABLE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'VORBIS_COMMENT': // 0x04 + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $this->ftell() - $METAdataBlockLength; + $getid3_temp->info['audio']['dataformat'] = 'flac'; + $getid3_temp->info['flac'] = $info['flac']; + $getid3_ogg = new getid3_ogg($getid3_temp); + $getid3_ogg->ParseVorbisCommentsFilepointer(); + $maybe_copy_keys = array('vendor', 'comments_raw', 'comments', 'replay_gain'); + foreach ($maybe_copy_keys as $maybe_copy_key) { + if (!empty($getid3_temp->info['ogg'][$maybe_copy_key])) { + $info['ogg'][$maybe_copy_key] = $getid3_temp->info['ogg'][$maybe_copy_key]; + } + } + if (!empty($getid3_temp->info['replay_gain'])) { + $info['replay_gain'] = $getid3_temp->info['replay_gain']; + } + unset($getid3_temp, $getid3_ogg); + break; + + case 'CUESHEET': // 0x05 + if (!getid3_flac::FLACparseCUESHEET($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'PICTURE': // 0x06 + if (!getid3_flac::FLACparsePICTURE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { + return false; + } + break; + + default: + $info['warning'][] = 'Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; + break; + } + + } while ($METAdataLastBlockFlag === false); + + if (isset($info['flac']['PICTURE'])) { + foreach ($info['flac']['PICTURE'] as $key => $valuearray) { + if (!empty($valuearray['image_mime']) && !empty($valuearray['data'])) { + $info['ogg']['comments']['picture'][] = array('image_mime'=>$valuearray['image_mime'], 'data'=>$valuearray['data']); + } + } + } + + if (isset($info['flac']['STREAMINFO'])) { + $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset']; + $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8); + if ($info['flac']['uncompressed_audio_bytes'] == 0) { + $info['error'][] = 'Corrupt FLAC file: uncompressed_audio_bytes == zero'; + return false; + } + $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes']; + } + + // set md5_data_source - built into flac 0.5+ + if (isset($info['flac']['STREAMINFO']['audio_signature'])) { + + if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { + + $info['warning'][] = 'FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'; + + } else { + + $info['md5_data_source'] = ''; + $md5 = $info['flac']['STREAMINFO']['audio_signature']; + for ($i = 0; $i < strlen($md5); $i++) { + $info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); + } + if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) { + unset($info['md5_data_source']); + } + + } + + } + + $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; + if ($info['audio']['bits_per_sample'] == 8) { + // special case + // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value + // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed + $info['warning'][] = 'FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'; + } + if (!empty($info['ogg']['vendor'])) { + $info['audio']['encoder'] = $info['ogg']['vendor']; + } + + return true; + } + + static function FLACmetaBlockTypeLookup($blocktype) { + static $FLACmetaBlockTypeLookup = array(); + if (empty($FLACmetaBlockTypeLookup)) { + $FLACmetaBlockTypeLookup[0] = 'STREAMINFO'; + $FLACmetaBlockTypeLookup[1] = 'PADDING'; + $FLACmetaBlockTypeLookup[2] = 'APPLICATION'; + $FLACmetaBlockTypeLookup[3] = 'SEEKTABLE'; + $FLACmetaBlockTypeLookup[4] = 'VORBIS_COMMENT'; + $FLACmetaBlockTypeLookup[5] = 'CUESHEET'; + $FLACmetaBlockTypeLookup[6] = 'PICTURE'; + } + return (isset($FLACmetaBlockTypeLookup[$blocktype]) ? $FLACmetaBlockTypeLookup[$blocktype] : 'reserved'); + } + + static function FLACapplicationIDLookup($applicationid) { + static $FLACapplicationIDLookup = array(); + if (empty($FLACapplicationIDLookup)) { + // http://flac.sourceforge.net/id.html + $FLACapplicationIDLookup[0x46746F6C] = 'flac-tools'; // 'Ftol' + $FLACapplicationIDLookup[0x46746F6C] = 'Sound Font FLAC'; // 'SFFL' + } + return (isset($FLACapplicationIDLookup[$applicationid]) ? $FLACapplicationIDLookup[$applicationid] : 'reserved'); + } + + static function FLACpictureTypeLookup($type_id) { + static $lookup = array ( + 0 => 'Other', + 1 => '32x32 pixels \'file icon\' (PNG only)', + 2 => 'Other file icon', + 3 => 'Cover (front)', + 4 => 'Cover (back)', + 5 => 'Leaflet page', + 6 => 'Media (e.g. label side of CD)', + 7 => 'Lead artist/lead performer/soloist', + 8 => 'Artist/performer', + 9 => 'Conductor', + 10 => 'Band/Orchestra', + 11 => 'Composer', + 12 => 'Lyricist/text writer', + 13 => 'Recording Location', + 14 => 'During recording', + 15 => 'During performance', + 16 => 'Movie/video screen capture', + 17 => 'A bright coloured fish', + 18 => 'Illustration', + 19 => 'Band/artist logotype', + 20 => 'Publisher/Studio logotype', + ); + return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved'); + } + + function FLACparseSTREAMINFO($METAdataBlockData) { + $info = &$this->getid3->info; + + $offset = 0; + $info['flac']['STREAMINFO']['min_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + $info['flac']['STREAMINFO']['max_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + $info['flac']['STREAMINFO']['min_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); + $offset += 3; + $info['flac']['STREAMINFO']['max_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); + $offset += 3; + + $SampleRateChannelsSampleBitsStreamSamples = getid3_lib::BigEndian2Bin(substr($METAdataBlockData, $offset, 8)); + $info['flac']['STREAMINFO']['sample_rate'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 0, 20)); + $info['flac']['STREAMINFO']['channels'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 20, 3)) + 1; + $info['flac']['STREAMINFO']['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 23, 5)) + 1; + $info['flac']['STREAMINFO']['samples_stream'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 28, 36)); + $offset += 8; + + $info['flac']['STREAMINFO']['audio_signature'] = substr($METAdataBlockData, $offset, 16); + $offset += 16; + + if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { + + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; + $info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; + $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; + $info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate']; + if ($info['playtime_seconds'] > 0) { + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + } + + } else { + $info['error'][] = 'Corrupt METAdata block: STREAMINFO'; + return false; + } + unset($info['flac']['STREAMINFO']['raw']); + return true; + } + + + function FLACparseAPPLICATION($METAdataBlockData) { + $info = &$this->getid3->info; + + $offset = 0; + $ApplicationID = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 4)); + $offset += 4; + $info['flac']['APPLICATION'][$ApplicationID]['name'] = getid3_flac::FLACapplicationIDLookup($ApplicationID); + $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($METAdataBlockData, $offset); + $offset = $METAdataBlockLength; + + unset($info['flac']['APPLICATION']['raw']); + return true; + } + + + function FLACparseSEEKTABLE($METAdataBlockData) { + $info = &$this->getid3->info; + + $offset = 0; + $METAdataBlockLength = strlen($METAdataBlockData); + $placeholderpattern = str_repeat("\xFF", 8); + while ($offset < $METAdataBlockLength) { + $SampleNumberString = substr($METAdataBlockData, $offset, 8); + $offset += 8; + if ($SampleNumberString == $placeholderpattern) { + + // placeholder point + getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1); + $offset += 10; + + } else { + + $SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString); + $info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + + } + } + + unset($info['flac']['SEEKTABLE']['raw']); + + return true; + } + + function FLACparseCUESHEET($METAdataBlockData) { + $info = &$this->getid3->info; + $offset = 0; + $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($METAdataBlockData, $offset, 128), "\0"); + $offset += 128; + $info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)) & 0x80); + $offset += 1; + + $offset += 258; // reserved + + $info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) { + $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $TrackNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset; + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($METAdataBlockData, $offset, 12); + $offset += 12; + + $TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80); + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40); + + $offset += 13; // reserved + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) { + $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $IndexNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + $offset += 3; // reserved + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset; + } + } + + unset($info['flac']['CUESHEET']['raw']); + + return true; + } + + + function FLACparsePICTURE($meta_data_block_data) { + $info = &$this->getid3->info; + $picture = &$info['flac']['PICTURE'][sizeof($info['flac']['PICTURE']) - 1]; + $picture['offset'] = $info['flac']['PICTURE']['raw']['offset']; + unset($info['flac']['PICTURE']['raw']); + + $offset = 0; + + $picture['typeid'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $picture['type'] = getid3_flac::FLACpictureTypeLookup($picture['typeid']); + $offset += 4; + + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['image_mime'] = substr($meta_data_block_data, $offset, $length); + $offset += $length; + + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['description'] = substr($meta_data_block_data, $offset, $length); + $offset += $length; + + $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); + $offset += 4; + + $picture['data'] = substr($meta_data_block_data, $offset, $length); + $offset += $length; + $picture['data_length'] = strlen($picture['data']); + + + do { + if ($this->inline_attachments === false) { + // skip entirely + unset($picture['data']); + break; + } + if ($this->inline_attachments === true) { + // great + } elseif (is_int($this->inline_attachments)) { + if ($this->inline_attachments < $picture['data_length']) { + // too big, skip + $info['warning'][] = 'attachment at '.$picture['offset'].' is too large to process inline ('.number_format($picture['data_length']).' bytes)'; + unset($picture['data']); + break; + } + } elseif (is_string($this->inline_attachments)) { + $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { + // cannot write, skip + $info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; + unset($picture['data']); + break; + } + } + // if we get this far, must be OK + if (is_string($this->inline_attachments)) { + $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$picture['offset']; + if (!file_exists($destination_filename) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $picture['data']); + } else { + $info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $picture['data_filename'] = $destination_filename; + unset($picture['data']); + } else { + if (!isset($info['flac']['comments']['picture'])) { + $info['flac']['comments']['picture'] = array(); + } + $info['flac']['comments']['picture'][] = array('data'=>$picture['data'], 'image_mime'=>$picture['image_mime']); + } + } while (false); + + + + return true; + } +} + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.la.php b/3rdparty/getid3/module.audio.la.php new file mode 100644 index 0000000000..98d80a6dc6 --- /dev/null +++ b/3rdparty/getid3/module.audio.la.php @@ -0,0 +1,229 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.la.php // +// module for analyzing LA (LosslessAudio) audio files // +// dependencies: module.audio.riff.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + +class getid3_la extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + $offset = 0; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $rawdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + + switch (substr($rawdata, $offset, 4)) { + case 'LA02': + case 'LA03': + case 'LA04': + $info['fileformat'] = 'la'; + $info['audio']['dataformat'] = 'la'; + $info['audio']['lossless'] = true; + + $info['la']['version_major'] = (int) substr($rawdata, $offset + 2, 1); + $info['la']['version_minor'] = (int) substr($rawdata, $offset + 3, 1); + $info['la']['version'] = (float) $info['la']['version_major'] + ($info['la']['version_minor'] / 10); + $offset += 4; + + $info['la']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + if ($info['la']['uncompressed_size'] == 0) { + $info['error'][] = 'Corrupt LA file: uncompressed_size == zero'; + return false; + } + + $WAVEchunk = substr($rawdata, $offset, 4); + if ($WAVEchunk !== 'WAVE') { + $info['error'][] = 'Expected "WAVE" ('.getid3_lib::PrintHexBytes('WAVE').') at offset '.$offset.', found "'.$WAVEchunk.'" ('.getid3_lib::PrintHexBytes($WAVEchunk).') instead.'; + return false; + } + $offset += 4; + + $info['la']['fmt_size'] = 24; + if ($info['la']['version'] >= 0.3) { + + $info['la']['fmt_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $info['la']['header_size'] = 49 + $info['la']['fmt_size'] - 24; + $offset += 4; + + } else { + + // version 0.2 didn't support additional data blocks + $info['la']['header_size'] = 41; + + } + + $fmt_chunk = substr($rawdata, $offset, 4); + if ($fmt_chunk !== 'fmt ') { + $info['error'][] = 'Expected "fmt " ('.getid3_lib::PrintHexBytes('fmt ').') at offset '.$offset.', found "'.$fmt_chunk.'" ('.getid3_lib::PrintHexBytes($fmt_chunk).') instead.'; + return false; + } + $offset += 4; + $fmt_size = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + + $info['la']['raw']['format'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); + $offset += 2; + + $info['la']['channels'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); + $offset += 2; + if ($info['la']['channels'] == 0) { + $info['error'][] = 'Corrupt LA file: channels == zero'; + return false; + } + + $info['la']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + if ($info['la']['sample_rate'] == 0) { + $info['error'][] = 'Corrupt LA file: sample_rate == zero'; + return false; + } + + $info['la']['bytes_per_second'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + $info['la']['bytes_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); + $offset += 2; + $info['la']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); + $offset += 2; + + $info['la']['samples'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + + $info['la']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 1)); + $offset += 1; + $info['la']['flags']['seekable'] = (bool) ($info['la']['raw']['flags'] & 0x01); + if ($info['la']['version'] >= 0.4) { + $info['la']['flags']['high_compression'] = (bool) ($info['la']['raw']['flags'] & 0x02); + } + + $info['la']['original_crc'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + + // mikeØbevin*de + // Basically, the blocksize/seekevery are 61440/19 in La0.4 and 73728/16 + // in earlier versions. A seekpoint is added every blocksize * seekevery + // samples, so 4 * int(totalSamples / (blockSize * seekEvery)) should + // give the number of bytes used for the seekpoints. Of course, if seeking + // is disabled, there are no seekpoints stored. + if ($info['la']['version'] >= 0.4) { + $info['la']['blocksize'] = 61440; + $info['la']['seekevery'] = 19; + } else { + $info['la']['blocksize'] = 73728; + $info['la']['seekevery'] = 16; + } + + $info['la']['seekpoint_count'] = 0; + if ($info['la']['flags']['seekable']) { + $info['la']['seekpoint_count'] = floor($info['la']['samples'] / ($info['la']['blocksize'] * $info['la']['seekevery'])); + + for ($i = 0; $i < $info['la']['seekpoint_count']; $i++) { + $info['la']['seekpoints'][] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + } + } + + if ($info['la']['version'] >= 0.3) { + + // Following the main header information, the program outputs all of the + // seekpoints. Following these is what I called the 'footer start', + // i.e. the position immediately after the La audio data is finished. + $info['la']['footerstart'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + + if ($info['la']['footerstart'] > $info['filesize']) { + $info['warning'][] = 'FooterStart value points to offset '.$info['la']['footerstart'].' which is beyond end-of-file ('.$info['filesize'].')'; + $info['la']['footerstart'] = $info['filesize']; + } + + } else { + + // La v0.2 didn't have FooterStart value + $info['la']['footerstart'] = $info['avdataend']; + + } + + if ($info['la']['footerstart'] < $info['avdataend']) { + if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) { + if ($RIFF_fp = fopen($RIFFtempfilename, 'w+b')) { + $RIFFdata = 'WAVE'; + if ($info['la']['version'] == 0.2) { + $RIFFdata .= substr($rawdata, 12, 24); + } else { + $RIFFdata .= substr($rawdata, 16, 24); + } + if ($info['la']['footerstart'] < $info['avdataend']) { + fseek($this->getid3->fp, $info['la']['footerstart'], SEEK_SET); + $RIFFdata .= fread($this->getid3->fp, $info['avdataend'] - $info['la']['footerstart']); + } + $RIFFdata = 'RIFF'.getid3_lib::LittleEndian2String(strlen($RIFFdata), 4, false).$RIFFdata; + fwrite($RIFF_fp, $RIFFdata, strlen($RIFFdata)); + fclose($RIFF_fp); + + $getid3_temp = new getID3(); + $getid3_temp->openfile($RIFFtempfilename); + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->Analyze(); + + if (empty($getid3_temp->info['error'])) { + $info['riff'] = $getid3_temp->info['riff']; + } else { + $info['warning'][] = 'Error parsing RIFF portion of La file: '.implode($getid3_temp->info['error']); + } + unset($getid3_temp, $getid3_riff); + } + unlink($RIFFtempfilename); + } + } + + // $info['avdataoffset'] should be zero to begin with, but just in case it's not, include the addition anyway + $info['avdataend'] = $info['avdataoffset'] + $info['la']['footerstart']; + $info['avdataoffset'] = $info['avdataoffset'] + $offset; + + //$info['la']['codec'] = RIFFwFormatTagLookup($info['la']['raw']['format']); + $info['la']['compression_ratio'] = (float) (($info['avdataend'] - $info['avdataoffset']) / $info['la']['uncompressed_size']); + $info['playtime_seconds'] = (float) ($info['la']['samples'] / $info['la']['sample_rate']) / $info['la']['channels']; + if ($info['playtime_seconds'] == 0) { + $info['error'][] = 'Corrupt LA file: playtime_seconds == zero'; + return false; + } + + $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; + //$info['audio']['codec'] = $info['la']['codec']; + $info['audio']['bits_per_sample'] = $info['la']['bits_per_sample']; + break; + + default: + if (substr($rawdata, $offset, 2) == 'LA') { + $info['error'][] = 'This version of getID3() ['.$this->getid3->version().'] does not support LA version '.substr($rawdata, $offset + 2, 1).'.'.substr($rawdata, $offset + 3, 1).' which this appears to be - check http://getid3.sourceforge.net for updates.'; + } else { + $info['error'][] = 'Not a LA (Lossless-Audio) file'; + } + return false; + break; + } + + $info['audio']['channels'] = $info['la']['channels']; + $info['audio']['sample_rate'] = (int) $info['la']['sample_rate']; + $info['audio']['encoder'] = 'LA v'.$info['la']['version']; + + return true; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.audio.lpac.php b/3rdparty/getid3/module.audio.lpac.php new file mode 100644 index 0000000000..6ef0fb8acd --- /dev/null +++ b/3rdparty/getid3/module.audio.lpac.php @@ -0,0 +1,130 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.lpac.php // +// module for analyzing LPAC Audio files // +// dependencies: module.audio-video.riff.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + +class getid3_lpac extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $LPACheader = fread($this->getid3->fp, 14); + if (substr($LPACheader, 0, 4) != 'LPAC') { + $info['error'][] = 'Expected "LPAC" at offset '.$info['avdataoffset'].', found "'.$StreamMarker.'"'; + return false; + } + $info['avdataoffset'] += 14; + + $info['fileformat'] = 'lpac'; + $info['audio']['dataformat'] = 'lpac'; + $info['audio']['lossless'] = true; + $info['audio']['bitrate_mode'] = 'vbr'; + + $info['lpac']['file_version'] = getid3_lib::BigEndian2Int(substr($LPACheader, 4, 1)); + $flags['audio_type'] = getid3_lib::BigEndian2Int(substr($LPACheader, 5, 1)); + $info['lpac']['total_samples']= getid3_lib::BigEndian2Int(substr($LPACheader, 6, 4)); + $flags['parameters'] = getid3_lib::BigEndian2Int(substr($LPACheader, 10, 4)); + + $info['lpac']['flags']['is_wave'] = (bool) ($flags['audio_type'] & 0x40); + $info['lpac']['flags']['stereo'] = (bool) ($flags['audio_type'] & 0x04); + $info['lpac']['flags']['24_bit'] = (bool) ($flags['audio_type'] & 0x02); + $info['lpac']['flags']['16_bit'] = (bool) ($flags['audio_type'] & 0x01); + + if ($info['lpac']['flags']['24_bit'] && $info['lpac']['flags']['16_bit']) { + $info['warning'][] = '24-bit and 16-bit flags cannot both be set'; + } + + $info['lpac']['flags']['fast_compress'] = (bool) ($flags['parameters'] & 0x40000000); + $info['lpac']['flags']['random_access'] = (bool) ($flags['parameters'] & 0x08000000); + $info['lpac']['block_length'] = pow(2, (($flags['parameters'] & 0x07000000) >> 24)) * 256; + $info['lpac']['flags']['adaptive_prediction_order'] = (bool) ($flags['parameters'] & 0x00800000); + $info['lpac']['flags']['adaptive_quantization'] = (bool) ($flags['parameters'] & 0x00400000); + $info['lpac']['flags']['joint_stereo'] = (bool) ($flags['parameters'] & 0x00040000); + $info['lpac']['quantization'] = ($flags['parameters'] & 0x00001F00) >> 8; + $info['lpac']['max_prediction_order'] = ($flags['parameters'] & 0x0000003F); + + if ($info['lpac']['flags']['fast_compress'] && ($info['lpac']['max_prediction_order'] != 3)) { + $info['warning'][] = 'max_prediction_order expected to be "3" if fast_compress is true, actual value is "'.$info['lpac']['max_prediction_order'].'"'; + } + switch ($info['lpac']['file_version']) { + case 6: + if ($info['lpac']['flags']['adaptive_quantization']) { + $info['warning'][] = 'adaptive_quantization expected to be false in LPAC file stucture v6, actually true'; + } + if ($info['lpac']['quantization'] != 20) { + $info['warning'][] = 'Quantization expected to be 20 in LPAC file stucture v6, actually '.$info['lpac']['flags']['Q']; + } + break; + + default: + //$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] only supports LPAC file format version 6, this file is version '.$info['lpac']['file_version'].' - please report to info@getid3.org'; + break; + } + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info = $info; + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->Analyze(); + $info['avdataoffset'] = $getid3_temp->info['avdataoffset']; + $info['riff'] = $getid3_temp->info['riff']; + $info['error'] = $getid3_temp->info['error']; + $info['warning'] = $getid3_temp->info['warning']; + $info['lpac']['comments']['comment'] = $getid3_temp->info['comments']; + $info['audio']['sample_rate'] = $getid3_temp->info['audio']['sample_rate']; + unset($getid3_temp, $getid3_riff); + + $info['audio']['channels'] = ($info['lpac']['flags']['stereo'] ? 2 : 1); + + if ($info['lpac']['flags']['24_bit']) { + $info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample']; + } elseif ($info['lpac']['flags']['16_bit']) { + $info['audio']['bits_per_sample'] = 16; + } else { + $info['audio']['bits_per_sample'] = 8; + } + + if ($info['lpac']['flags']['fast_compress']) { + // fast + $info['audio']['encoder_options'] = '-1'; + } else { + switch ($info['lpac']['max_prediction_order']) { + case 20: // simple + $info['audio']['encoder_options'] = '-2'; + break; + case 30: // medium + $info['audio']['encoder_options'] = '-3'; + break; + case 40: // high + $info['audio']['encoder_options'] = '-4'; + break; + case 60: // extrahigh + $info['audio']['encoder_options'] = '-5'; + break; + } + } + + $info['playtime_seconds'] = $info['lpac']['total_samples'] / $info['audio']['sample_rate']; + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + + return true; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.midi.php b/3rdparty/getid3/module.audio.midi.php similarity index 87% rename from apps/media/getID3/getid3/module.audio.midi.php rename to 3rdparty/getid3/module.audio.midi.php index 0fd7c9bdbf..7b839cf140 100644 --- a/apps/media/getID3/getid3/module.audio.midi.php +++ b/3rdparty/getid3/module.audio.midi.php @@ -13,27 +13,31 @@ // /// ///////////////////////////////////////////////////////////////// +define('GETID3_MIDI_MAGIC_MTHD', 'MThd'); // MIDI file header magic +define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic -class getid3_midi +class getid3_midi extends getid3_handler { + var $scanwholefile = true; - function getid3_midi(&$fd, &$ThisFileInfo, $scanwholefile=true) { + function Analyze() { + $info = &$this->getid3->info; // shortcut - $ThisFileInfo['midi']['raw'] = array(); - $thisfile_midi = &$ThisFileInfo['midi']; + $info['midi']['raw'] = array(); + $thisfile_midi = &$info['midi']; $thisfile_midi_raw = &$thisfile_midi['raw']; - $ThisFileInfo['fileformat'] = 'midi'; - $ThisFileInfo['audio']['dataformat'] = 'midi'; + $info['fileformat'] = 'midi'; + $info['audio']['dataformat'] = 'midi'; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MIDIdata = fread($fd, GETID3_FREAD_BUFFER_SIZE); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $MIDIdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); $offset = 0; $MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd' - if ($MIDIheaderID != 'MThd') { - $ThisFileInfo['error'][] = 'Expecting "MThd" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$MIDIheaderID.'"'; - unset($ThisFileInfo['fileformat']); + if ($MIDIheaderID != GETID3_MIDI_MAGIC_MTHD) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTHD).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($MIDIheaderID).'"'; + unset($info['fileformat']); return false; } $offset += 4; @@ -47,33 +51,33 @@ class getid3_midi $offset += 2; for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) { - if ((strlen($MIDIdata) - $offset) < 8) { - $MIDIdata .= fread($fd, GETID3_FREAD_BUFFER_SIZE); + while ((strlen($MIDIdata) - $offset) < 8) { + $MIDIdata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size()); } $trackID = substr($MIDIdata, $offset, 4); $offset += 4; - if ($trackID == 'MTrk') { + if ($trackID == GETID3_MIDI_MAGIC_MTRK) { $tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4)); $offset += 4; // $thisfile_midi['tracks'][$i]['size'] = $tracksize; $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize); $offset += $tracksize; } else { - $ThisFileInfo['error'][] = 'Expecting "MTrk" at '.$offset.', found '.$trackID.' instead'; + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTRK).'" at '.($offset - 4).', found "'.getid3_lib::PrintHexBytes($trackID).'" instead'; return false; } } if (!isset($trackdataarray) || !is_array($trackdataarray)) { - $ThisFileInfo['error'][] = 'Cannot find MIDI track information'; + $info['error'][] = 'Cannot find MIDI track information'; unset($thisfile_midi); - unset($ThisFileInfo['fileformat']); + unset($info['fileformat']); return false; } - if ($scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important + if ($this->scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important $thisfile_midi['totalticks'] = 0; - $ThisFileInfo['playtime_seconds'] = 0; + $info['playtime_seconds'] = 0; $CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat $CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat $MicroSecondsPerQuarterNoteAfter = array (); @@ -215,7 +219,7 @@ class getid3_midi case 0x51: // Tempo: microseconds / quarter note $CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength)); if ($CurrentMicroSecondsPerBeat == 0) { - $ThisFileInfo['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero'; + $info['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero'; return false; } $thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat; @@ -258,13 +262,13 @@ class getid3_midi break; default: - $ThisFileInfo['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand; + $info['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand; break; } } else { - $ThisFileInfo['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']; + $info['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']; } } @@ -284,11 +288,11 @@ class getid3_midi if ($thisfile_midi['totalticks'] > $tickoffset) { if ($thisfile_midi_raw['ticksperqnote'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; + $info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; return false; } - $ThisFileInfo['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000); + $info['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000); $prevmicrosecondsperbeat = $microsecondsperbeat; $previoustickoffset = $tickoffset; @@ -297,18 +301,18 @@ class getid3_midi if ($thisfile_midi['totalticks'] > $previoustickoffset) { if ($thisfile_midi_raw['ticksperqnote'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; + $info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; return false; } - $ThisFileInfo['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000); + $info['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000); } } - - if (@$ThisFileInfo['playtime_seconds'] > 0) { - $ThisFileInfo['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + if (!empty($info['playtime_seconds'])) { + $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; } if (!empty($thisfile_midi['lyrics'])) { diff --git a/3rdparty/getid3/module.audio.mod.php b/3rdparty/getid3/module.audio.mod.php new file mode 100644 index 0000000000..b881769426 --- /dev/null +++ b/3rdparty/getid3/module.audio.mod.php @@ -0,0 +1,101 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.mod.php // +// module for analyzing MOD Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_mod extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $fileheader = fread($this->getid3->fp, 1088); + if (preg_match('#^IMPM#', $fileheader)) { + return $this->getITheaderFilepointer(); + } elseif (preg_match('#^Extended Module#', $fileheader)) { + return $this->getXMheaderFilepointer(); + } elseif (preg_match('#^.{44}SCRM#', $fileheader)) { + return $this->getS3MheaderFilepointer(); + } elseif (preg_match('#^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)#', $fileheader)) { + return $this->getMODheaderFilepointer(); + } + $info['error'][] = 'This is not a known type of MOD file'; + return false; + } + + + function getMODheaderFilepointer() { + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset'] + 1080); + $FormatID = fread($this->getid3->fp, 4); + if (!preg_match('#^(M.K.|[5-9]CHN|[1-3][0-9]CH)$#', $FormatID)) { + $info['error'][] = 'This is not a known type of MOD file'; + return false; + } + + $info['fileformat'] = 'mod'; + + $info['error'][] = 'MOD parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; + return false; + } + + function getXMheaderFilepointer() { + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset']); + $FormatID = fread($this->getid3->fp, 15); + if (!preg_match('#^Extended Module$#', $FormatID)) { + $info['error'][] = 'This is not a known type of XM-MOD file'; + return false; + } + + $info['fileformat'] = 'xm'; + + $info['error'][] = 'XM-MOD parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; + return false; + } + + function getS3MheaderFilepointer() { + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset'] + 44); + $FormatID = fread($this->getid3->fp, 4); + if (!preg_match('#^SCRM$#', $FormatID)) { + $info['error'][] = 'This is not a ScreamTracker MOD file'; + return false; + } + + $info['fileformat'] = 's3m'; + + $info['error'][] = 'ScreamTracker parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; + return false; + } + + function getITheaderFilepointer() { + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset']); + $FormatID = fread($this->getid3->fp, 4); + if (!preg_match('#^IMPM$#', $FormatID)) { + $info['error'][] = 'This is not an ImpulseTracker MOD file'; + return false; + } + + $info['fileformat'] = 'it'; + + $info['error'][] = 'ImpulseTracker parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; + return false; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.monkey.php b/3rdparty/getid3/module.audio.monkey.php similarity index 78% rename from apps/media/getID3/getid3/module.audio.monkey.php rename to 3rdparty/getid3/module.audio.monkey.php index 42382ad15e..ffaeae9f07 100644 --- a/apps/media/getID3/getid3/module.audio.monkey.php +++ b/3rdparty/getid3/module.audio.monkey.php @@ -14,29 +14,32 @@ ///////////////////////////////////////////////////////////////// -class getid3_monkey +class getid3_monkey extends getid3_handler { - function getid3_monkey(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; + // based loosely on code from TMonkey by Jurgen Faul // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html - $ThisFileInfo['fileformat'] = 'mac'; - $ThisFileInfo['audio']['dataformat'] = 'mac'; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; - $ThisFileInfo['audio']['lossless'] = true; + $info['fileformat'] = 'mac'; + $info['audio']['dataformat'] = 'mac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; - $ThisFileInfo['monkeys_audio']['raw'] = array(); - $thisfile_monkeysaudio = &$ThisFileInfo['monkeys_audio']; + $info['monkeys_audio']['raw'] = array(); + $thisfile_monkeysaudio = &$info['monkeys_audio']; $thisfile_monkeysaudio_raw = &$thisfile_monkeysaudio['raw']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MACheaderData = fread($fd, 74); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $MACheaderData = fread($this->getid3->fp, 74); $thisfile_monkeysaudio_raw['magic'] = substr($MACheaderData, 0, 4); - if ($thisfile_monkeysaudio_raw['magic'] != 'MAC ') { - $ThisFileInfo['error'][] = 'Expecting "MAC" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_monkeysaudio_raw['magic'].'"'; - unset($ThisFileInfo['fileformat']); + $magic = 'MAC '; + if ($thisfile_monkeysaudio_raw['magic'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_monkeysaudio_raw['magic']).'"'; + unset($info['fileformat']); return false; } $thisfile_monkeysaudio_raw['nVersion'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 4, 2)); // appears to be uint32 in 3.98+ @@ -105,13 +108,13 @@ class getid3_monkey } $thisfile_monkeysaudio['bits_per_sample'] = ($thisfile_monkeysaudio['flags']['24-bit'] ? 24 : ($thisfile_monkeysaudio['flags']['8-bit'] ? 8 : 16)); $thisfile_monkeysaudio['channels'] = $thisfile_monkeysaudio_raw['nChannels']; - $ThisFileInfo['audio']['channels'] = $thisfile_monkeysaudio['channels']; + $info['audio']['channels'] = $thisfile_monkeysaudio['channels']; $thisfile_monkeysaudio['sample_rate'] = $thisfile_monkeysaudio_raw['nSampleRate']; if ($thisfile_monkeysaudio['sample_rate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MAC file: frequency == zero'; + $info['error'][] = 'Corrupt MAC file: frequency == zero'; return false; } - $ThisFileInfo['audio']['sample_rate'] = $thisfile_monkeysaudio['sample_rate']; + $info['audio']['sample_rate'] = $thisfile_monkeysaudio['sample_rate']; if ($thisfile_monkeysaudio['flags']['peak_level']) { $thisfile_monkeysaudio['peak_level'] = $thisfile_monkeysaudio_raw['nPeakLevel']; $thisfile_monkeysaudio['peak_ratio'] = $thisfile_monkeysaudio['peak_level'] / pow(2, $thisfile_monkeysaudio['bits_per_sample'] - 1); @@ -123,52 +126,52 @@ class getid3_monkey } $thisfile_monkeysaudio['playtime'] = $thisfile_monkeysaudio['samples'] / $thisfile_monkeysaudio['sample_rate']; if ($thisfile_monkeysaudio['playtime'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MAC file: playtime == zero'; + $info['error'][] = 'Corrupt MAC file: playtime == zero'; return false; } - $ThisFileInfo['playtime_seconds'] = $thisfile_monkeysaudio['playtime']; - $thisfile_monkeysaudio['compressed_size'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']; + $info['playtime_seconds'] = $thisfile_monkeysaudio['playtime']; + $thisfile_monkeysaudio['compressed_size'] = $info['avdataend'] - $info['avdataoffset']; $thisfile_monkeysaudio['uncompressed_size'] = $thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * ($thisfile_monkeysaudio['bits_per_sample'] / 8); if ($thisfile_monkeysaudio['uncompressed_size'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MAC file: uncompressed_size == zero'; + $info['error'][] = 'Corrupt MAC file: uncompressed_size == zero'; return false; } $thisfile_monkeysaudio['compression_ratio'] = $thisfile_monkeysaudio['compressed_size'] / ($thisfile_monkeysaudio['uncompressed_size'] + $thisfile_monkeysaudio_raw['nHeaderDataBytes']); $thisfile_monkeysaudio['bitrate'] = (($thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * $thisfile_monkeysaudio['bits_per_sample']) / $thisfile_monkeysaudio['playtime']) * $thisfile_monkeysaudio['compression_ratio']; - $ThisFileInfo['audio']['bitrate'] = $thisfile_monkeysaudio['bitrate']; + $info['audio']['bitrate'] = $thisfile_monkeysaudio['bitrate']; // add size of MAC header to avdataoffset if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) { - $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nDescriptorBytes']; - $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderBytes']; - $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nSeekTableBytes']; - $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderDataBytes']; + $info['avdataoffset'] += $thisfile_monkeysaudio_raw['nDescriptorBytes']; + $info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderBytes']; + $info['avdataoffset'] += $thisfile_monkeysaudio_raw['nSeekTableBytes']; + $info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderDataBytes']; - $ThisFileInfo['avdataend'] -= $thisfile_monkeysaudio_raw['nTerminatingDataBytes']; + $info['avdataend'] -= $thisfile_monkeysaudio_raw['nTerminatingDataBytes']; } else { - $ThisFileInfo['avdataoffset'] += $offset; + $info['avdataoffset'] += $offset; } if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) { if ($thisfile_monkeysaudio_raw['cFileMD5'] === str_repeat("\x00", 16)) { - //$ThisFileInfo['warning'][] = 'cFileMD5 is null'; + //$info['warning'][] = 'cFileMD5 is null'; } else { - $ThisFileInfo['md5_data_source'] = ''; + $info['md5_data_source'] = ''; $md5 = $thisfile_monkeysaudio_raw['cFileMD5']; for ($i = 0; $i < strlen($md5); $i++) { - $ThisFileInfo['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); + $info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); } - if (!preg_match('/^[0-9a-f]{32}$/', $ThisFileInfo['md5_data_source'])) { - unset($ThisFileInfo['md5_data_source']); + if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) { + unset($info['md5_data_source']); } } } - $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_monkeysaudio['bits_per_sample']; - $ThisFileInfo['audio']['encoder'] = 'MAC v'.number_format($thisfile_monkeysaudio['version'], 2); - $ThisFileInfo['audio']['encoder_options'] = ucfirst($thisfile_monkeysaudio['compression']).' compression'; + $info['audio']['bits_per_sample'] = $thisfile_monkeysaudio['bits_per_sample']; + $info['audio']['encoder'] = 'MAC v'.number_format($thisfile_monkeysaudio['version'], 2); + $info['audio']['encoder_options'] = ucfirst($thisfile_monkeysaudio['compression']).' compression'; return true; } diff --git a/apps/media/getID3/getid3/module.audio.mp3.php b/3rdparty/getid3/module.audio.mp3.php similarity index 72% rename from apps/media/getID3/getid3/module.audio.mp3.php rename to 3rdparty/getid3/module.audio.mp3.php index ac9a27380e..909646e1a1 100644 --- a/apps/media/getID3/getid3/module.audio.mp3.php +++ b/3rdparty/getid3/module.audio.mp3.php @@ -21,65 +21,70 @@ define('GETID3_MP3_VALID_CHECK_FRAMES', 35); -class getid3_mp3 +class getid3_mp3 extends getid3_handler { var $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files - function getid3_mp3(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - if (!$this->getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset'])) { + $initialOffset = $info['avdataoffset']; + + if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) { if ($this->allow_bruteforce) { - $ThisFileInfo['error'][] = 'Rescanning file in BruteForce mode'; - $this->getOnlyMPEGaudioInfoBruteForce($fd, $ThisFileInfo); + $info['error'][] = 'Rescanning file in BruteForce mode'; + $this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info); } } - if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode'])) { - $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); + if (isset($info['mpeg']['audio']['bitrate_mode'])) { + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); } - if (((isset($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] > $ThisFileInfo['id3v2']['headerlength'])) || (!isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > 0)))) { + if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) { $synchoffsetwarning = 'Unknown data before synch '; - if (isset($ThisFileInfo['id3v2']['headerlength'])) { - $synchoffsetwarning .= '(ID3v2 header ends at '.$ThisFileInfo['id3v2']['headerlength'].', then '.($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']).' bytes garbage, '; + if (isset($info['id3v2']['headerlength'])) { + $synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, '; + } elseif ($initialOffset > 0) { + $synchoffsetwarning .= '(should be at '.$initialOffset.', '; } else { $synchoffsetwarning .= '(should be at beginning of file, '; } - $synchoffsetwarning .= 'synch detected at '.$ThisFileInfo['avdataoffset'].')'; - if (@$ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { + $synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')'; + if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) { - if (!empty($ThisFileInfo['id3v2']['headerlength']) && (($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']) == $ThisFileInfo['mpeg']['audio']['framelength'])) { + if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) { $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.'; - $ThisFileInfo['audio']['codec'] = 'LAME'; + $info['audio']['codec'] = 'LAME'; $CurrentDataLAMEversionString = 'LAME3.'; - } elseif (empty($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] == $ThisFileInfo['mpeg']['audio']['framelength'])) { + } elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) { $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.'; - $ThisFileInfo['audio']['codec'] = 'LAME'; + $info['audio']['codec'] = 'LAME'; $CurrentDataLAMEversionString = 'LAME3.'; } } - $ThisFileInfo['warning'][] = $synchoffsetwarning; + $info['warning'][] = $synchoffsetwarning; } - if (isset($ThisFileInfo['mpeg']['audio']['LAME'])) { - $ThisFileInfo['audio']['codec'] = 'LAME'; - if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['long_version'])) { - $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], "\x00"); - } elseif (!empty($ThisFileInfo['mpeg']['audio']['LAME']['short_version'])) { - $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['short_version'], "\x00"); + if (isset($info['mpeg']['audio']['LAME'])) { + $info['audio']['codec'] = 'LAME'; + if (!empty($info['mpeg']['audio']['LAME']['long_version'])) { + $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00"); + } elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) { + $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00"); } } - $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : @$ThisFileInfo['audio']['encoder']); + $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : '')); if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) { // a version number of LAME that does not end with a number like "LAME3.92" // or with a closing parenthesis like "LAME3.88 (alpha)" @@ -89,9 +94,9 @@ class getid3_mp3 $PossiblyLongerLAMEversion_FrameLength = 1441; // Not sure what version of LAME this is - look in padding of last frame for longer version string - $PossibleLAMEversionStringOffset = $ThisFileInfo['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; - fseek($fd, $PossibleLAMEversionStringOffset); - $PossiblyLongerLAMEversion_Data = fread($fd, $PossiblyLongerLAMEversion_FrameLength); + $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; + fseek($this->getid3->fp, $PossibleLAMEversionStringOffset); + $PossiblyLongerLAMEversion_Data = fread($this->getid3->fp, $PossiblyLongerLAMEversion_FrameLength); switch (substr($CurrentDataLAMEversionString, -1)) { case 'a': case 'b': @@ -103,62 +108,63 @@ class getid3_mp3 if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) { if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) { $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)" - if (strlen($PossiblyLongerLAMEversion_NewString) > strlen(@$ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; + if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) { + $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; } } } } - if (!empty($ThisFileInfo['audio']['encoder'])) { - $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['audio']['encoder'], "\x00 "); + if (!empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 "); } - switch (@$ThisFileInfo['mpeg']['audio']['layer']) { + switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') { case 1: case 2: - $ThisFileInfo['audio']['dataformat'] = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; + $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; break; } - if (@$ThisFileInfo['fileformat'] == 'mp3') { - switch ($ThisFileInfo['audio']['dataformat']) { + if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) { + switch ($info['audio']['dataformat']) { case 'mp1': case 'mp2': case 'mp3': - $ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat']; + $info['fileformat'] = $info['audio']['dataformat']; break; default: - $ThisFileInfo['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$ThisFileInfo['audio']['dataformat'].'"'; + $info['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"'; break; } } - if (empty($ThisFileInfo['fileformat'])) { - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['audio']['bitrate_mode']); - unset($ThisFileInfo['avdataoffset']); - unset($ThisFileInfo['avdataend']); + if (empty($info['fileformat'])) { + unset($info['fileformat']); + unset($info['audio']['bitrate_mode']); + unset($info['avdataoffset']); + unset($info['avdataend']); return false; } - $ThisFileInfo['mime_type'] = 'audio/mpeg'; - $ThisFileInfo['audio']['lossless'] = false; + $info['mime_type'] = 'audio/mpeg'; + $info['audio']['lossless'] = false; // Calculate playtime - if (!isset($ThisFileInfo['playtime_seconds']) && isset($ThisFileInfo['audio']['bitrate']) && ($ThisFileInfo['audio']['bitrate'] > 0)) { - $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['audio']['bitrate']; + if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) { + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate']; } - $ThisFileInfo['audio']['encoder_options'] = $this->GuessEncoderOptions($ThisFileInfo); + $info['audio']['encoder_options'] = $this->GuessEncoderOptions(); return true; } - function GuessEncoderOptions(&$ThisFileInfo) { + function GuessEncoderOptions() { // shortcuts - if (!empty($ThisFileInfo['mpeg']['audio'])) { - $thisfile_mpeg_audio = &$ThisFileInfo['mpeg']['audio']; + $info = &$this->getid3->info; + if (!empty($info['mpeg']['audio'])) { + $thisfile_mpeg_audio = &$info['mpeg']['audio']; if (!empty($thisfile_mpeg_audio['LAME'])) { $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; } @@ -167,7 +173,7 @@ class getid3_mp3 $encoder_options = ''; static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256); - if ((@$thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) { + if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) { $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality']; @@ -242,7 +248,7 @@ class getid3_mp3 $encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; - } elseif ($ThisFileInfo['audio']['bitrate_mode'] == 'vbr') { + } elseif ($info['audio']['bitrate_mode'] == 'vbr') { // http://gabriel.mp3-tech.org/mp3infotag.html // int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h @@ -252,13 +258,13 @@ class getid3_mp3 $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10); $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value; - } elseif ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { + } elseif ($info['audio']['bitrate_mode'] == 'cbr') { - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); + $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); } else { - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + $encoder_options = strtoupper($info['audio']['bitrate_mode']); } @@ -266,12 +272,12 @@ class getid3_mp3 $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr']; - } elseif (!empty($ThisFileInfo['audio']['bitrate'])) { + } elseif (!empty($info['audio']['bitrate'])) { - if ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); + if ($info['audio']['bitrate_mode'] == 'cbr') { + $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); } else { - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + $encoder_options = strtoupper($info['audio']['bitrate_mode']); } } @@ -279,7 +285,7 @@ class getid3_mp3 $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min']; } - if (@$thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] || @$thisfile_mpeg_audio_lame['encoding_flags']['nogap_next']) { + if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) { $encoder_options .= ' --nogap'; } @@ -389,17 +395,16 @@ class getid3_mp3 } } } - if (empty($encoder_options) && !empty($ThisFileInfo['audio']['bitrate']) && !empty($ThisFileInfo['audio']['bitrate_mode'])) { - //$encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); - $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) { + //$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); + $encoder_options = strtoupper($info['audio']['bitrate_mode']); } return $encoder_options; } - function decodeMPEGaudioHeader($fd, $offset, &$ThisFileInfo, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { - + function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; @@ -417,13 +422,12 @@ class getid3_mp3 $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); } - if ($offset >= $ThisFileInfo['avdataend']) { - $ThisFileInfo['error'][] = 'end of file encounter looking for MPEG synch'; + if (fseek($this->getid3->fp, $offset, SEEK_SET) != 0) { + $info['error'][] = 'decodeMPEGaudioHeader() failed to seek to next offset at '.$offset; return false; } - fseek($fd, $offset, SEEK_SET); - //$headerstring = fread($fd, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame - $headerstring = fread($fd, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data + //$headerstring = fread($this->getid3->fp, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame + $headerstring = fread($this->getid3->fp, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data // MP3 audio frame structure: // $aa $aa $aa $aa [$bb $bb] $cc... @@ -442,29 +446,26 @@ class getid3_mp3 } static $MPEGaudioHeaderValidCache = array(); - - // Not in cache - if (!isset($MPEGaudioHeaderValidCache[$head4])) { + if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache //$MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1) $MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false); } // shortcut - if (!isset($ThisFileInfo['mpeg']['audio'])) { - $ThisFileInfo['mpeg']['audio'] = array(); + if (!isset($info['mpeg']['audio'])) { + $info['mpeg']['audio'] = array(); } - $thisfile_mpeg_audio = &$ThisFileInfo['mpeg']['audio']; + $thisfile_mpeg_audio = &$info['mpeg']['audio']; if ($MPEGaudioHeaderValidCache[$head4]) { $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray; } else { - $ThisFileInfo['error'][] = 'Invalid MPEG audio header at offset '.$offset; + $info['error'][] = 'Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset; return false; } if (!$FastMPEGheaderScan) { - $thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']]; $thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']]; @@ -478,24 +479,23 @@ class getid3_mp3 $thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original']; $thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']]; - $ThisFileInfo['audio']['channels'] = $thisfile_mpeg_audio['channels']; - $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate']; + $info['audio']['channels'] = $thisfile_mpeg_audio['channels']; + $info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate']; if ($thisfile_mpeg_audio['protection']) { $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2)); } - } if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) { // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0 - $ThisFileInfo['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'; + $info['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'; $thisfile_mpeg_audio['raw']['bitrate'] = 0; } $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding']; $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']]; - if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $ThisFileInfo['avdataoffset'])) { + if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) { // only skip multiple frame check if free-format bitstream found at beginning of file // otherwise is quite possibly simply corrupted data $recursivesearch = false; @@ -504,14 +504,14 @@ class getid3_mp3 // For Layer 2 there are some combinations of bitrate and mode which are not allowed. if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) { - $ThisFileInfo['audio']['dataformat'] = 'mp2'; + $info['audio']['dataformat'] = 'mp2'; switch ($thisfile_mpeg_audio['channelmode']) { case 'mono': if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) { // these are ok } else { - $ThisFileInfo['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; + $info['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; return false; } break; @@ -522,7 +522,7 @@ class getid3_mp3 if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) { // these are ok } else { - $ThisFileInfo['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; + $info['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; return false; } break; @@ -532,19 +532,19 @@ class getid3_mp3 } - if ($ThisFileInfo['audio']['sample_rate'] > 0) { - $thisfile_mpeg_audio['framelength'] = getid3_mp3::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $ThisFileInfo['audio']['sample_rate']); + if ($info['audio']['sample_rate'] > 0) { + $thisfile_mpeg_audio['framelength'] = getid3_mp3::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']); } $nextframetestoffset = $offset + 1; if ($thisfile_mpeg_audio['bitrate'] != 'free') { - $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; if (isset($thisfile_mpeg_audio['framelength'])) { $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength']; } else { - $ThisFileInfo['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.'; + $info['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.'; return false; } @@ -561,7 +561,7 @@ class getid3_mp3 $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer'; - $ThisFileInfo['audio']['codec'] = 'Fraunhofer'; + $info['audio']['codec'] = 'Fraunhofer'; $SideInfoData = substr($headerstring, 4 + 2, 32); @@ -653,12 +653,12 @@ class getid3_mp3 if ($thisfile_mpeg_audio['layer'] == '1') { // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 - //$ThisFileInfo['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; - $ThisFileInfo['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $ThisFileInfo['audio']['channels']) / 12; + //$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + $info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12; } else { // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 - //$ThisFileInfo['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; - $ThisFileInfo['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $ThisFileInfo['audio']['channels']) / 144; + //$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + $info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144; } $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat); } @@ -759,10 +759,10 @@ class getid3_mp3 $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']); if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { - $ThisFileInfo['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; } - $ThisFileInfo['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator']; - $ThisFileInfo['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db']; + $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator']; + $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db']; } else { unset($thisfile_mpeg_audio_lame_RGAD['track']); } @@ -777,10 +777,10 @@ class getid3_mp3 $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']); if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { - $ThisFileInfo['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; } - $ThisFileInfo['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator']; - $ThisFileInfo['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db']; + $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator']; + $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db']; } else { unset($thisfile_mpeg_audio_lame_RGAD['album']); } @@ -836,7 +836,7 @@ class getid3_mp3 $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF); $thisfile_mpeg_audio_lame['preset_used'] = getid3_mp3::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame); if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) { - $ThisFileInfo['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'; + $info['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'; } if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) { // this may change if 3.90.4 ever comes out @@ -859,7 +859,7 @@ class getid3_mp3 $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; $thisfile_mpeg_audio['bitrate'] = getid3_mp3::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']); - $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) { // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min']; //} @@ -875,12 +875,12 @@ class getid3_mp3 $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; if ($recursivesearch) { $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; - if (getid3_mp3::RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, true)) { + if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) { $recursivesearch = false; $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; } if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') { - $ThisFileInfo['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'; + $info['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'; } } @@ -888,64 +888,64 @@ class getid3_mp3 } - if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']))) { - if ($ExpectedNumberOfAudioBytes > ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) { - if (@$ThisFileInfo['fileformat'] == 'riff') { + if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) { + if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) { + if (isset($info['fileformat']) && ($info['fileformat'] == 'riff')) { // ignore, audio data is broken into chunks so will always be data "missing" - } elseif (($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) == 1) { - $ThisFileInfo['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; + } elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) { + $info['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; } else { - $ThisFileInfo['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])).' bytes)'; + $info['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)'; } } else { - if ((($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { - // $prenullbytefileoffset = ftell($fd); - // fseek($fd, $ThisFileInfo['avdataend'], SEEK_SET); - // $PossibleNullByte = fread($fd, 1); - // fseek($fd, $prenullbytefileoffset, SEEK_SET); + if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { + // $prenullbytefileoffset = ftell($this->getid3->fp); + // fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); + // $PossibleNullByte = fread($this->getid3->fp, 1); + // fseek($this->getid3->fp, $prenullbytefileoffset, SEEK_SET); // if ($PossibleNullByte === "\x00") { - $ThisFileInfo['avdataend']--; - // $ThisFileInfo['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; + $info['avdataend']--; + // $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; // } else { - // $ThisFileInfo['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' ('.(($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; + // $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; // } } else { - $ThisFileInfo['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' ('.(($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; + $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; } } } - if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($ThisFileInfo['audio']['bitrate'])) { - if (($offset == $ThisFileInfo['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) { - $framebytelength = getid3_mp3::FreeFormatFrameLength($fd, $offset, $ThisFileInfo, true); + if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) { + if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) { + $framebytelength = $this->FreeFormatFrameLength($offset, true); if ($framebytelength > 0) { $thisfile_mpeg_audio['framelength'] = $framebytelength; if ($thisfile_mpeg_audio['layer'] == '1') { // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 - $ThisFileInfo['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + $info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; } else { // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 - $ThisFileInfo['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + $info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; } } else { - $ThisFileInfo['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header'; + $info['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header'; } } } - if (@$thisfile_mpeg_audio['VBR_frames']) { + if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') { switch ($thisfile_mpeg_audio['bitrate_mode']) { case 'vbr': case 'abr': + $bytes_per_frame = 1152; if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) { - $thisfile_mpeg_audio['VBR_bitrate'] = ((@$thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 384); + $bytes_per_frame = 384; } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) { - $thisfile_mpeg_audio['VBR_bitrate'] = ((@$thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 576); - } else { - $thisfile_mpeg_audio['VBR_bitrate'] = ((@$thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 1152); + $bytes_per_frame = 576; } + $thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0); if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) { - $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; + $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion } break; @@ -957,7 +957,7 @@ class getid3_mp3 if ($recursivesearch) { - if (!getid3_mp3::RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, $ScanAsCBR)) { + if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) { return false; } @@ -997,7 +997,7 @@ class getid3_mp3 // } // // if ($thisfile_mpeg_audio['version'] == '1') { - // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) { + // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) { // $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1); // $SideInfoOffset += 2; @@ -1005,7 +1005,7 @@ class getid3_mp3 // } // } // for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) { - // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) { + // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { // $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12); // $SideInfoOffset += 12; // $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); @@ -1069,20 +1069,23 @@ class getid3_mp3 return true; } - function RecursiveFrameScanning(&$fd, &$ThisFileInfo, &$offset, &$nextframetestoffset, $ScanAsCBR) { + function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) { + $info = &$this->getid3->info; + $firstframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false); + for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) { // check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch - if (($nextframetestoffset + 4) >= $ThisFileInfo['avdataend']) { + if (($nextframetestoffset + 4) >= $info['avdataend']) { // end of file return true; } - $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']); - if (getid3_mp3::decodeMPEGaudioHeader($fd, $nextframetestoffset, $nextframetestarray, false)) { + $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) { if ($ScanAsCBR) { - // force CBR mode, used for trying to pick out invalid audio streams with - // valid(?) VBR headers, or VBR streams with no VBR header - if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($ThisFileInfo['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $ThisFileInfo['mpeg']['audio']['bitrate'])) { + // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header + if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) { return false; } } @@ -1092,14 +1095,19 @@ class getid3_mp3 if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) { $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength']; } else { - $ThisFileInfo['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.'; + $info['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.'; return false; } + } elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) { + + // it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK + return true; + } else { // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence - $ThisFileInfo['error'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'; + $info['warning'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'; return false; } @@ -1107,9 +1115,11 @@ class getid3_mp3 return true; } - function FreeFormatFrameLength($fd, $offset, &$ThisFileInfo, $deepscan=false) { - fseek($fd, $offset, SEEK_SET); - $MPEGaudioData = fread($fd, 32768); + function FreeFormatFrameLength($offset, $deepscan=false) { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $offset, SEEK_SET); + $MPEGaudioData = fread($this->getid3->fp, 32768); $SyncPattern1 = substr($MPEGaudioData, 0, 4); // may be different pattern due to padding @@ -1140,12 +1150,12 @@ class getid3_mp3 $framelength = $framelength2; } if (!$framelength) { - $ThisFileInfo['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset; + $info['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset; return false; } else { - $ThisFileInfo['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'; - $ThisFileInfo['audio']['codec'] = 'LAME'; - $ThisFileInfo['audio']['encoder'] = 'LAME3.88'; + $info['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'; + $info['audio']['codec'] = 'LAME'; + $info['audio']['encoder'] = 'LAME3.88'; $SyncPattern1 = substr($SyncPattern1, 0, 3); $SyncPattern2 = substr($SyncPattern2, 0, 3); } @@ -1155,9 +1165,9 @@ class getid3_mp3 $ActualFrameLengthValues = array(); $nextoffset = $offset + $framelength; - while ($nextoffset < ($ThisFileInfo['avdataend'] - 6)) { - fseek($fd, $nextoffset - 1, SEEK_SET); - $NextSyncPattern = fread($fd, 6); + while ($nextoffset < ($info['avdataend'] - 6)) { + fseek($this->getid3->fp, $nextoffset - 1, SEEK_SET); + $NextSyncPattern = fread($this->getid3->fp, 6); if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) { // good - found where expected $ActualFrameLengthValues[] = $framelength; @@ -1170,7 +1180,7 @@ class getid3_mp3 $ActualFrameLengthValues[] = ($framelength + 1); $nextoffset++; } else { - $ThisFileInfo['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset; + $info['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset; return false; } $nextoffset += $framelength; @@ -1182,11 +1192,10 @@ class getid3_mp3 return $framelength; } - function getOnlyMPEGaudioInfoBruteForce($fd, &$ThisFileInfo) { - - $MPEGaudioHeaderDecodeCache = array(); - $MPEGaudioHeaderValidCache = array(); - $MPEGaudioHeaderLengthCache = array(); + function getOnlyMPEGaudioInfoBruteForce() { + $MPEGaudioHeaderDecodeCache = array(); + $MPEGaudioHeaderValidCache = array(); + $MPEGaudioHeaderLengthCache = array(); $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); @@ -1194,34 +1203,34 @@ class getid3_mp3 $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); - $LongMPEGversionLookup = array(); - $LongMPEGlayerLookup = array(); - $LongMPEGbitrateLookup = array(); - $LongMPEGpaddingLookup = array(); - $LongMPEGfrequencyLookup = array(); + $LongMPEGversionLookup = array(); + $LongMPEGlayerLookup = array(); + $LongMPEGbitrateLookup = array(); + $LongMPEGpaddingLookup = array(); + $LongMPEGfrequencyLookup = array(); + $Distribution['bitrate'] = array(); + $Distribution['frequency'] = array(); + $Distribution['layer'] = array(); + $Distribution['version'] = array(); + $Distribution['padding'] = array(); - $Distribution['bitrate'] = array(); - $Distribution['frequency'] = array(); - $Distribution['layer'] = array(); - $Distribution['version'] = array(); - $Distribution['padding'] = array(); - - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $max_frames_scan = 5000; $frames_scanned = 0; - $previousvalidframe = $ThisFileInfo['avdataoffset']; - while (ftell($fd) < $ThisFileInfo['avdataend']) { + $previousvalidframe = $info['avdataoffset']; + while (ftell($this->getid3->fp) < $info['avdataend']) { set_time_limit(30); - $head4 = fread($fd, 4); + $head4 = fread($this->getid3->fp, 4); if (strlen($head4) < 4) { break; } if ($head4{0} != "\xFF") { for ($i = 1; $i < 4; $i++) { if ($head4{$i} == "\xFF") { - fseek($fd, $i - 4, SEEK_CUR); + fseek($this->getid3->fp, $i - 4, SEEK_CUR); continue 2; } } @@ -1249,9 +1258,9 @@ class getid3_mp3 $LongMPEGfrequencyLookup[$head4]); } if ($MPEGaudioHeaderLengthCache[$head4] > 4) { - $WhereWeWere = ftell($fd); - fseek($fd, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); - $next4 = fread($fd, 4); + $WhereWeWere = ftell($this->getid3->fp); + fseek($this->getid3->fp, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); + $next4 = fread($this->getid3->fp, 4); if ($next4{0} == "\xFF") { if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { $MPEGaudioHeaderDecodeCache[$next4] = getid3_mp3::MPEGaudioHeaderDecode($next4); @@ -1260,16 +1269,16 @@ class getid3_mp3 $MPEGaudioHeaderValidCache[$next4] = getid3_mp3::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false); } if ($MPEGaudioHeaderValidCache[$next4]) { - fseek($fd, -4, SEEK_CUR); + fseek($this->getid3->fp, -4, SEEK_CUR); - @$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]++; - @$Distribution['layer'][$LongMPEGlayerLookup[$head4]]++; - @$Distribution['version'][$LongMPEGversionLookup[$head4]]++; - @$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]++; - @$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]++; + getid3_lib::safe_inc($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]); + getid3_lib::safe_inc($Distribution['layer'][$LongMPEGlayerLookup[$head4]]); + getid3_lib::safe_inc($Distribution['version'][$LongMPEGversionLookup[$head4]]); + getid3_lib::safe_inc($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]); + getid3_lib::safe_inc($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]); if ($max_frames_scan && (++$frames_scanned >= $max_frames_scan)) { - $pct_data_scanned = (ftell($fd) - $ThisFileInfo['avdataoffset']) / ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); - $ThisFileInfo['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; + $pct_data_scanned = (ftell($this->getid3->fp) - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']); + $info['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; foreach ($Distribution as $key1 => $value1) { foreach ($value1 as $key2 => $value2) { $Distribution[$key1][$key2] = round($value2 / $pct_data_scanned); @@ -1281,7 +1290,7 @@ class getid3_mp3 } } unset($next4); - fseek($fd, $WhereWeWere - 3, SEEK_SET); + fseek($this->getid3->fp, $WhereWeWere - 3, SEEK_SET); } } @@ -1290,19 +1299,19 @@ class getid3_mp3 ksort($Distribution[$key], SORT_NUMERIC); } ksort($Distribution['version'], SORT_STRING); - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate']; - $ThisFileInfo['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency']; - $ThisFileInfo['mpeg']['audio']['layer_distribution'] = $Distribution['layer']; - $ThisFileInfo['mpeg']['audio']['version_distribution'] = $Distribution['version']; - $ThisFileInfo['mpeg']['audio']['padding_distribution'] = $Distribution['padding']; + $info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate']; + $info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency']; + $info['mpeg']['audio']['layer_distribution'] = $Distribution['layer']; + $info['mpeg']['audio']['version_distribution'] = $Distribution['version']; + $info['mpeg']['audio']['padding_distribution'] = $Distribution['padding']; if (count($Distribution['version']) > 1) { - $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG version detected'; + $info['error'][] = 'Corrupt file - more than one MPEG version detected'; } if (count($Distribution['layer']) > 1) { - $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG layer detected'; + $info['error'][] = 'Corrupt file - more than one MPEG layer detected'; } if (count($Distribution['frequency']) > 1) { - $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG sample rate detected'; + $info['error'][] = 'Corrupt file - more than one MPEG sample rate detected'; } @@ -1312,29 +1321,30 @@ class getid3_mp3 $bittotal += ($bitratevalue * $bitratecount); } } - $ThisFileInfo['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']); - if ($ThisFileInfo['mpeg']['audio']['frame_count'] == 0) { - $ThisFileInfo['error'][] = 'no MPEG audio frames found'; + $info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']); + if ($info['mpeg']['audio']['frame_count'] == 0) { + $info['error'][] = 'no MPEG audio frames found'; return false; } - $ThisFileInfo['mpeg']['audio']['bitrate'] = ($bittotal / $ThisFileInfo['mpeg']['audio']['frame_count']); - $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr'); - $ThisFileInfo['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true); + $info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']); + $info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr'); + $info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true); - $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; - $ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode']; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; - $ThisFileInfo['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true); - $ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true); + $info['fileformat'] = $info['audio']['dataformat']; return true; } - function getOnlyMPEGaudioInfo($fd, &$ThisFileInfo, $avdataoffset, $BitrateHistogram=false) { - + function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) { // looks for synch, decodes MPEG audio header + $info = &$this->getid3->info; + static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; @@ -1345,131 +1355,125 @@ class getid3_mp3 } - fseek($fd, $avdataoffset, SEEK_SET); - $sync_seek_buffer_size = min(128 * 1024, $ThisFileInfo['avdataend'] - $avdataoffset); + fseek($this->getid3->fp, $avdataoffset, SEEK_SET); + $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset); if ($sync_seek_buffer_size <= 0) { - $ThisFileInfo['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset; + $info['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset; return false; } - $header = fread($fd, $sync_seek_buffer_size); + $header = fread($this->getid3->fp, $sync_seek_buffer_size); $sync_seek_buffer_size = strlen($header); $SynchSeekOffset = 0; while ($SynchSeekOffset < $sync_seek_buffer_size) { - - if ((($avdataoffset + $SynchSeekOffset) < $ThisFileInfo['avdataend']) && !feof($fd)) { + if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) { if ($SynchSeekOffset > $sync_seek_buffer_size) { // if a synch's not found within the first 128k bytes, then give up - $ThisFileInfo['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB'; - if (isset($ThisFileInfo['audio']['bitrate'])) { - unset($ThisFileInfo['audio']['bitrate']); + $info['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); } - if (isset($ThisFileInfo['mpeg']['audio'])) { - unset($ThisFileInfo['mpeg']['audio']); + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); } - if (empty($ThisFileInfo['mpeg'])) { - unset($ThisFileInfo['mpeg']); + if (empty($info['mpeg'])) { + unset($info['mpeg']); } return false; - } elseif (feof($fd)) { + } elseif (feof($this->getid3->fp)) { - $ThisFileInfo['error'][] = 'Could not find valid MPEG audio synch before end of file'; - if (isset($ThisFileInfo['audio']['bitrate'])) { - unset($ThisFileInfo['audio']['bitrate']); + $info['error'][] = 'Could not find valid MPEG audio synch before end of file'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); } - if (isset($ThisFileInfo['mpeg']['audio'])) { - unset($ThisFileInfo['mpeg']['audio']); + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); } - if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) { - unset($ThisFileInfo['mpeg']); + if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) { + unset($info['mpeg']); } return false; } } if (($SynchSeekOffset + 1) >= strlen($header)) { - $ThisFileInfo['error'][] = 'Could not find valid MPEG synch before end of file'; + $info['error'][] = 'Could not find valid MPEG synch before end of file'; return false; } if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected - - if (!isset($FirstFrameThisfileInfo) && !isset($ThisFileInfo['mpeg']['audio'])) { - $FirstFrameThisfileInfo = $ThisFileInfo; + if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) { + $FirstFrameThisfileInfo = $info; $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; - if (!getid3_mp3::decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $FirstFrameThisfileInfo, false)) { + if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) { // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below unset($FirstFrameThisfileInfo); } } - $dummy = $ThisFileInfo; // only overwrite real data if valid header found - if (getid3_mp3::decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $dummy, true)) { - $ThisFileInfo = $dummy; - $ThisFileInfo['avdataoffset'] = $avdataoffset + $SynchSeekOffset; - switch (@$ThisFileInfo['fileformat']) { + $dummy = $info; // only overwrite real data if valid header found + if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) { + $info = $dummy; + $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset; + switch (isset($info['fileformat']) ? $info['fileformat'] : '') { case '': case 'id3': case 'ape': case 'mp3': - $ThisFileInfo['fileformat'] = 'mp3'; - $ThisFileInfo['audio']['dataformat'] = 'mp3'; + $info['fileformat'] = 'mp3'; + $info['audio']['dataformat'] = 'mp3'; break; } if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { - if (!(abs($ThisFileInfo['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { + if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { // If there is garbage data between a valid VBR header frame and a sequence // of valid MPEG-audio frames the VBR data is no longer discarded. - $ThisFileInfo = $FirstFrameThisfileInfo; - $ThisFileInfo['avdataoffset'] = $FirstFrameAVDataOffset; - $ThisFileInfo['fileformat'] = 'mp3'; - $ThisFileInfo['audio']['dataformat'] = 'mp3'; - $dummy = $ThisFileInfo; + $info = $FirstFrameThisfileInfo; + $info['avdataoffset'] = $FirstFrameAVDataOffset; + $info['fileformat'] = 'mp3'; + $info['audio']['dataformat'] = 'mp3'; + $dummy = $info; unset($dummy['mpeg']['audio']); $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength']; $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset; - if (getid3_mp3::decodeMPEGaudioHeader($fd, $GarbageOffsetEnd, $dummy, true, true)) { - - $ThisFileInfo = $dummy; - $ThisFileInfo['avdataoffset'] = $GarbageOffsetEnd; - $ThisFileInfo['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd; - + if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) { + $info = $dummy; + $info['avdataoffset'] = $GarbageOffsetEnd; + $info['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd; } else { - - $ThisFileInfo['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'; - + $info['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'; } } } - if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode']) && ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($ThisFileInfo['mpeg']['audio']['VBR_method'])) { + if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) { // VBR file with no VBR header $BitrateHistogram = true; } if ($BitrateHistogram) { - $ThisFileInfo['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0); - $ThisFileInfo['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0); + $info['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0); + $info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0); - if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { - if ($ThisFileInfo['mpeg']['audio']['layer'] == 3) { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0); - } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 2) { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0); - } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 1) { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0); + if ($info['mpeg']['audio']['version'] == '1') { + if ($info['mpeg']['audio']['layer'] == 3) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 2) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 1) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0); } - } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 1) { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 1) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0); } else { - $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0); + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0); } - $dummy = array('error'=>$ThisFileInfo['error'], 'warning'=>$ThisFileInfo['warning'], 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']); - $synchstartoffset = $ThisFileInfo['avdataoffset']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + $synchstartoffset = $info['avdataoffset']; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); // you can play with these numbers: $max_frames_scan = 50000; @@ -1483,116 +1487,102 @@ class getid3_mp3 $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments); $pct_data_scanned = 0; for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) { -//echo 'was at '.ftell($fd).'
    '; $frames_scanned_this_segment = 0; - if (ftell($fd) >= $ThisFileInfo['avdataend']) { -//echo 'breaking because current position ('.ftell($fd).') is >= $ThisFileInfo[avdataend] ('.$ThisFileInfo['avdataend'].')
    '; + if (ftell($this->getid3->fp) >= $info['avdataend']) { break; } - $scan_start_offset[$current_segment] = max(ftell($fd), $ThisFileInfo['avdataoffset'] + round($current_segment * (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $max_scan_segments))); -//echo 'start at '.$scan_start_offset[$current_segment].'
    '; + $scan_start_offset[$current_segment] = max(ftell($this->getid3->fp), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments))); if ($current_segment > 0) { - fseek($fd, $scan_start_offset[$current_segment], SEEK_SET); - $buffer_4k = fread($fd, 4096); + fseek($this->getid3->fp, $scan_start_offset[$current_segment], SEEK_SET); + $buffer_4k = fread($this->getid3->fp, 4096); for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) { if (($buffer_4k{$j} == "\xFF") && ($buffer_4k{($j + 1)} > "\xE0")) { // synch detected - if (getid3_mp3::decodeMPEGaudioHeader($fd, $scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { + if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength']; - if (getid3_mp3::decodeMPEGaudioHeader($fd, $calculated_next_offset, $dummy, false, false, $FastMode)) { + if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) { $scan_start_offset[$current_segment] += $j; break; - } else { -//echo 'header['.__LINE__.'] at '.($calculated_next_offset).' invalid
    '; } - } else { -//echo 'header['.__LINE__.'] at '.($scan_start_offset[$current_segment] + $j).' invalid
    '; } } } } -//echo 'actually start at '.$scan_start_offset[$current_segment].'
    '; $synchstartoffset = $scan_start_offset[$current_segment]; - while (getid3_mp3::decodeMPEGaudioHeader($fd, $synchstartoffset, $dummy, false, false, $FastMode)) { + while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) { $FastMode = true; $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']]; if (empty($dummy['mpeg']['audio']['framelength'])) { $SynchErrorsFound++; $synchstartoffset++; -//echo ' [Ø] '; } else { -//echo ' . '; - @$ThisFileInfo['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]++; - @$ThisFileInfo['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]++; - @$ThisFileInfo['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]++; - + getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]); + getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]); + getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]); $synchstartoffset += $dummy['mpeg']['audio']['framelength']; } $frames_scanned++; if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) { - $this_pct_scanned = (ftell($fd) - $scan_start_offset[$current_segment]) / ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); + $this_pct_scanned = (ftell($this->getid3->fp) - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']); if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) { // file likely contains < $max_frames_scan, just scan as one segment $max_scan_segments = 1; $frames_scan_per_segment = $max_frames_scan; } else { $pct_data_scanned += $this_pct_scanned; -//var_dump($pct_data_scanned); -//exit; break; } } } -//echo '
    '; } if ($pct_data_scanned > 0) { - $ThisFileInfo['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; - foreach ($ThisFileInfo['mpeg']['audio'] as $key1 => $value1) { - if (!eregi('_distribution$', $key1)) { + $info['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; + foreach ($info['mpeg']['audio'] as $key1 => $value1) { + if (!preg_match('#_distribution$#i', $key1)) { continue; } foreach ($value1 as $key2 => $value2) { - $ThisFileInfo['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned); + $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned); } } } if ($SynchErrorsFound > 0) { - $ThisFileInfo['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis'; + $info['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis'; //return false; } $bittotal = 0; $framecounter = 0; - foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { + foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { $framecounter += $bitratecount; if ($bitratevalue != 'free') { $bittotal += ($bitratevalue * $bitratecount); } } if ($framecounter == 0) { - $ThisFileInfo['error'][] = 'Corrupt MP3 file: framecounter == zero'; + $info['error'][] = 'Corrupt MP3 file: framecounter == zero'; return false; } - $ThisFileInfo['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter); - $ThisFileInfo['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter); + $info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter); + $info['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter); - $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently $distinct_bitrates = 0; - foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { + foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { if ($bitrate_count > 0) { $distinct_bitrates++; } } if ($distinct_bitrates > 1) { - $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr'; + $info['mpeg']['audio']['bitrate_mode'] = 'vbr'; } else { - $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr'; + $info['mpeg']['audio']['bitrate_mode'] = 'cbr'; } - $ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode']; + $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; } @@ -1601,20 +1591,20 @@ class getid3_mp3 } $SynchSeekOffset++; - if (($avdataoffset + $SynchSeekOffset) >= $ThisFileInfo['avdataend']) { + if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) { // end of file/data - if (empty($ThisFileInfo['mpeg']['audio'])) { + if (empty($info['mpeg']['audio'])) { - $ThisFileInfo['error'][] = 'could not find valid MPEG synch before end of file'; - if (isset($ThisFileInfo['audio']['bitrate'])) { - unset($ThisFileInfo['audio']['bitrate']); + $info['error'][] = 'could not find valid MPEG synch before end of file'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); } - if (isset($ThisFileInfo['mpeg']['audio'])) { - unset($ThisFileInfo['mpeg']['audio']); + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); } - if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || empty($ThisFileInfo['mpeg']))) { - unset($ThisFileInfo['mpeg']); + if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) { + unset($info['mpeg']); } return false; @@ -1623,24 +1613,24 @@ class getid3_mp3 } } - $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; - $ThisFileInfo['audio']['channelmode'] = $ThisFileInfo['mpeg']['audio']['channelmode']; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; return true; } - function MPEGaudioVersionArray() { + static function MPEGaudioVersionArray() { static $MPEGaudioVersion = array('2.5', false, '2', '1'); return $MPEGaudioVersion; } - function MPEGaudioLayerArray() { + static function MPEGaudioLayerArray() { static $MPEGaudioLayer = array(false, 3, 2, 1); return $MPEGaudioLayer; } - function MPEGaudioBitrateArray() { + static function MPEGaudioBitrateArray() { static $MPEGaudioBitrate; if (empty($MPEGaudioBitrate)) { $MPEGaudioBitrate = array ( @@ -1659,7 +1649,7 @@ class getid3_mp3 return $MPEGaudioBitrate; } - function MPEGaudioFrequencyArray() { + static function MPEGaudioFrequencyArray() { static $MPEGaudioFrequency; if (empty($MPEGaudioFrequency)) { $MPEGaudioFrequency = array ( @@ -1671,12 +1661,12 @@ class getid3_mp3 return $MPEGaudioFrequency; } - function MPEGaudioChannelModeArray() { + static function MPEGaudioChannelModeArray() { static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono'); return $MPEGaudioChannelMode; } - function MPEGaudioModeExtensionArray() { + static function MPEGaudioModeExtensionArray() { static $MPEGaudioModeExtension; if (empty($MPEGaudioModeExtension)) { $MPEGaudioModeExtension = array ( @@ -1688,16 +1678,16 @@ class getid3_mp3 return $MPEGaudioModeExtension; } - function MPEGaudioEmphasisArray() { + static function MPEGaudioEmphasisArray() { static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17'); return $MPEGaudioEmphasis; } - function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { + static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { return getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); } - function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { + static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) { return false; } @@ -1769,7 +1759,7 @@ class getid3_mp3 return true; } - function MPEGaudioHeaderDecode($Header4Bytes) { + static function MPEGaudioHeaderDecode($Header4Bytes) { // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM // A - Frame sync (all bits set) // B - MPEG Audio version ID @@ -1806,7 +1796,7 @@ class getid3_mp3 return $MPEGrawHeader; } - function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { + static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { static $AudioFrameLengthCache = array(); if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) { @@ -1867,28 +1857,27 @@ class getid3_mp3 return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; } - function ClosestStandardMP3Bitrate($bitrate) { - static $StandardBitrates = array(320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); - static $BitrateTable = array(0=>'-'); - $roundbitrate = intval(round($bitrate, -3)); - if (!isset($BitrateTable[$roundbitrate])) { - if ($roundbitrate > 320000) { - $BitrateTable[$roundbitrate] = round($bitrate, -4); + static function ClosestStandardMP3Bitrate($bit_rate) { + static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); + static $bit_rate_table = array (0=>'-'); + $round_bit_rate = intval(round($bit_rate, -3)); + if (!isset($bit_rate_table[$round_bit_rate])) { + if ($round_bit_rate > max($standard_bit_rates)) { + $bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate)); } else { - $LastBitrate = 320000; - foreach ($StandardBitrates as $StandardBitrate) { - $BitrateTable[$roundbitrate] = $StandardBitrate; - if ($roundbitrate >= $StandardBitrate - (($LastBitrate - $StandardBitrate) / 2)) { + $bit_rate_table[$round_bit_rate] = max($standard_bit_rates); + foreach ($standard_bit_rates as $standard_bit_rate) { + if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) { break; } - $LastBitrate = $StandardBitrate; + $bit_rate_table[$round_bit_rate] = $standard_bit_rate; } } } - return $BitrateTable[$roundbitrate]; + return $bit_rate_table[$round_bit_rate]; } - function XingVBRidOffset($version, $channelmode) { + static function XingVBRidOffset($version, $channelmode) { static $XingVBRidOffsetCache = array(); if (empty($XingVBRidOffset)) { $XingVBRidOffset = array ( @@ -1914,7 +1903,7 @@ class getid3_mp3 return $XingVBRidOffset[$version][$channelmode]; } - function LAMEvbrMethodLookup($VBRmethodID) { + static function LAMEvbrMethodLookup($VBRmethodID) { static $LAMEvbrMethodLookup = array( 0x00 => 'unknown', 0x01 => 'cbr', @@ -1930,7 +1919,7 @@ class getid3_mp3 return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); } - function LAMEmiscStereoModeLookup($StereoModeID) { + static function LAMEmiscStereoModeLookup($StereoModeID) { static $LAMEmiscStereoModeLookup = array( 0 => 'mono', 1 => 'stereo', @@ -1944,7 +1933,7 @@ class getid3_mp3 return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); } - function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { + static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { static $LAMEmiscSourceSampleFrequencyLookup = array( 0 => '<= 32 kHz', 1 => '44.1 kHz', @@ -1954,7 +1943,7 @@ class getid3_mp3 return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); } - function LAMEsurroundInfoLookup($SurroundInfoID) { + static function LAMEsurroundInfoLookup($SurroundInfoID) { static $LAMEsurroundInfoLookup = array( 0 => 'no surround info', 1 => 'DPL encoding', @@ -1964,7 +1953,7 @@ class getid3_mp3 return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); } - function LAMEpresetUsedLookup($LAMEtag) { + static function LAMEpresetUsedLookup($LAMEtag) { if ($LAMEtag['preset_used_id'] == 0) { // no preset used (LAME >=3.93) diff --git a/apps/media/getID3/getid3/module.audio.mpc.php b/3rdparty/getid3/module.audio.mpc.php similarity index 72% rename from apps/media/getID3/getid3/module.audio.mpc.php rename to 3rdparty/getid3/module.audio.mpc.php index 66200ad02a..9a0b16d9ab 100644 --- a/apps/media/getID3/getid3/module.audio.mpc.php +++ b/3rdparty/getid3/module.audio.mpc.php @@ -14,42 +14,44 @@ ///////////////////////////////////////////////////////////////// -class getid3_mpc +class getid3_mpc extends getid3_handler { - function getid3_mpc(&$fd, &$ThisFileInfo) { - $ThisFileInfo['mpc']['header'] = array(); - $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'mpc'; - $ThisFileInfo['audio']['dataformat'] = 'mpc'; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; - $ThisFileInfo['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only - $ThisFileInfo['audio']['lossless'] = false; + $info['mpc']['header'] = array(); + $thisfile_mpc_header = &$info['mpc']['header']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MPCheaderData = fread($fd, 4); - $ThisFileInfo['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6) - if (ereg('^MPCK', $ThisFileInfo['mpc']['header']['preamble'])) { + $info['fileformat'] = 'mpc'; + $info['audio']['dataformat'] = 'mpc'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only + $info['audio']['lossless'] = false; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $MPCheaderData = fread($this->getid3->fp, 4); + $info['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6) + if (preg_match('#^MPCK#', $info['mpc']['header']['preamble'])) { // this is SV8 - return $this->ParseMPCsv8($fd, $ThisFileInfo); + return $this->ParseMPCsv8(); - } elseif (ereg('^MP\+', $ThisFileInfo['mpc']['header']['preamble'])) { + } elseif (preg_match('#^MP\+#', $info['mpc']['header']['preamble'])) { // this is SV7 - return $this->ParseMPCsv7($fd, $ThisFileInfo); + return $this->ParseMPCsv7(); } elseif (preg_match('/^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]/s', $MPCheaderData)) { // this is SV4 - SV6, handle seperately - return $this->ParseMPCsv6($fd, $ThisFileInfo); + return $this->ParseMPCsv6(); } else { - $ThisFileInfo['error'][] = 'Expecting "MP+" or "MPCK" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($MPCheaderData, 0, 4).'"'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['mpc']); + $info['error'][] = 'Expecting "MP+" or "MPCK" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($MPCheaderData, 0, 4)).'"'; + unset($info['fileformat']); + unset($info['mpc']); return false; } @@ -57,35 +59,36 @@ class getid3_mpc } - function ParseMPCsv8(&$fd, &$ThisFileInfo) { + function ParseMPCsv8() { // this is SV8 // http://trac.musepack.net/trac/wiki/SV8Specification - $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; $keyNameSize = 2; $maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10" - $offset = ftell($fd); - while ($offset < $ThisFileInfo['avdataend']) { + $offset = ftell($this->getid3->fp); + while ($offset < $info['avdataend']) { $thisPacket = array(); $thisPacket['offset'] = $offset; $packet_offset = 0; // Size is a variable-size field, could be 1-4 bytes (possibly more?) // read enough data in and figure out the exact size later - $MPCheaderData = fread($fd, $keyNameSize + $maxHandledPacketLength); + $MPCheaderData = fread($this->getid3->fp, $keyNameSize + $maxHandledPacketLength); $packet_offset += $keyNameSize; $thisPacket['key'] = substr($MPCheaderData, 0, $keyNameSize); $thisPacket['key_name'] = $this->MPCsv8PacketName($thisPacket['key']); if ($thisPacket['key'] == $thisPacket['key_name']) { - $ThisFileInfo['error'][] = 'Found unexpected key value "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; + $info['error'][] = 'Found unexpected key value "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; return false; } $packetLength = 0; $thisPacket['packet_size'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $keyNameSize), $packetLength); // includes keyname and packet_size field if ($thisPacket['packet_size'] === false) { - $ThisFileInfo['error'][] = 'Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize); + $info['error'][] = 'Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize); return false; } $packet_offset += $packetLength; @@ -95,7 +98,7 @@ class getid3_mpc case 'SH': // Stream Header $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { - $MPCheaderData .= fread($fd, $moreBytesToRead); + $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $thisPacket['crc'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4)); $packet_offset += 4; @@ -124,16 +127,16 @@ class getid3_mpc $thisfile_mpc_header['samples'] = $thisPacket['sample_count']; $thisfile_mpc_header['stream_version_major'] = $thisPacket['stream_version']; - $ThisFileInfo['audio']['channels'] = $thisPacket['channels']; - $ThisFileInfo['audio']['sample_rate'] = $thisPacket['sample_frequency']; - $ThisFileInfo['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency']; - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $info['audio']['channels'] = $thisPacket['channels']; + $info['audio']['sample_rate'] = $thisPacket['sample_frequency']; + $info['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency']; + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; break; case 'RG': // Replay Gain $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { - $MPCheaderData .= fread($fd, $moreBytesToRead); + $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $thisPacket['replaygain_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; @@ -146,16 +149,16 @@ class getid3_mpc $thisPacket['replaygain_album_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); $packet_offset += 2; - if ($thisPacket['replaygain_title_gain']) { $ThisFileInfo['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; } - if ($thisPacket['replaygain_title_peak']) { $ThisFileInfo['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; } - if ($thisPacket['replaygain_album_gain']) { $ThisFileInfo['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; } - if ($thisPacket['replaygain_album_peak']) { $ThisFileInfo['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; } + if ($thisPacket['replaygain_title_gain']) { $info['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; } + if ($thisPacket['replaygain_title_peak']) { $info['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; } + if ($thisPacket['replaygain_album_gain']) { $info['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; } + if ($thisPacket['replaygain_album_peak']) { $info['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; } break; case 'EI': // Encoder Info $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { - $MPCheaderData .= fread($fd, $moreBytesToRead); + $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $profile_pns = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; @@ -171,8 +174,8 @@ class getid3_mpc $packet_offset += 1; $thisPacket['version'] = $thisPacket['version_major'].'.'.$thisPacket['version_minor'].'.'.$thisPacket['version_build']; - $ThisFileInfo['audio']['encoder'] = 'MPC v'.$thisPacket['version'].' ('.(($thisPacket['version_minor'] % 2) ? 'unstable' : 'stable').')'; - $thisfile_mpc_header['encoder_version'] = $ThisFileInfo['audio']['encoder']; + $info['audio']['encoder'] = 'MPC v'.$thisPacket['version'].' ('.(($thisPacket['version_minor'] % 2) ? 'unstable' : 'stable').')'; + $thisfile_mpc_header['encoder_version'] = $info['audio']['encoder']; //$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] / 1.5875); // values can range from 0.000 to 15.875, mapped to qualities of 0.0 to 10.0 $thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] - 5); // values can range from 0.000 to 15.875, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0 break; @@ -191,28 +194,30 @@ class getid3_mpc break; default: - $ThisFileInfo['error'][] = 'Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; + $info['error'][] = 'Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; return false; break; } if (!empty($thisPacket)) { - $ThisFileInfo['mpc']['packets'][] = $thisPacket; + $info['mpc']['packets'][] = $thisPacket; } - fseek($fd, $offset); + fseek($this->getid3->fp, $offset); } $thisfile_mpc_header['size'] = $offset; return true; } - function ParseMPCsv7(&$fd, &$ThisFileInfo) { + function ParseMPCsv7() { // this is SV7 // http://www.uni-jena.de/~pfk/mpp/sv8/header.html - $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; $offset = 0; $thisfile_mpc_header['size'] = 28; - $MPCheaderData = $ThisFileInfo['mpc']['header']['preamble']; - $MPCheaderData .= fread($fd, $thisfile_mpc_header['size'] - strlen($ThisFileInfo['mpc']['header']['preamble'])); + $MPCheaderData = $info['mpc']['header']['preamble']; + $MPCheaderData .= fread($this->getid3->fp, $thisfile_mpc_header['size'] - strlen($info['mpc']['header']['preamble'])); $offset = strlen('MP+'); $StreamVersionByte = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1)); @@ -223,7 +228,7 @@ class getid3_mpc $offset += 4; if ($thisfile_mpc_header['stream_version_major'] != 7) { - $ThisFileInfo['error'][] = 'Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')'; + $info['error'][] = 'Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')'; return false; } @@ -262,22 +267,22 @@ class getid3_mpc $thisfile_mpc_header['profile'] = $this->MPCprofileNameLookup($thisfile_mpc_header['raw']['profile']); $thisfile_mpc_header['sample_rate'] = $this->MPCfrequencyLookup($thisfile_mpc_header['raw']['sample_rate']); if ($thisfile_mpc_header['sample_rate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MPC file: frequency == zero'; + $info['error'][] = 'Corrupt MPC file: frequency == zero'; return false; } - $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; - $thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $ThisFileInfo['audio']['channels']; + $info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; + $thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $info['audio']['channels']; - $ThisFileInfo['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $ThisFileInfo['audio']['channels']) / $ThisFileInfo['audio']['sample_rate']; - if ($ThisFileInfo['playtime_seconds'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt MPC file: playtime_seconds == zero'; + $info['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $info['audio']['channels']) / $info['audio']['sample_rate']; + if ($info['playtime_seconds'] == 0) { + $info['error'][] = 'Corrupt MPC file: playtime_seconds == zero'; return false; } // add size of file header to avdataoffset - calc bitrate correctly + MD5 data - $ThisFileInfo['avdataoffset'] += $thisfile_mpc_header['size']; + $info['avdataoffset'] += $thisfile_mpc_header['size']; - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; $thisfile_mpc_header['title_peak'] = $thisfile_mpc_header['raw']['title_peak']; $thisfile_mpc_header['title_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['title_peak']); @@ -296,37 +301,39 @@ class getid3_mpc } $thisfile_mpc_header['encoder_version'] = $this->MPCencoderVersionLookup($thisfile_mpc_header['raw']['encoder_version']); - $ThisFileInfo['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db']; - $ThisFileInfo['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db']; + $info['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db']; + $info['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db']; if ($thisfile_mpc_header['title_peak'] > 0) { - $ThisFileInfo['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak']; + $info['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak']; } elseif (round($thisfile_mpc_header['max_level'] * 1.18) > 0) { - $ThisFileInfo['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c + $info['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c } if ($thisfile_mpc_header['album_peak'] > 0) { - $ThisFileInfo['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak']; + $info['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak']; } - //$ThisFileInfo['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'].'.'.$thisfile_mpc_header['stream_version_minor'].', '.$thisfile_mpc_header['encoder_version']; - $ThisFileInfo['audio']['encoder'] = $thisfile_mpc_header['encoder_version']; - $ThisFileInfo['audio']['encoder_options'] = $thisfile_mpc_header['profile']; + //$info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'].'.'.$thisfile_mpc_header['stream_version_minor'].', '.$thisfile_mpc_header['encoder_version']; + $info['audio']['encoder'] = $thisfile_mpc_header['encoder_version']; + $info['audio']['encoder_options'] = $thisfile_mpc_header['profile']; $thisfile_mpc_header['quality'] = (float) ($thisfile_mpc_header['raw']['profile'] - 5); // values can range from 0 to 15, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0 return true; } - function ParseMPCsv6(&$fd, &$ThisFileInfo) { + function ParseMPCsv6() { // this is SV4 - SV6 - $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; $offset = 0; - $thisfile_mpc_header['size'] = 8; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $MPCheaderData = fread($fd, $thisfile_mpc_header['size']); + $thisfile_mpc_header['size'] = 8; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $MPCheaderData = fread($this->getid3->fp, $thisfile_mpc_header['size']); - // add size of file header to avdataoffset - calc bitrate correctly + MD5 data - $ThisFileInfo['avdataoffset'] += $thisfile_mpc_header['size']; + // add size of file header to avdataoffset - calc bitrate correctly + MD5 data + $info['avdataoffset'] += $thisfile_mpc_header['size']; // Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :) $HeaderDWORD[0] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 0, 4)); @@ -362,29 +369,29 @@ class getid3_mpc break; default: - $ThisFileInfo['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_version_major'].' instead'; - unset($ThisFileInfo['mpc']); + $info['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_version_major'].' instead'; + unset($info['mpc']); return false; break; } if (($thisfile_mpc_header['stream_version_major'] > 4) && ($thisfile_mpc_header['block_size'] != 1)) { - $ThisFileInfo['warning'][] = 'Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size']; + $info['warning'][] = 'Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size']; } $thisfile_mpc_header['sample_rate'] = 44100; // AB: used by all files up to SV7 - $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; - $thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $ThisFileInfo['audio']['channels']; + $info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; + $thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $info['audio']['channels']; if ($thisfile_mpc_header['target_bitrate'] == 0) { - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['bitrate_mode'] = 'vbr'; } else { - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['bitrate_mode'] = 'cbr'; } - $ThisFileInfo['mpc']['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152; - $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpc']['bitrate']; - $ThisFileInfo['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major']; + $info['mpc']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152; + $info['audio']['bitrate'] = $info['mpc']['bitrate']; + $info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major']; return true; } diff --git a/3rdparty/getid3/module.audio.ogg.php b/3rdparty/getid3/module.audio.ogg.php new file mode 100644 index 0000000000..c987fa67d7 --- /dev/null +++ b/3rdparty/getid3/module.audio.ogg.php @@ -0,0 +1,705 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.ogg.php // +// module for analyzing Ogg Vorbis, OggFLAC and Speex files // +// dependencies: module.audio.flac.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true); + +class getid3_ogg extends getid3_handler +{ + var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'ogg'; + + // Warn about illegal tags - only vorbiscomments are allowed + if (isset($info['id3v2'])) { + $info['warning'][] = 'Illegal ID3v2 tag present.'; + } + if (isset($info['id3v1'])) { + $info['warning'][] = 'Illegal ID3v1 tag present.'; + } + if (isset($info['ape'])) { + $info['warning'][] = 'Illegal APE tag present.'; + } + + + // Page 1 - Stream Header + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $oggpageinfo = $this->ParseOggPageHeader(); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + if (ftell($this->getid3->fp) >= $this->getid3->fread_buffer_size()) { + $info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)'; + unset($info['fileformat']); + unset($info['ogg']); + return false; + } + + $filedata = fread($this->getid3->fp, $oggpageinfo['page_length']); + $filedataoffset = 0; + + if (substr($filedata, 0, 4) == 'fLaC') { + + $info['audio']['dataformat'] = 'flac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; + + } elseif (substr($filedata, 1, 6) == 'vorbis') { + + $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); + + } elseif (substr($filedata, 0, 8) == 'Speex ') { + + // http://www.speex.org/manual/node10.html + + $info['audio']['dataformat'] = 'speex'; + $info['mime_type'] = 'audio/speex'; + $info['audio']['bitrate_mode'] = 'abr'; + $info['audio']['lossless'] = false; + + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex ' + $filedataoffset += 8; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20); + $filedataoffset += 20; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + + $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']); + $info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']; + $info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']; + $info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']; + $info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']); + + $info['audio']['sample_rate'] = $info['speex']['sample_rate']; + $info['audio']['channels'] = $info['speex']['channels']; + if ($info['speex']['vbr']) { + $info['audio']['bitrate_mode'] = 'vbr'; + } + + + } elseif (substr($filedata, 0, 8) == "fishead\x00") { + + // Ogg Skeleton version 3.0 Format Specification + // http://xiph.org/ogg/doc/skeleton.html + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + $info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20)); + $filedataoffset += 20; + + $info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor']; + $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']; + $info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']; + $info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc']; + + + $counter = 0; + do { + $oggpageinfo = $this->ParseOggPageHeader(); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo; + $filedata = fread($this->getid3->fp, $oggpageinfo['page_length']); + fseek($this->getid3->fp, $oggpageinfo['page_end_offset'], SEEK_SET); + + if (substr($filedata, 0, 8) == "fisbone\x00") { + + $filedataoffset = 8; + $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3); + $filedataoffset += 3; + + } elseif (substr($filedata, 1, 6) == 'theora') { + + $info['video']['dataformat'] = 'theora'; +$info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']'; +//break; + + } elseif (substr($filedata, 1, 6) == 'vorbis') { + + $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); + + } else { +$info['error'][] = 'unexpected'; +//break; + } + //} while ($oggpageinfo['page_seqno'] == 0); + } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00")); + fseek($this->getid3->fp, $oggpageinfo['page_start_offset'], SEEK_SET); + + +$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'; +//return false; + + + } else { + + $info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'; + unset($info['ogg']); + unset($info['mime_type']); + return false; + + } + + // Page 2 - Comment Header + $oggpageinfo = $this->ParseOggPageHeader(); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + switch ($info['audio']['dataformat']) { + case 'vorbis': + $filedata = fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' + + $this->ParseVorbisCommentsFilepointer(); + break; + + case 'flac': + $getid3_flac = new getid3_flac($this->getid3); + if (!$getid3_flac->FLACparseMETAdata()) { + $info['error'][] = 'Failed to parse FLAC headers'; + return false; + } + unset($getid3_flac); + break; + + case 'speex': + fseek($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); + $this->ParseVorbisCommentsFilepointer(); + break; + + } + + + + // Last Page - Number of Samples + + if (!getid3_lib::intValueSupported($info['avdataend'])) { + + $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'; + + } else { + + fseek($this->getid3->fp, max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0), SEEK_SET); + $LastChunkOfOgg = strrev(fread($this->getid3->fp, $this->getid3->fread_buffer_size())); + if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { + fseek($this->getid3->fp, $info['avdataend'] - ($LastOggSpostion + strlen('SggO')), SEEK_SET); + $info['avdataend'] = ftell($this->getid3->fp); + $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader(); + $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position']; + if ($info['ogg']['samples'] == 0) { + $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero'; + return false; + } + if (!empty($info['audio']['sample_rate'])) { + $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']); + } + } + + } + + if (!empty($info['ogg']['bitrate_average'])) { + $info['audio']['bitrate'] = $info['ogg']['bitrate_average']; + } elseif (!empty($info['ogg']['bitrate_nominal'])) { + $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal']; + } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) { + $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2; + } + if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) { + if ($info['audio']['bitrate'] == 0) { + $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero'; + return false; + } + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']); + } + + if (isset($info['ogg']['vendor'])) { + $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']); + + // Vorbis only + if ($info['audio']['dataformat'] == 'vorbis') { + + // Vorbis 1.0 starts with Xiph.Org + if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) { + + if ($info['audio']['bitrate_mode'] == 'abr') { + + // Set -b 128 on abr files + $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000); + + } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) { + // Set -q N on vbr files + $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']); + + } + } + + if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) { + $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps'; + } + } + } + + return true; + } + + function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { + $info = &$this->getid3->info; + $info['audio']['dataformat'] = 'vorbis'; + $info['audio']['lossless'] = false; + + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis' + $filedataoffset += 6; + $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['audio']['channels'] = $info['ogg']['numberofchannels']; + $info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + if ($info['ogg']['samplerate'] == 0) { + $info['error'][] = 'Corrupt Ogg file: sample rate == zero'; + return false; + } + $info['audio']['sample_rate'] = $info['ogg']['samplerate']; + $info['ogg']['samples'] = 0; // filled in later + $info['ogg']['bitrate_average'] = 0; // filled in later + $info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F); + $info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4); + $info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet + + $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr + if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) { + unset($info['ogg']['bitrate_max']); + $info['audio']['bitrate_mode'] = 'abr'; + } + if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) { + unset($info['ogg']['bitrate_nominal']); + } + if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) { + unset($info['ogg']['bitrate_min']); + $info['audio']['bitrate_mode'] = 'abr'; + } + return true; + } + + function ParseOggPageHeader() { + // http://xiph.org/ogg/vorbis/doc/framing.html + $oggheader['page_start_offset'] = ftell($this->getid3->fp); // where we started from in the file + + $filedata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + $filedataoffset = 0; + while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { + if ((ftell($this->getid3->fp) - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { + // should be found before here + return false; + } + if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { + if (feof($this->getid3->fp) || (($filedata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size())) === false)) { + // get some more data, unless eof, in which case fail + return false; + } + } + } + $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' + + $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet + $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) + $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) + + $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['page_length'] = 0; + for ($i = 0; $i < $oggheader['page_segments']; $i++) { + $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['page_length'] += $oggheader['segment_table'][$i]; + } + $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; + $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; + fseek($this->getid3->fp, $oggheader['header_end_offset'], SEEK_SET); + + return $oggheader; + } + + + function ParseVorbisCommentsFilepointer() { + $info = &$this->getid3->info; + + $OriginalOffset = ftell($this->getid3->fp); + $commentdataoffset = 0; + $VorbisCommentPage = 1; + + switch ($info['audio']['dataformat']) { + case 'vorbis': + $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block + fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); + $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; + $commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + + $commentdataoffset += (strlen('vorbis') + 1); + break; + + case 'flac': + $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; + fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); + $commentdata = fread($this->getid3->fp, $info['flac']['VORBIS_COMMENT']['raw']['block_length']); + break; + + case 'speex': + $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block + fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); + $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; + $commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + break; + + default: + return false; + break; + } + + $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + $commentdataoffset += 4; + + $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); + $commentdataoffset += $VendorSize; + + $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + $commentdataoffset += 4; + $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset; + + $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); + $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; + for ($i = 0; $i < $CommentsCount; $i++) { + + $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; + + if (ftell($this->getid3->fp) < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { + if ($oggpageinfo = $this->ParseOggPageHeader()) { + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + $VorbisCommentPage++; + + // First, save what we haven't read yet + $AsYetUnusedData = substr($commentdata, $commentdataoffset); + + // Then take that data off the end + $commentdata = substr($commentdata, 0, $commentdataoffset); + + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct + $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + + // Finally, stick the unused data back on the end + $commentdata .= $AsYetUnusedData; + + //$commentdata .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $commentdata .= fread($this->getid3->fp, $this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); + } + + } + $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + + // replace avdataoffset with position just after the last vorbiscomment + $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4; + + $commentdataoffset += 4; + while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) { + if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) { + $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments'; + break 2; + } + + $VorbisCommentPage++; + + $oggpageinfo = $this->ParseOggPageHeader(); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + // First, save what we haven't read yet + $AsYetUnusedData = substr($commentdata, $commentdataoffset); + + // Then take that data off the end + $commentdata = substr($commentdata, 0, $commentdataoffset); + + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct + $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + + // Finally, stick the unused data back on the end + $commentdata .= $AsYetUnusedData; + + //$commentdata .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { + $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp); + break; + } + $readlength = getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); + if ($readlength <= 0) { + $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp); + break; + } + $commentdata .= fread($this->getid3->fp, $readlength); + + //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; + } + $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset; + $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']); + $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size']; + + if (!$commentstring) { + + // no comment? + $info['warning'][] = 'Blank Ogg comment ['.$i.']'; + + } elseif (strstr($commentstring, '=')) { + + $commentexploded = explode('=', $commentstring, 2); + $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); + $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); + $ThisFileInfo_ogg_comments_raw[$i]['data'] = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); + $ThisFileInfo_ogg_comments_raw[$i]['data_length'] = strlen($ThisFileInfo_ogg_comments_raw[$i]['data']); + + if (preg_match('#^(BM|GIF|\xFF\xD8\xFF|\x89\x50\x4E\x47\x0D\x0A\x1A\x0A|II\x2A\x00|MM\x00\x2A)#s', $ThisFileInfo_ogg_comments_raw[$i]['data'])) { + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($ThisFileInfo_ogg_comments_raw[$i]['data'], $imageinfo); + unset($imageinfo); + if (!empty($imagechunkcheck)) { + $ThisFileInfo_ogg_comments_raw[$i]['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + if ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] && ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] != 'application/octet-stream')) { + unset($ThisFileInfo_ogg_comments_raw[$i]['value']); + } + } + } + + if (isset($ThisFileInfo_ogg_comments_raw[$i]['value'])) { + unset($ThisFileInfo_ogg_comments_raw[$i]['data']); + $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; + } else { + do { + if ($this->inline_attachments === false) { + // skip entirely + unset($ThisFileInfo_ogg_comments_raw[$i]['data']); + break; + } + if ($this->inline_attachments === true) { + // great + } elseif (is_int($this->inline_attachments)) { + if ($this->inline_attachments < $ThisFileInfo_ogg_comments_raw[$i]['data_length']) { + // too big, skip + $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' is too large to process inline ('.number_format($ThisFileInfo_ogg_comments_raw[$i]['data_length']).' bytes)'; + unset($ThisFileInfo_ogg_comments_raw[$i]['data']); + break; + } + } elseif (is_string($this->inline_attachments)) { + $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { + // cannot write, skip + $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; + unset($ThisFileInfo_ogg_comments_raw[$i]['data']); + break; + } + } + // if we get this far, must be OK + if (is_string($this->inline_attachments)) { + $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$ThisFileInfo_ogg_comments_raw[$i]['offset']; + if (!file_exists($destination_filename) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $ThisFileInfo_ogg_comments_raw[$i]['data']); + } else { + $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $ThisFileInfo_ogg_comments_raw[$i]['data_filename'] = $destination_filename; + unset($ThisFileInfo_ogg_comments_raw[$i]['data']); + } else { + $info['ogg']['comments']['picture'][] = array('data'=>$ThisFileInfo_ogg_comments_raw[$i]['data'], 'image_mime'=>$ThisFileInfo_ogg_comments_raw[$i]['image_mime']); + } + } while (false); + + } + + + } else { + + $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring; + + } + } + + + // Replay Gain Adjustment + // http://privatewww.essex.ac.uk/~djmrob/replaygain/ + if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) { + foreach ($info['ogg']['comments'] as $index => $commentvalue) { + switch ($index) { + case 'rg_audiophile': + case 'replaygain_album_gain': + $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + case 'rg_radio': + case 'replaygain_track_gain': + $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + case 'replaygain_album_peak': + $info['replay_gain']['album']['peak'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + case 'rg_peak': + case 'replaygain_track_peak': + $info['replay_gain']['track']['peak'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + case 'replaygain_reference_loudness': + $info['replay_gain']['reference_volume'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + default: + // do nothing + break; + } + } + } + + fseek($this->getid3->fp, $OriginalOffset, SEEK_SET); + + return true; + } + + static function SpeexBandModeLookup($mode) { + static $SpeexBandModeLookup = array(); + if (empty($SpeexBandModeLookup)) { + $SpeexBandModeLookup[0] = 'narrow'; + $SpeexBandModeLookup[1] = 'wide'; + $SpeexBandModeLookup[2] = 'ultra-wide'; + } + return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); + } + + + static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { + for ($i = 0; $i < $SegmentNumber; $i++) { + $segmentlength = 0; + foreach ($OggInfoArray['segment_table'] as $key => $value) { + $segmentlength += $value; + if ($value < 255) { + break; + } + } + } + return $segmentlength; + } + + + static function get_quality_from_nominal_bitrate($nominal_bitrate) { + + // decrease precision + $nominal_bitrate = $nominal_bitrate / 1000; + + if ($nominal_bitrate < 128) { + // q-1 to q4 + $qval = ($nominal_bitrate - 64) / 16; + } elseif ($nominal_bitrate < 256) { + // q4 to q8 + $qval = $nominal_bitrate / 32; + } elseif ($nominal_bitrate < 320) { + // q8 to q9 + $qval = ($nominal_bitrate + 256) / 64; + } else { + // q9 to q10 + $qval = ($nominal_bitrate + 1300) / 180; + } + //return $qval; // 5.031324 + //return intval($qval); // 5 + return round($qval, 1); // 5 or 4.9 + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.optimfrog.php b/3rdparty/getid3/module.audio.optimfrog.php similarity index 65% rename from apps/media/getID3/getid3/module.audio.optimfrog.php rename to 3rdparty/getid3/module.audio.optimfrog.php index 3c2dfb0bd2..c1c8963837 100644 --- a/apps/media/getID3/getid3/module.audio.optimfrog.php +++ b/3rdparty/getid3/module.audio.optimfrog.php @@ -15,39 +15,42 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); -class getid3_optimfrog +class getid3_optimfrog extends getid3_handler { - function getid3_optimfrog(&$fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'ofr'; - $ThisFileInfo['audio']['dataformat'] = 'ofr'; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; - $ThisFileInfo['audio']['lossless'] = true; + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $OFRheader = fread($fd, 8); + $info['fileformat'] = 'ofr'; + $info['audio']['dataformat'] = 'ofr'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $OFRheader = fread($this->getid3->fp, 8); if (substr($OFRheader, 0, 5) == '*RIFF') { - return $this->ParseOptimFROGheader42($fd, $ThisFileInfo); + return $this->ParseOptimFROGheader42(); } elseif (substr($OFRheader, 0, 3) == 'OFR') { - return $this->ParseOptimFROGheader45($fd, $ThisFileInfo); + return $this->ParseOptimFROGheader45(); } - $ThisFileInfo['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$ThisFileInfo['avdataoffset'].', found "'.$OFRheader.'"'; - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($OFRheader).'"'; + unset($info['fileformat']); return false; } - function ParseOptimFROGheader42(&$fd, &$ThisFileInfo) { + function ParseOptimFROGheader42() { // for fileformat of v4.21 and older - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $OptimFROGheaderData = fread($fd, 45); - $ThisFileInfo['avdataoffset'] = 45; + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $OptimFROGheaderData = fread($this->getid3->fp, 45); + $info['avdataoffset'] = 45; $OptimFROGencoderVersion_raw = getid3_lib::LittleEndian2Int(substr($OptimFROGheaderData, 0, 1)); $OptimFROGencoderVersion_major = floor($OptimFROGencoderVersion_raw / 10); @@ -57,36 +60,46 @@ class getid3_optimfrog $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { - $ThisFileInfo['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); - fseek($fd, $ThisFileInfo['avdataend'], SEEK_SET); - $RIFFdata .= fread($fd, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); + $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); + fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); + $RIFFdata .= fread($this->getid3->fp, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); } // move the data chunk after all other chunks (if any) // so that the RIFF parser doesn't see EOF when trying // to skip over the data chunk $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); - getid3_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); - $ThisFileInfo['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor; - $ThisFileInfo['audio']['channels'] = $ThisFileInfo['riff']['audio'][0]['channels']; - $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['riff']['audio'][0]['sample_rate']; - $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['riff']['audio'][0]['bits_per_sample']; - $ThisFileInfo['playtime_seconds'] = $OrignalRIFFdataSize / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate'] * ($ThisFileInfo['audio']['bits_per_sample'] / 8)); - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->ParseRIFFdata($RIFFdata); + $info['riff'] = $getid3_temp->info['riff']; + + $info['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor; + $info['audio']['channels'] = $info['riff']['audio'][0]['channels']; + $info['audio']['sample_rate'] = $info['riff']['audio'][0]['sample_rate']; + $info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample']; + $info['playtime_seconds'] = $OrignalRIFFdataSize / ($info['audio']['channels'] * $info['audio']['sample_rate'] * ($info['audio']['bits_per_sample'] / 8)); + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + + unset($getid3_riff, $getid3_temp, $RIFFdata); return true; } - function ParseOptimFROGheader45(&$fd, &$ThisFileInfo) { + function ParseOptimFROGheader45() { // for fileformat of v4.50a and higher + $info = &$this->getid3->info; $RIFFdata = ''; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - while (!feof($fd) && (ftell($fd) < $ThisFileInfo['avdataend'])) { - $BlockOffset = ftell($fd); - $BlockData = fread($fd, 8); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < $info['avdataend'])) { + $BlockOffset = ftell($this->getid3->fp); + $BlockData = fread($this->getid3->fp, 8); $offset = 8; $BlockName = substr($BlockData, 0, 4); $BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4)); @@ -94,10 +107,10 @@ class getid3_optimfrog if ($BlockName == 'OFRX') { $BlockName = 'OFR '; } - if (!isset($ThisFileInfo['ofr'][$BlockName])) { - $ThisFileInfo['ofr'][$BlockName] = array(); + if (!isset($info['ofr'][$BlockName])) { + $info['ofr'][$BlockName] = array(); } - $thisfile_ofr_thisblock = &$ThisFileInfo['ofr'][$BlockName]; + $thisfile_ofr_thisblock = &$info['ofr'][$BlockName]; switch ($BlockName) { case 'OFR ': @@ -106,7 +119,7 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $ThisFileInfo['audio']['encoder'] = 'OptimFROG 4.50 alpha'; + $info['audio']['encoder'] = 'OptimFROG 4.50 alpha'; switch ($BlockSize) { case 12: case 15: @@ -114,10 +127,10 @@ class getid3_optimfrog break; default: - $ThisFileInfo['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)'; + $info['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)'; break; } - $BlockData .= fread($fd, $BlockSize); + $BlockData .= fread($this->getid3->fp, $BlockSize); $thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6)); $offset += 6; @@ -142,23 +155,23 @@ class getid3_optimfrog $thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']); $offset += 1; - $ThisFileInfo['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder']; - $ThisFileInfo['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression']; + $info['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder']; + $info['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression']; if ((($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xF0) >> 4) == 7) { // v4.507 - if (strtolower(getid3_lib::fileextension($ThisFileInfo['filename'])) == 'ofs') { + if (strtolower(getid3_lib::fileextension($info['filename'])) == 'ofs') { // OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference // between lossless and lossy other than the file extension. - $ThisFileInfo['audio']['dataformat'] = 'ofs'; - $ThisFileInfo['audio']['lossless'] = true; + $info['audio']['dataformat'] = 'ofs'; + $info['audio']['lossless'] = true; } } } - $ThisFileInfo['audio']['channels'] = $thisfile_ofr_thisblock['channels']; - $ThisFileInfo['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate']; - $ThisFileInfo['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); + $info['audio']['channels'] = $thisfile_ofr_thisblock['channels']; + $info['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate']; + $info['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); break; @@ -168,13 +181,13 @@ class getid3_optimfrog $COMPdata['offset'] = $BlockOffset; $COMPdata['size'] = $BlockSize; - if ($ThisFileInfo['avdataoffset'] == 0) { - $ThisFileInfo['avdataoffset'] = $BlockOffset; + if ($info['avdataoffset'] == 0) { + $info['avdataoffset'] = $BlockOffset; } // Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data - $BlockData .= fread($fd, 14); - fseek($fd, $BlockSize - 14, SEEK_CUR); + $BlockData .= fread($this->getid3->fp, 14); + fseek($this->getid3->fp, $BlockSize - 14, SEEK_CUR); $COMPdata['crc_32'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); $offset += 4; @@ -190,7 +203,7 @@ class getid3_optimfrog //$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']); $offset += 2; - if ($ThisFileInfo['ofr']['OFR ']['size'] > 12) { + if ($info['ofr']['OFR ']['size'] > 12) { // OFR 4.504b or higher $COMPdata['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); @@ -211,7 +224,7 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $RIFFdata .= fread($fd, $BlockSize); + $RIFFdata .= fread($this->getid3->fp, $BlockSize); break; case 'TAIL': @@ -219,7 +232,7 @@ class getid3_optimfrog $thisfile_ofr_thisblock['size'] = $BlockSize; if ($BlockSize > 0) { - $RIFFdata .= fread($fd, $BlockSize); + $RIFFdata .= fread($this->getid3->fp, $BlockSize); } break; @@ -229,7 +242,7 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - fseek($fd, $BlockSize, SEEK_CUR); + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; @@ -238,9 +251,9 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $ThisFileInfo['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.GETID3_VERSION.') of getID3()'; + $info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.$this->getid3->version().') of getID3()'; - fseek($fd, $BlockSize, SEEK_CUR); + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; @@ -252,14 +265,14 @@ class getid3_optimfrog if ($BlockSize == 16) { - $thisfile_ofr_thisblock['md5_binary'] = fread($fd, $BlockSize); + $thisfile_ofr_thisblock['md5_binary'] = fread($this->getid3->fp, $BlockSize); $thisfile_ofr_thisblock['md5_string'] = getid3_lib::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false); - $ThisFileInfo['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; + $info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; } else { - $ThisFileInfo['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead'; - fseek($fd, $BlockSize, SEEK_CUR); + $info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead'; + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); } break; @@ -269,29 +282,38 @@ class getid3_optimfrog $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $ThisFileInfo['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset']; - fseek($fd, $BlockSize, SEEK_CUR); + $info['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset']; + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; } } - if (isset($ThisFileInfo['ofr']['TAIL']['offset'])) { - $ThisFileInfo['avdataend'] = $ThisFileInfo['ofr']['TAIL']['offset']; + if (isset($info['ofr']['TAIL']['offset'])) { + $info['avdataend'] = $info['ofr']['TAIL']['offset']; } - $ThisFileInfo['playtime_seconds'] = (float) $ThisFileInfo['ofr']['OFR ']['total_samples'] / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate']); - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $info['playtime_seconds'] = (float) $info['ofr']['OFR ']['total_samples'] / ($info['audio']['channels'] * $info['audio']['sample_rate']); + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; // move the data chunk after all other chunks (if any) // so that the RIFF parser doesn't see EOF when trying // to skip over the data chunk $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); - getid3_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->ParseRIFFdata($RIFFdata); + $info['riff'] = $getid3_temp->info['riff']; + + unset($getid3_riff, $getid3_temp, $RIFFdata); return true; } - function OptimFROGsampleTypeLookup($SampleType) { + static function OptimFROGsampleTypeLookup($SampleType) { static $OptimFROGsampleTypeLookup = array( 0 => 'unsigned int (8-bit)', 1 => 'signed int (8-bit)', @@ -308,7 +330,7 @@ class getid3_optimfrog return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false); } - function OptimFROGbitsPerSampleTypeLookup($SampleType) { + static function OptimFROGbitsPerSampleTypeLookup($SampleType) { static $OptimFROGbitsPerSampleTypeLookup = array( 0 => 8, 1 => 8, @@ -325,7 +347,7 @@ class getid3_optimfrog return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false); } - function OptimFROGchannelConfigurationLookup($ChannelConfiguration) { + static function OptimFROGchannelConfigurationLookup($ChannelConfiguration) { static $OptimFROGchannelConfigurationLookup = array( 0 => 'mono', 1 => 'stereo' @@ -333,7 +355,7 @@ class getid3_optimfrog return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false); } - function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) { + static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) { static $OptimFROGchannelConfigNumChannelsLookup = array( 0 => 1, 1 => 2 @@ -343,13 +365,13 @@ class getid3_optimfrog - // function OptimFROGalgorithmNameLookup($AlgorithID) { + // static function OptimFROGalgorithmNameLookup($AlgorithID) { // static $OptimFROGalgorithmNameLookup = array(); // return (isset($OptimFROGalgorithmNameLookup[$AlgorithID]) ? $OptimFROGalgorithmNameLookup[$AlgorithID] : false); // } - function OptimFROGencoderNameLookup($EncoderID) { + static function OptimFROGencoderNameLookup($EncoderID) { // version = (encoderID >> 4) + 4500 // system = encoderID & 0xF @@ -364,7 +386,7 @@ class getid3_optimfrog return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')'; } - function OptimFROGcompressionLookup($CompressionID) { + static function OptimFROGcompressionLookup($CompressionID) { // mode = compression >> 3 // speedup = compression & 0x07 @@ -386,7 +408,7 @@ class getid3_optimfrog return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')'); } - function OptimFROGspeedupLookup($CompressionID) { + static function OptimFROGspeedupLookup($CompressionID) { // mode = compression >> 3 // speedup = compression & 0x07 @@ -398,7 +420,6 @@ class getid3_optimfrog 0x01 => '2x', 0x02 => '4x' ); - return (isset($OptimFROGencoderSpeedupLookup[$CompressionSpeedupID]) ? $OptimFROGencoderSpeedupLookup[$CompressionSpeedupID] : 'undefined mode (0x'.dechex($CompressionSpeedupID)); } diff --git a/3rdparty/getid3/module.audio.rkau.php b/3rdparty/getid3/module.audio.rkau.php new file mode 100644 index 0000000000..c442076275 --- /dev/null +++ b/3rdparty/getid3/module.audio.rkau.php @@ -0,0 +1,94 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.shorten.php // +// module for analyzing Shorten Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_rkau extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $RKAUHeader = fread($this->getid3->fp, 20); + $magic = 'RKA'; + if (substr($RKAUHeader, 0, 3) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($RKAUHeader, 0, 3)).'"'; + return false; + } + + $info['fileformat'] = 'rkau'; + $info['audio']['dataformat'] = 'rkau'; + $info['audio']['bitrate_mode'] = 'vbr'; + + $info['rkau']['raw']['version'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 3, 1)); + $info['rkau']['version'] = '1.'.str_pad($info['rkau']['raw']['version'] & 0x0F, 2, '0', STR_PAD_LEFT); + if (($info['rkau']['version'] > 1.07) || ($info['rkau']['version'] < 1.06)) { + $info['error'][] = 'This version of getID3() ['.$this->getid3->version().'] can only parse RKAU files v1.06 and 1.07 (this file is v'.$info['rkau']['version'].')'; + unset($info['rkau']); + return false; + } + + $info['rkau']['source_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 4, 4)); + $info['rkau']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 8, 4)); + $info['rkau']['channels'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 12, 1)); + $info['rkau']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 13, 1)); + + $info['rkau']['raw']['quality'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 14, 1)); + $this->RKAUqualityLookup($info['rkau']); + + $info['rkau']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 15, 1)); + $info['rkau']['flags']['joint_stereo'] = (bool) (!($info['rkau']['raw']['flags'] & 0x01)); + $info['rkau']['flags']['streaming'] = (bool) ($info['rkau']['raw']['flags'] & 0x02); + $info['rkau']['flags']['vrq_lossy_mode'] = (bool) ($info['rkau']['raw']['flags'] & 0x04); + + if ($info['rkau']['flags']['streaming']) { + $info['avdataoffset'] += 20; + $info['rkau']['compressed_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 16, 4)); + } else { + $info['avdataoffset'] += 16; + $info['rkau']['compressed_bytes'] = $info['avdataend'] - $info['avdataoffset'] - 1; + } + // Note: compressed_bytes does not always equal what appears to be the actual number of compressed bytes, + // sometimes it's more, sometimes less. No idea why(?) + + $info['audio']['lossless'] = $info['rkau']['lossless']; + $info['audio']['channels'] = $info['rkau']['channels']; + $info['audio']['bits_per_sample'] = $info['rkau']['bits_per_sample']; + $info['audio']['sample_rate'] = $info['rkau']['sample_rate']; + + $info['playtime_seconds'] = $info['rkau']['source_bytes'] / ($info['rkau']['sample_rate'] * $info['rkau']['channels'] * ($info['rkau']['bits_per_sample'] / 8)); + $info['audio']['bitrate'] = ($info['rkau']['compressed_bytes'] * 8) / $info['playtime_seconds']; + + return true; + + } + + + function RKAUqualityLookup(&$RKAUdata) { + $level = ($RKAUdata['raw']['quality'] & 0xF0) >> 4; + $quality = $RKAUdata['raw']['quality'] & 0x0F; + + $RKAUdata['lossless'] = (($quality == 0) ? true : false); + $RKAUdata['compression_level'] = $level + 1; + if (!$RKAUdata['lossless']) { + $RKAUdata['quality_setting'] = $quality; + } + + return true; + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.shorten.php b/3rdparty/getid3/module.audio.shorten.php similarity index 54% rename from apps/media/getID3/getid3/module.audio.shorten.php rename to 3rdparty/getid3/module.audio.shorten.php index a9eb1ab1cc..7b3d312ea2 100644 --- a/apps/media/getID3/getid3/module.audio.shorten.php +++ b/3rdparty/getid3/module.audio.shorten.php @@ -14,36 +14,39 @@ ///////////////////////////////////////////////////////////////// -class getid3_shorten +class getid3_shorten extends getid3_handler { - function getid3_shorten(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $ShortenHeader = fread($fd, 8); - if (substr($ShortenHeader, 0, 4) != 'ajkg') { - $ThisFileInfo['error'][] = 'Expecting "ajkg" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($ShortenHeader, 0, 4).'"'; + $ShortenHeader = fread($this->getid3->fp, 8); + $magic = 'ajkg'; + if (substr($ShortenHeader, 0, 4) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($ShortenHeader, 0, 4)).'"'; return false; } - $ThisFileInfo['fileformat'] = 'shn'; - $ThisFileInfo['audio']['dataformat'] = 'shn'; - $ThisFileInfo['audio']['lossless'] = true; - $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $info['fileformat'] = 'shn'; + $info['audio']['dataformat'] = 'shn'; + $info['audio']['lossless'] = true; + $info['audio']['bitrate_mode'] = 'vbr'; - $ThisFileInfo['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1)); + $info['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1)); - fseek($fd, $ThisFileInfo['avdataend'] - 12, SEEK_SET); - $SeekTableSignatureTest = fread($fd, 12); - $ThisFileInfo['shn']['seektable']['present'] = (bool) (substr($SeekTableSignatureTest, 4, 8) == 'SHNAMPSK'); - if ($ThisFileInfo['shn']['seektable']['present']) { - $ThisFileInfo['shn']['seektable']['length'] = getid3_lib::LittleEndian2Int(substr($SeekTableSignatureTest, 0, 4)); - $ThisFileInfo['shn']['seektable']['offset'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['shn']['seektable']['length']; - fseek($fd, $ThisFileInfo['shn']['seektable']['offset'], SEEK_SET); - $SeekTableMagic = fread($fd, 4); - if ($SeekTableMagic != 'SEEK') { + fseek($this->getid3->fp, $info['avdataend'] - 12, SEEK_SET); + $SeekTableSignatureTest = fread($this->getid3->fp, 12); + $info['shn']['seektable']['present'] = (bool) (substr($SeekTableSignatureTest, 4, 8) == 'SHNAMPSK'); + if ($info['shn']['seektable']['present']) { + $info['shn']['seektable']['length'] = getid3_lib::LittleEndian2Int(substr($SeekTableSignatureTest, 0, 4)); + $info['shn']['seektable']['offset'] = $info['avdataend'] - $info['shn']['seektable']['length']; + fseek($this->getid3->fp, $info['shn']['seektable']['offset'], SEEK_SET); + $SeekTableMagic = fread($this->getid3->fp, 4); + $magic = 'SEEK'; + if ($SeekTableMagic != $magic) { - $ThisFileInfo['error'][] = 'Expecting "SEEK" at offset '.$ThisFileInfo['shn']['seektable']['offset'].', found "'.$SeekTableMagic.'"'; + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['shn']['seektable']['offset'].', found "'.getid3_lib::PrintHexBytes($SeekTableMagic).'"'; return false; } else { @@ -64,11 +67,11 @@ class getid3_shorten // long Offset1[4]; // }TSeekEntry; - $SeekTableData = fread($fd, $ThisFileInfo['shn']['seektable']['length'] - 16); - $ThisFileInfo['shn']['seektable']['entry_count'] = floor(strlen($SeekTableData) / 80); - //$ThisFileInfo['shn']['seektable']['entries'] = array(); + $SeekTableData = fread($this->getid3->fp, $info['shn']['seektable']['length'] - 16); + $info['shn']['seektable']['entry_count'] = floor(strlen($SeekTableData) / 80); + //$info['shn']['seektable']['entries'] = array(); //$SeekTableOffset = 0; - //for ($i = 0; $i < $ThisFileInfo['shn']['seektable']['entry_count']; $i++) { + //for ($i = 0; $i < $info['shn']['seektable']['entry_count']; $i++) { // $SeekTableEntry['sample_number'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); // $SeekTableOffset += 4; // $SeekTableEntry['shn_file_byte_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); @@ -101,16 +104,16 @@ class getid3_shorten // $SeekTableEntry['offset1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); // $SeekTableOffset += 4; // } - // - // $ThisFileInfo['shn']['seektable']['entries'][] = $SeekTableEntry; + // + // $info['shn']['seektable']['entries'][] = $SeekTableEntry; //} } } - if ((bool) ini_get('safe_mode')) { - $ThisFileInfo['error'][] = 'PHP running in Safe Mode - backtick operator not available, cannot run shntool to analyze Shorten files'; + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + $info['error'][] = 'PHP running in Safe Mode - backtick operator not available, cannot run shntool to analyze Shorten files'; return false; } @@ -119,24 +122,24 @@ class getid3_shorten $RequiredFiles = array('shorten.exe', 'cygwin1.dll', 'head.exe'); foreach ($RequiredFiles as $required_file) { if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { - $ThisFileInfo['error'][] = GETID3_HELPERAPPSDIR.$required_file.' does not exist'; + $info['error'][] = GETID3_HELPERAPPSDIR.$required_file.' does not exist'; return false; } } - $commandline = GETID3_HELPERAPPSDIR.'shorten.exe -x "'.$ThisFileInfo['filenamepath'].'" - | '.GETID3_HELPERAPPSDIR.'head.exe -c 64'; + $commandline = GETID3_HELPERAPPSDIR.'shorten.exe -x "'.$info['filenamepath'].'" - | '.GETID3_HELPERAPPSDIR.'head.exe -c 64'; $commandline = str_replace('/', '\\', $commandline); } else { - static $shorten_present; - if (!isset($shorten_present)) { - $shorten_present = file_exists('/usr/local/bin/shorten') || `which shorten`; - } - if (!$shorten_present) { - $ThisFileInfo['error'][] = 'shorten binary was not found in path or /usr/local/bin'; - return false; - } - $commandline = (file_exists('/usr/local/bin/shorten') ? '/usr/local/bin/' : '' ) . 'shorten -x '.escapeshellarg($ThisFileInfo['filenamepath']).' - | head -c 64'; + static $shorten_present; + if (!isset($shorten_present)) { + $shorten_present = file_exists('/usr/local/bin/shorten') || `which shorten`; + } + if (!$shorten_present) { + $info['error'][] = 'shorten binary was not found in path or /usr/local/bin'; + return false; + } + $commandline = (file_exists('/usr/local/bin/shorten') ? '/usr/local/bin/' : '' ) . 'shorten -x '.escapeshellarg($info['filenamepath']).' - | head -c 64'; } @@ -148,26 +151,26 @@ class getid3_shorten $fmt_size = getid3_lib::LittleEndian2Int(substr($output, 16, 4)); $DecodedWAVFORMATEX = getid3_riff::RIFFparseWAVEFORMATex(substr($output, 20, $fmt_size)); - $ThisFileInfo['audio']['channels'] = $DecodedWAVFORMATEX['channels']; - $ThisFileInfo['audio']['bits_per_sample'] = $DecodedWAVFORMATEX['bits_per_sample']; - $ThisFileInfo['audio']['sample_rate'] = $DecodedWAVFORMATEX['sample_rate']; + $info['audio']['channels'] = $DecodedWAVFORMATEX['channels']; + $info['audio']['bits_per_sample'] = $DecodedWAVFORMATEX['bits_per_sample']; + $info['audio']['sample_rate'] = $DecodedWAVFORMATEX['sample_rate']; if (substr($output, 20 + $fmt_size, 4) == 'data') { - $ThisFileInfo['playtime_seconds'] = getid3_lib::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec']; + $info['playtime_seconds'] = getid3_lib::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec']; } else { - $ThisFileInfo['error'][] = 'shorten failed to decode DATA chunk to expected location, cannot determine playtime'; + $info['error'][] = 'shorten failed to decode DATA chunk to expected location, cannot determine playtime'; return false; } - $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $ThisFileInfo['playtime_seconds']) * 8; + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8; } else { - $ThisFileInfo['error'][] = 'shorten failed to decode file to WAV for parsing'; + $info['error'][] = 'shorten failed to decode file to WAV for parsing'; return false; } diff --git a/3rdparty/getid3/module.audio.tta.php b/3rdparty/getid3/module.audio.tta.php new file mode 100644 index 0000000000..1c646ee676 --- /dev/null +++ b/3rdparty/getid3/module.audio.tta.php @@ -0,0 +1,109 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.tta.php // +// module for analyzing TTA Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_tta extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'tta'; + $info['audio']['dataformat'] = 'tta'; + $info['audio']['lossless'] = true; + $info['audio']['bitrate_mode'] = 'vbr'; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $ttaheader = fread($this->getid3->fp, 26); + + $info['tta']['magic'] = substr($ttaheader, 0, 3); + $magic = 'TTA'; + if ($info['tta']['magic'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['tta']['magic']).'"'; + unset($info['fileformat']); + unset($info['audio']); + unset($info['tta']); + return false; + } + + switch ($ttaheader{3}) { + case "\x01": // TTA v1.x + case "\x02": // TTA v1.x + case "\x03": // TTA v1.x + // "It was the demo-version of the TTA encoder. There is no released format with such header. TTA encoder v1 is not supported about a year." + $info['tta']['major_version'] = 1; + $info['avdataoffset'] += 16; + + $info['tta']['compression_level'] = ord($ttaheader{3}); + $info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); + $info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2)); + $info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 4)); + $info['tta']['samples_per_channel'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4)); + + $info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level']; + $info['playtime_seconds'] = $info['tta']['samples_per_channel'] / $info['tta']['sample_rate']; + break; + + case '2': // TTA v2.x + // "I have hurried to release the TTA 2.0 encoder. Format documentation is removed from our site. This format still in development. Please wait the TTA2 format, encoder v4." + $info['tta']['major_version'] = 2; + $info['avdataoffset'] += 20; + + $info['tta']['compression_level'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); + $info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2)); + $info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2)); + $info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 2)); + $info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4)); + $info['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 16, 4)); + + $info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level']; + $info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate']; + break; + + case '1': // TTA v3.x + // "This is a first stable release of the TTA format. It will be supported by the encoders v3 or higher." + $info['tta']['major_version'] = 3; + $info['avdataoffset'] += 26; + + $info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); // getid3_riff::RIFFwFormatTagLookup() + $info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2)); + $info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2)); + $info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 4)); + $info['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 14, 4)); + $info['tta']['crc32_footer'] = substr($ttaheader, 18, 4); + $info['tta']['seek_point'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 22, 4)); + + $info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate']; + break; + + default: + $info['error'][] = 'This version of getID3() ['.$this->getid3->version().'] only knows how to handle TTA v1 and v2 - it may not work correctly with this file which appears to be TTA v'.$ttaheader{3}; + return false; + break; + } + + $info['audio']['encoder'] = 'TTA v'.$info['tta']['major_version']; + $info['audio']['bits_per_sample'] = $info['tta']['bits_per_sample']; + $info['audio']['sample_rate'] = $info['tta']['sample_rate']; + $info['audio']['channels'] = $info['tta']['channels']; + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + + return true; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.audio.voc.php b/3rdparty/getid3/module.audio.voc.php similarity index 77% rename from apps/media/getID3/getid3/module.audio.voc.php rename to 3rdparty/getid3/module.audio.voc.php index e93b44fa61..1186a4cc03 100644 --- a/apps/media/getID3/getid3/module.audio.voc.php +++ b/3rdparty/getid3/module.audio.voc.php @@ -14,26 +14,28 @@ ///////////////////////////////////////////////////////////////// -class getid3_voc +class getid3_voc extends getid3_handler { - function getid3_voc(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $OriginalAVdataOffset = $ThisFileInfo['avdataoffset']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $VOCheader = fread($fd, 26); + $OriginalAVdataOffset = $info['avdataoffset']; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $VOCheader = fread($this->getid3->fp, 26); - if (substr($VOCheader, 0, 19) != 'Creative Voice File') { - $ThisFileInfo['error'][] = 'Expecting "Creative Voice File" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($VOCheader, 0, 19).'"'; + $magic = 'Creative Voice File'; + if (substr($VOCheader, 0, 19) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($VOCheader, 0, 19)).'"'; return false; } // shortcuts - $thisfile_audio = &$ThisFileInfo['audio']; - $ThisFileInfo['voc'] = array(); - $thisfile_voc = &$ThisFileInfo['voc']; + $thisfile_audio = &$info['audio']; + $info['voc'] = array(); + $thisfile_voc = &$info['voc']; - $ThisFileInfo['fileformat'] = 'voc'; + $info['fileformat'] = 'voc'; $thisfile_audio['dataformat'] = 'voc'; $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio['lossless'] = true; @@ -54,24 +56,24 @@ class getid3_voc do { - $BlockOffset = ftell($fd); - $BlockData = fread($fd, 4); + $BlockOffset = ftell($this->getid3->fp); + $BlockData = fread($this->getid3->fp, 4); $BlockType = ord($BlockData{0}); $BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 1, 3)); $ThisBlock = array(); - @$thisfile_voc['blocktypes'][$BlockType]++; + getid3_lib::safe_inc($thisfile_voc['blocktypes'][$BlockType], 1); switch ($BlockType) { case 0: // Terminator // do nothing, we'll break out of the loop down below break; case 1: // Sound data - $BlockData .= fread($fd, 2); - if ($ThisFileInfo['avdataoffset'] <= $OriginalAVdataOffset) { - $ThisFileInfo['avdataoffset'] = ftell($fd); + $BlockData .= fread($this->getid3->fp, 2); + if ($info['avdataoffset'] <= $OriginalAVdataOffset) { + $info['avdataoffset'] = ftell($this->getid3->fp); } - fseek($fd, $BlockSize - 2, SEEK_CUR); + fseek($this->getid3->fp, $BlockSize - 2, SEEK_CUR); $ThisBlock['sample_rate_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 1)); $ThisBlock['compression_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, 5, 1)); @@ -94,11 +96,11 @@ class getid3_voc case 6: // Repeat case 7: // End repeat // nothing useful, just skip - fseek($fd, $BlockSize, SEEK_CUR); + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; case 8: // Extended - $BlockData .= fread($fd, 4); + $BlockData .= fread($this->getid3->fp, 4); //00-01 Time Constant: // Mono: 65536 - (256000000 / sample_rate) @@ -112,11 +114,11 @@ class getid3_voc break; case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit - $BlockData .= fread($fd, 12); - if ($ThisFileInfo['avdataoffset'] <= $OriginalAVdataOffset) { - $ThisFileInfo['avdataoffset'] = ftell($fd); + $BlockData .= fread($this->getid3->fp, 12); + if ($info['avdataoffset'] <= $OriginalAVdataOffset) { + $info['avdataoffset'] = ftell($this->getid3->fp); } - fseek($fd, $BlockSize - 12, SEEK_CUR); + fseek($this->getid3->fp, $BlockSize - 12, SEEK_CUR); $ThisBlock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4)); $ThisBlock['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($BlockData, 8, 1)); @@ -134,8 +136,8 @@ class getid3_voc break; default: - $ThisFileInfo['warning'][] = 'Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset; - fseek($fd, $BlockSize, SEEK_CUR); + $info['warning'][] = 'Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset; + fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; } @@ -146,16 +148,16 @@ class getid3_voc $thisfile_voc['blocks'][] = $ThisBlock; } - } while (!feof($fd) && ($BlockType != 0)); + } while (!feof($this->getid3->fp) && ($BlockType != 0)); // Terminator block doesn't have size field, so seek back 3 spaces - fseek($fd, -3, SEEK_CUR); + fseek($this->getid3->fp, -3, SEEK_CUR); ksort($thisfile_voc['blocktypes']); if (!empty($thisfile_voc['compressed_bits_per_sample'])) { - $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']); - $thisfile_audio['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']); + $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; } return true; diff --git a/apps/media/getID3/getid3/module.audio.vqf.php b/3rdparty/getid3/module.audio.vqf.php similarity index 55% rename from apps/media/getID3/getid3/module.audio.vqf.php rename to 3rdparty/getid3/module.audio.vqf.php index 49d4e8510e..dc6ff5ecc4 100644 --- a/apps/media/getID3/getid3/module.audio.vqf.php +++ b/3rdparty/getid3/module.audio.vqf.php @@ -14,58 +14,61 @@ ///////////////////////////////////////////////////////////////// -class getid3_vqf +class getid3_vqf extends getid3_handler { - function getid3_vqf(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; + // based loosely on code from TTwinVQ by Jurgen Faul // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html - $ThisFileInfo['fileformat'] = 'vqf'; - $ThisFileInfo['audio']['dataformat'] = 'vqf'; - $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; - $ThisFileInfo['audio']['lossless'] = false; + $info['fileformat'] = 'vqf'; + $info['audio']['dataformat'] = 'vqf'; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; // shortcut - $ThisFileInfo['vqf']['raw'] = array(); - $thisfile_vqf = &$ThisFileInfo['vqf']; + $info['vqf']['raw'] = array(); + $thisfile_vqf = &$info['vqf']; $thisfile_vqf_raw = &$thisfile_vqf['raw']; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $VQFheaderData = fread($fd, 16); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $VQFheaderData = fread($this->getid3->fp, 16); $offset = 0; - $thisfile_vqf_raw['header_tag'] = substr($VQFheaderData, $offset, 4); - if ($thisfile_vqf_raw['header_tag'] != 'TWIN') { - $ThisFileInfo['error'][] = 'Expecting "TWIN" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_vqf_raw['header_tag'].'"'; - unset($ThisFileInfo['vqf']); - unset($ThisFileInfo['fileformat']); + $thisfile_vqf_raw['header_tag'] = substr($VQFheaderData, $offset, 4); + $magic = 'TWIN'; + if ($thisfile_vqf_raw['header_tag'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_vqf_raw['header_tag']).'"'; + unset($info['vqf']); + unset($info['fileformat']); return false; } $offset += 4; - $thisfile_vqf_raw['version'] = substr($VQFheaderData, $offset, 8); + $thisfile_vqf_raw['version'] = substr($VQFheaderData, $offset, 8); $offset += 8; - $thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4)); + $thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4)); $offset += 4; - while (ftell($fd) < $ThisFileInfo['avdataend']) { + while (ftell($this->getid3->fp) < $info['avdataend']) { - $ChunkBaseOffset = ftell($fd); + $ChunkBaseOffset = ftell($this->getid3->fp); $chunkoffset = 0; - $ChunkData = fread($fd, 8); + $ChunkData = fread($this->getid3->fp, 8); $ChunkName = substr($ChunkData, $chunkoffset, 4); if ($ChunkName == 'DATA') { - $ThisFileInfo['avdataoffset'] = $ChunkBaseOffset; + $info['avdataoffset'] = $ChunkBaseOffset; break; } $chunkoffset += 4; $ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; - if ($ChunkSize > ($ThisFileInfo['avdataend'] - ftell($fd))) { - $ThisFileInfo['error'][] = 'Invalid chunk size ('.$ChunkSize.') for chunk "'.$ChunkName.'" at offset '.$ChunkBaseOffset; + if ($ChunkSize > ($info['avdataend'] - ftell($this->getid3->fp))) { + $info['error'][] = 'Invalid chunk size ('.$ChunkSize.') for chunk "'.$ChunkName.'" at offset '.$ChunkBaseOffset; break; } if ($ChunkSize > 0) { - $ChunkData .= fread($fd, $ChunkSize); + $ChunkData .= fread($this->getid3->fp, $ChunkSize); } switch ($ChunkName) { @@ -83,13 +86,13 @@ class getid3_vqf $thisfile_vqf_COMM['security_level'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; - $ThisFileInfo['audio']['channels'] = $thisfile_vqf_COMM['channel_mode'] + 1; - $ThisFileInfo['audio']['sample_rate'] = $this->VQFchannelFrequencyLookup($thisfile_vqf_COMM['sample_rate']); - $ThisFileInfo['audio']['bitrate'] = $thisfile_vqf_COMM['bitrate'] * 1000; - $ThisFileInfo['audio']['encoder_options'] = 'CBR' . ceil($ThisFileInfo['audio']['bitrate']/1000); + $info['audio']['channels'] = $thisfile_vqf_COMM['channel_mode'] + 1; + $info['audio']['sample_rate'] = $this->VQFchannelFrequencyLookup($thisfile_vqf_COMM['sample_rate']); + $info['audio']['bitrate'] = $thisfile_vqf_COMM['bitrate'] * 1000; + $info['audio']['encoder_options'] = 'CBR' . ceil($info['audio']['bitrate']/1000); - if ($ThisFileInfo['audio']['bitrate'] == 0) { - $ThisFileInfo['error'][] = 'Corrupt VQF file: bitrate_audio == zero'; + if ($info['audio']['bitrate'] == 0) { + $info['error'][] = 'Corrupt VQF file: bitrate_audio == zero'; return false; } break; @@ -108,23 +111,23 @@ class getid3_vqf break; default: - $ThisFileInfo['warning'][] = 'Unhandled chunk type "'.$ChunkName.'" at offset '.$ChunkBaseOffset; + $info['warning'][] = 'Unhandled chunk type "'.$ChunkName.'" at offset '.$ChunkBaseOffset; break; } } - $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['audio']['bitrate']; + $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']; - if (isset($thisfile_vqf['DSIZ']) && (($thisfile_vqf['DSIZ'] != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'] - strlen('DATA'))))) { + if (isset($thisfile_vqf['DSIZ']) && (($thisfile_vqf['DSIZ'] != ($info['avdataend'] - $info['avdataoffset'] - strlen('DATA'))))) { switch ($thisfile_vqf['DSIZ']) { case 0: case 1: - $ThisFileInfo['warning'][] = 'Invalid DSIZ value "'.$thisfile_vqf['DSIZ'].'". This is known to happen with VQF files encoded by Ahead Nero, and seems to be its way of saying this is TwinVQF v'.($thisfile_vqf['DSIZ'] + 1).'.0'; - $ThisFileInfo['audio']['encoder'] = 'Ahead Nero'; + $info['warning'][] = 'Invalid DSIZ value "'.$thisfile_vqf['DSIZ'].'". This is known to happen with VQF files encoded by Ahead Nero, and seems to be its way of saying this is TwinVQF v'.($thisfile_vqf['DSIZ'] + 1).'.0'; + $info['audio']['encoder'] = 'Ahead Nero'; break; default: - $ThisFileInfo['warning'][] = 'Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'] - strlen('DATA')); + $info['warning'][] = 'Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($info['avdataend'] - $info['avdataoffset'] - strlen('DATA')); break; } } diff --git a/3rdparty/getid3/module.audio.wavpack.php b/3rdparty/getid3/module.audio.wavpack.php new file mode 100644 index 0000000000..6ab5b43867 --- /dev/null +++ b/3rdparty/getid3/module.audio.wavpack.php @@ -0,0 +1,400 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.wavpack.php // +// module for analyzing WavPack v4.0+ Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_wavpack extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + while (true) { + + $wavpackheader = fread($this->getid3->fp, 32); + + if (ftell($this->getid3->fp) >= $info['avdataend']) { + break; + } elseif (feof($this->getid3->fp)) { + break; + } elseif ( + isset($info['wavpack']['blockheader']['total_samples']) && + isset($info['wavpack']['blockheader']['block_samples']) && + ($info['wavpack']['blockheader']['total_samples'] > 0) && + ($info['wavpack']['blockheader']['block_samples'] > 0) && + (!isset($info['wavpack']['riff_trailer_size']) || ($info['wavpack']['riff_trailer_size'] <= 0)) && + ((isset($info['wavpack']['config_flags']['md5_checksum']) && ($info['wavpack']['config_flags']['md5_checksum'] === false)) || !empty($info['md5_data_source']))) { + break; + } + + $blockheader_offset = ftell($this->getid3->fp) - 32; + $blockheader_magic = substr($wavpackheader, 0, 4); + $blockheader_size = getid3_lib::LittleEndian2Int(substr($wavpackheader, 4, 4)); + + $magic = 'wvpk'; + if ($blockheader_magic != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$blockheader_offset.', found "'.getid3_lib::PrintHexBytes($blockheader_magic).'"'; + switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { + case 'wavpack': + case 'wvc': + break; + default: + unset($info['fileformat']); + unset($info['audio']); + unset($info['wavpack']); + break; + } + return false; + } + + if (empty($info['wavpack']['blockheader']['block_samples']) || + empty($info['wavpack']['blockheader']['total_samples']) || + ($info['wavpack']['blockheader']['block_samples'] <= 0) || + ($info['wavpack']['blockheader']['total_samples'] <= 0)) { + // Also, it is possible that the first block might not have + // any samples (block_samples == 0) and in this case you should skip blocks + // until you find one with samples because the other information (like + // total_samples) are not guaranteed to be correct until (block_samples > 0) + + // Finally, I have defined a format for files in which the length is not known + // (for example when raw files are created using pipes). In these cases + // total_samples will be -1 and you must seek to the final block to determine + // the total number of samples. + + + $info['audio']['dataformat'] = 'wavpack'; + $info['fileformat'] = 'wavpack'; + $info['audio']['lossless'] = true; + $info['audio']['bitrate_mode'] = 'vbr'; + + $info['wavpack']['blockheader']['offset'] = $blockheader_offset; + $info['wavpack']['blockheader']['magic'] = $blockheader_magic; + $info['wavpack']['blockheader']['size'] = $blockheader_size; + + if ($info['wavpack']['blockheader']['size'] >= 0x100000) { + $info['error'][] = 'Expecting WavPack block size less than "0x100000", found "'.$info['wavpack']['blockheader']['size'].'" at offset '.$info['wavpack']['blockheader']['offset']; + switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { + case 'wavpack': + case 'wvc': + break; + default: + unset($info['fileformat']); + unset($info['audio']); + unset($info['wavpack']); + break; + } + return false; + } + + $info['wavpack']['blockheader']['minor_version'] = ord($wavpackheader{8}); + $info['wavpack']['blockheader']['major_version'] = ord($wavpackheader{9}); + + if (($info['wavpack']['blockheader']['major_version'] != 4) || + (($info['wavpack']['blockheader']['minor_version'] < 4) && + ($info['wavpack']['blockheader']['minor_version'] > 16))) { + $info['error'][] = 'Expecting WavPack version between "4.2" and "4.16", found version "'.$info['wavpack']['blockheader']['major_version'].'.'.$info['wavpack']['blockheader']['minor_version'].'" at offset '.$info['wavpack']['blockheader']['offset']; + switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { + case 'wavpack': + case 'wvc': + break; + default: + unset($info['fileformat']); + unset($info['audio']); + unset($info['wavpack']); + break; + } + return false; + } + + $info['wavpack']['blockheader']['track_number'] = ord($wavpackheader{10}); // unused + $info['wavpack']['blockheader']['index_number'] = ord($wavpackheader{11}); // unused + $info['wavpack']['blockheader']['total_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 12, 4)); + $info['wavpack']['blockheader']['block_index'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 16, 4)); + $info['wavpack']['blockheader']['block_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 20, 4)); + $info['wavpack']['blockheader']['flags_raw'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 24, 4)); + $info['wavpack']['blockheader']['crc'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 28, 4)); + + $info['wavpack']['blockheader']['flags']['bytes_per_sample'] = 1 + ($info['wavpack']['blockheader']['flags_raw'] & 0x00000003); + $info['wavpack']['blockheader']['flags']['mono'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000004); + $info['wavpack']['blockheader']['flags']['hybrid'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000008); + $info['wavpack']['blockheader']['flags']['joint_stereo'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000010); + $info['wavpack']['blockheader']['flags']['cross_decorrelation'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000020); + $info['wavpack']['blockheader']['flags']['hybrid_noiseshape'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000040); + $info['wavpack']['blockheader']['flags']['ieee_32bit_float'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000080); + $info['wavpack']['blockheader']['flags']['int_32bit'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000100); + $info['wavpack']['blockheader']['flags']['hybrid_bitrate_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000200); + $info['wavpack']['blockheader']['flags']['hybrid_balance_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000400); + $info['wavpack']['blockheader']['flags']['multichannel_initial'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000800); + $info['wavpack']['blockheader']['flags']['multichannel_final'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00001000); + + $info['audio']['lossless'] = !$info['wavpack']['blockheader']['flags']['hybrid']; + } + + while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < ($blockheader_offset + $blockheader_size + 8))) { + + $metablock = array('offset'=>ftell($this->getid3->fp)); + $metablockheader = fread($this->getid3->fp, 2); + if (feof($this->getid3->fp)) { + break; + } + $metablock['id'] = ord($metablockheader{0}); + $metablock['function_id'] = ($metablock['id'] & 0x3F); + $metablock['function_name'] = $this->WavPackMetablockNameLookup($metablock['function_id']); + + // The 0x20 bit in the id of the meta subblocks (which is defined as + // ID_OPTIONAL_DATA) is a permanent part of the id. The idea is that + // if a decoder encounters an id that it does not know about, it uses + // that "ID_OPTIONAL_DATA" flag to determine what to do. If it is set + // then the decoder simply ignores the metadata, but if it is zero + // then the decoder should quit because it means that an understanding + // of the metadata is required to correctly decode the audio. + $metablock['non_decoder'] = (bool) ($metablock['id'] & 0x20); + + $metablock['padded_data'] = (bool) ($metablock['id'] & 0x40); + $metablock['large_block'] = (bool) ($metablock['id'] & 0x80); + if ($metablock['large_block']) { + $metablockheader .= fread($this->getid3->fp, 2); + } + $metablock['size'] = getid3_lib::LittleEndian2Int(substr($metablockheader, 1)) * 2; // size is stored in words + $metablock['data'] = null; + + if ($metablock['size'] > 0) { + + switch ($metablock['function_id']) { + case 0x21: // ID_RIFF_HEADER + case 0x22: // ID_RIFF_TRAILER + case 0x23: // ID_REPLAY_GAIN + case 0x24: // ID_CUESHEET + case 0x25: // ID_CONFIG_BLOCK + case 0x26: // ID_MD5_CHECKSUM + $metablock['data'] = fread($this->getid3->fp, $metablock['size']); + + if ($metablock['padded_data']) { + // padded to the nearest even byte + $metablock['size']--; + $metablock['data'] = substr($metablock['data'], 0, -1); + } + break; + + case 0x00: // ID_DUMMY + case 0x01: // ID_ENCODER_INFO + case 0x02: // ID_DECORR_TERMS + case 0x03: // ID_DECORR_WEIGHTS + case 0x04: // ID_DECORR_SAMPLES + case 0x05: // ID_ENTROPY_VARS + case 0x06: // ID_HYBRID_PROFILE + case 0x07: // ID_SHAPING_WEIGHTS + case 0x08: // ID_FLOAT_INFO + case 0x09: // ID_INT32_INFO + case 0x0A: // ID_WV_BITSTREAM + case 0x0B: // ID_WVC_BITSTREAM + case 0x0C: // ID_WVX_BITSTREAM + case 0x0D: // ID_CHANNEL_INFO + fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET); + break; + + default: + $info['warning'][] = 'Unexpected metablock type "0x'.str_pad(dechex($metablock['function_id']), 2, '0', STR_PAD_LEFT).'" at offset '.$metablock['offset']; + fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET); + break; + } + + switch ($metablock['function_id']) { + case 0x21: // ID_RIFF_HEADER + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + $original_wav_filesize = getid3_lib::LittleEndian2Int(substr($metablock['data'], 4, 4)); + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->ParseRIFFdata($metablock['data']); + $metablock['riff'] = $getid3_temp->info['riff']; + $info['audio']['sample_rate'] = $getid3_temp->info['riff']['raw']['fmt ']['nSamplesPerSec']; + unset($getid3_riff, $getid3_temp); + + $metablock['riff']['original_filesize'] = $original_wav_filesize; + $info['wavpack']['riff_trailer_size'] = $original_wav_filesize - $metablock['riff']['WAVE']['data'][0]['size'] - $metablock['riff']['header_size']; + $info['playtime_seconds'] = $info['wavpack']['blockheader']['total_samples'] / $info['audio']['sample_rate']; + + // Safe RIFF header in case there's a RIFF footer later + $metablockRIFFheader = $metablock['data']; + break; + + + case 0x22: // ID_RIFF_TRAILER + $metablockRIFFfooter = $metablockRIFFheader.$metablock['data']; + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + + $startoffset = $metablock['offset'] + ($metablock['large_block'] ? 4 : 2); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_temp->info['fileformat'] = 'riff'; + $getid3_riff = new getid3_riff($getid3_temp); + $metablock['riff'] = $getid3_riff->ParseRIFF($startoffset, $startoffset + $metablock['size']); + + if (!empty($metablock['riff']['INFO'])) { + $getid3_riff->RIFFcommentsParse($metablock['riff']['INFO'], $metablock['comments']); + $info['tags']['riff'] = $metablock['comments']; + } + unset($getid3_temp, $getid3_riff); + break; + + + case 0x23: // ID_REPLAY_GAIN + $info['warning'][] = 'WavPack "Replay Gain" contents not yet handled by getID3() in metablock at offset '.$metablock['offset']; + break; + + + case 0x24: // ID_CUESHEET + $info['warning'][] = 'WavPack "Cuesheet" contents not yet handled by getID3() in metablock at offset '.$metablock['offset']; + break; + + + case 0x25: // ID_CONFIG_BLOCK + $metablock['flags_raw'] = getid3_lib::LittleEndian2Int(substr($metablock['data'], 0, 3)); + + $metablock['flags']['adobe_mode'] = (bool) ($metablock['flags_raw'] & 0x000001); // "adobe" mode for 32-bit floats + $metablock['flags']['fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000002); // fast mode + $metablock['flags']['very_fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000004); // double fast + $metablock['flags']['high_flag'] = (bool) ($metablock['flags_raw'] & 0x000008); // high quality mode + $metablock['flags']['very_high_flag'] = (bool) ($metablock['flags_raw'] & 0x000010); // double high (not used yet) + $metablock['flags']['bitrate_kbps'] = (bool) ($metablock['flags_raw'] & 0x000020); // bitrate is kbps, not bits / sample + $metablock['flags']['auto_shaping'] = (bool) ($metablock['flags_raw'] & 0x000040); // automatic noise shaping + $metablock['flags']['shape_override'] = (bool) ($metablock['flags_raw'] & 0x000080); // shaping mode specified + $metablock['flags']['joint_override'] = (bool) ($metablock['flags_raw'] & 0x000100); // joint-stereo mode specified + $metablock['flags']['copy_time'] = (bool) ($metablock['flags_raw'] & 0x000200); // copy file-time from source + $metablock['flags']['create_exe'] = (bool) ($metablock['flags_raw'] & 0x000400); // create executable + $metablock['flags']['create_wvc'] = (bool) ($metablock['flags_raw'] & 0x000800); // create correction file + $metablock['flags']['optimize_wvc'] = (bool) ($metablock['flags_raw'] & 0x001000); // maximize bybrid compression + $metablock['flags']['quality_mode'] = (bool) ($metablock['flags_raw'] & 0x002000); // psychoacoustic quality mode + $metablock['flags']['raw_flag'] = (bool) ($metablock['flags_raw'] & 0x004000); // raw mode (not implemented yet) + $metablock['flags']['calc_noise'] = (bool) ($metablock['flags_raw'] & 0x008000); // calc noise in hybrid mode + $metablock['flags']['lossy_mode'] = (bool) ($metablock['flags_raw'] & 0x010000); // obsolete (for information) + $metablock['flags']['extra_mode'] = (bool) ($metablock['flags_raw'] & 0x020000); // extra processing mode + $metablock['flags']['skip_wvx'] = (bool) ($metablock['flags_raw'] & 0x040000); // no wvx stream w/ floats & big ints + $metablock['flags']['md5_checksum'] = (bool) ($metablock['flags_raw'] & 0x080000); // compute & store MD5 signature + $metablock['flags']['quiet_mode'] = (bool) ($metablock['flags_raw'] & 0x100000); // don't report progress % + + $info['wavpack']['config_flags'] = $metablock['flags']; + + + $info['audio']['encoder_options'] = ''; + if ($info['wavpack']['blockheader']['flags']['hybrid']) { + $info['audio']['encoder_options'] .= ' -b???'; + } + $info['audio']['encoder_options'] .= ($metablock['flags']['adobe_mode'] ? ' -a' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['optimize_wvc'] ? ' -cc' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['create_exe'] ? ' -e' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['fast_flag'] ? ' -f' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['joint_override'] ? ' -j?' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['high_flag'] ? ' -h' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['md5_checksum'] ? ' -m' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['calc_noise'] ? ' -n' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['shape_override'] ? ' -s?' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['extra_mode'] ? ' -x?' : ''); + if (!empty($info['audio']['encoder_options'])) { + $info['audio']['encoder_options'] = trim($info['audio']['encoder_options']); + } elseif (isset($info['audio']['encoder_options'])) { + unset($info['audio']['encoder_options']); + } + break; + + + case 0x26: // ID_MD5_CHECKSUM + if (strlen($metablock['data']) == 16) { + $info['md5_data_source'] = strtolower(getid3_lib::PrintHexBytes($metablock['data'], true, false, false)); + } else { + $info['warning'][] = 'Expecting 16 bytes of WavPack "MD5 Checksum" in metablock at offset '.$metablock['offset'].', but found '.strlen($metablock['data']).' bytes'; + } + break; + + + case 0x00: // ID_DUMMY + case 0x01: // ID_ENCODER_INFO + case 0x02: // ID_DECORR_TERMS + case 0x03: // ID_DECORR_WEIGHTS + case 0x04: // ID_DECORR_SAMPLES + case 0x05: // ID_ENTROPY_VARS + case 0x06: // ID_HYBRID_PROFILE + case 0x07: // ID_SHAPING_WEIGHTS + case 0x08: // ID_FLOAT_INFO + case 0x09: // ID_INT32_INFO + case 0x0A: // ID_WV_BITSTREAM + case 0x0B: // ID_WVC_BITSTREAM + case 0x0C: // ID_WVX_BITSTREAM + case 0x0D: // ID_CHANNEL_INFO + unset($metablock); + break; + } + + } + if (!empty($metablock)) { + $info['wavpack']['metablocks'][] = $metablock; + } + + } + + } + + $info['audio']['encoder'] = 'WavPack v'.$info['wavpack']['blockheader']['major_version'].'.'.str_pad($info['wavpack']['blockheader']['minor_version'], 2, '0', STR_PAD_LEFT); + $info['audio']['bits_per_sample'] = $info['wavpack']['blockheader']['flags']['bytes_per_sample'] * 8; + $info['audio']['channels'] = ($info['wavpack']['blockheader']['flags']['mono'] ? 1 : 2); + + if (!empty($info['playtime_seconds'])) { + + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + + } else { + + $info['audio']['dataformat'] = 'wvc'; + + } + + return true; + } + + + function WavPackMetablockNameLookup(&$id) { + static $WavPackMetablockNameLookup = array( + 0x00 => 'Dummy', + 0x01 => 'Encoder Info', + 0x02 => 'Decorrelation Terms', + 0x03 => 'Decorrelation Weights', + 0x04 => 'Decorrelation Samples', + 0x05 => 'Entropy Variables', + 0x06 => 'Hybrid Profile', + 0x07 => 'Shaping Weights', + 0x08 => 'Float Info', + 0x09 => 'Int32 Info', + 0x0A => 'WV Bitstream', + 0x0B => 'WVC Bitstream', + 0x0C => 'WVX Bitstream', + 0x0D => 'Channel Info', + 0x21 => 'RIFF header', + 0x22 => 'RIFF trailer', + 0x23 => 'Replay Gain', + 0x24 => 'Cuesheet', + 0x25 => 'Config Block', + 0x26 => 'MD5 Checksum', + ); + return (isset($WavPackMetablockNameLookup[$id]) ? $WavPackMetablockNameLookup[$id] : ''); + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.graphic.bmp.php b/3rdparty/getid3/module.graphic.bmp.php similarity index 89% rename from apps/media/getID3/getid3/module.graphic.bmp.php rename to 3rdparty/getid3/module.graphic.bmp.php index ea8a119b5a..5ac671fbca 100644 --- a/apps/media/getID3/getid3/module.graphic.bmp.php +++ b/3rdparty/getid3/module.graphic.bmp.php @@ -14,16 +14,19 @@ ///////////////////////////////////////////////////////////////// -class getid3_bmp +class getid3_bmp extends getid3_handler { + var $ExtractPalette = false; + var $ExtractData = false; - function getid3_bmp(&$fd, &$ThisFileInfo, $ExtractPalette=false, $ExtractData=false) { + function Analyze() { + $info = &$this->getid3->info; - // shortcuts - $ThisFileInfo['bmp']['header']['raw'] = array(); - $thisfile_bmp = &$ThisFileInfo['bmp']; - $thisfile_bmp_header = &$thisfile_bmp['header']; - $thisfile_bmp_header_raw = &$thisfile_bmp_header['raw']; + // shortcuts + $info['bmp']['header']['raw'] = array(); + $thisfile_bmp = &$info['bmp']; + $thisfile_bmp_header = &$thisfile_bmp['header']; + $thisfile_bmp_header_raw = &$thisfile_bmp_header['raw']; // BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp // all versions @@ -33,17 +36,18 @@ class getid3_bmp // WORD bfReserved2; // DWORD bfOffBits; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $offset = 0; - $BMPheader = fread($fd, 14 + 40); + $BMPheader = fread($this->getid3->fp, 14 + 40); $thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2); $offset += 2; - if ($thisfile_bmp_header_raw['identifier'] != 'BM') { - $ThisFileInfo['error'][] = 'Expecting "BM" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_bmp_header_raw['identifier'].'"'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['bmp']); + $magic = 'BM'; + if ($thisfile_bmp_header_raw['identifier'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_bmp_header_raw['identifier']).'"'; + unset($info['fileformat']); + unset($info['bmp']); return false; } @@ -81,16 +85,16 @@ class getid3_bmp $thisfile_bmp['type_os'] = 'Windows'; $thisfile_bmp['type_version'] = 5; } else { - $ThisFileInfo['error'][] = 'Unknown BMP subtype (or not a BMP file)'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['bmp']); + $info['error'][] = 'Unknown BMP subtype (or not a BMP file)'; + unset($info['fileformat']); + unset($info['bmp']); return false; } - $ThisFileInfo['fileformat'] = 'bmp'; - $ThisFileInfo['video']['dataformat'] = 'bmp'; - $ThisFileInfo['video']['lossless'] = true; - $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + $info['fileformat'] = 'bmp'; + $info['video']['dataformat'] = 'bmp'; + $info['video']['lossless'] = true; + $info['video']['pixel_aspect_ratio'] = (float) 1; if ($thisfile_bmp['type_os'] == 'OS/2') { @@ -112,10 +116,10 @@ class getid3_bmp $thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); $offset += 2; - $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width']; - $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height']; - $ThisFileInfo['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; - $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel']; + $info['video']['resolution_x'] = $thisfile_bmp_header_raw['width']; + $info['video']['resolution_y'] = $thisfile_bmp_header_raw['height']; + $info['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; + $info['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel']; if ($thisfile_bmp['type_version'] >= 2) { // DWORD Compression; /* Bitmap compression scheme */ @@ -162,9 +166,9 @@ class getid3_bmp $thisfile_bmp_header_raw['identifier'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); $offset += 4; - $thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']); + $thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']); - $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; + $info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; } } elseif ($thisfile_bmp['type_os'] == 'Windows') { @@ -209,14 +213,14 @@ class getid3_bmp $offset += 4; $thisfile_bmp_header['compression'] = $this->BMPcompressionWindowsLookup($thisfile_bmp_header_raw['compression']); - $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width']; - $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height']; - $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; - $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel']; + $info['video']['resolution_x'] = $thisfile_bmp_header_raw['width']; + $info['video']['resolution_y'] = $thisfile_bmp_header_raw['height']; + $info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; + $info['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel']; if (($thisfile_bmp['type_version'] >= 4) || ($thisfile_bmp_header_raw['compression'] == 3)) { // should only be v4+, but BMPs with type_version==1 and BI_BITFIELDS compression have been seen - $BMPheader .= fread($fd, 44); + $BMPheader .= fread($this->getid3->fp, 44); // BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp // Win95+, WinNT4.0+ @@ -258,7 +262,7 @@ class getid3_bmp } if ($thisfile_bmp['type_version'] >= 5) { - $BMPheader .= fread($fd, 16); + $BMPheader .= fread($this->getid3->fp, 16); // BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp // Win98+, Win2000+ @@ -278,13 +282,13 @@ class getid3_bmp } else { - $ThisFileInfo['error'][] = 'Unknown BMP format in header.'; + $info['error'][] = 'Unknown BMP format in header.'; return false; } - if ($ExtractPalette || $ExtractData) { + if ($this->ExtractPalette || $this->ExtractData) { $PaletteEntries = 0; if ($thisfile_bmp_header_raw['bits_per_pixel'] < 16) { $PaletteEntries = pow(2, $thisfile_bmp_header_raw['bits_per_pixel']); @@ -292,7 +296,7 @@ class getid3_bmp $PaletteEntries = $thisfile_bmp_header_raw['colors_used']; } if ($PaletteEntries > 0) { - $BMPpalette = fread($fd, 4 * $PaletteEntries); + $BMPpalette = fread($this->getid3->fp, 4 * $PaletteEntries); $paletteoffset = 0; for ($i = 0; $i < $PaletteEntries; $i++) { // RGBQUAD - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_5f8y.asp @@ -313,12 +317,13 @@ class getid3_bmp } } - if ($ExtractData) { - fseek($fd, $thisfile_bmp_header_raw['data_offset'], SEEK_SET); + if ($this->ExtractData) { + fseek($this->getid3->fp, $thisfile_bmp_header_raw['data_offset'], SEEK_SET); $RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry - $BMPpixelData = fread($fd, $thisfile_bmp_header_raw['height'] * $RowByteLength); + $BMPpixelData = fread($this->getid3->fp, $thisfile_bmp_header_raw['height'] * $RowByteLength); $pixeldataoffset = 0; - switch (@$thisfile_bmp_header_raw['compression']) { + $thisfile_bmp_header_raw['compression'] = (isset($thisfile_bmp_header_raw['compression']) ? $thisfile_bmp_header_raw['compression'] : ''); + switch ($thisfile_bmp_header_raw['compression']) { case 0: // BI_RGB switch ($thisfile_bmp_header_raw['bits_per_pixel']) { @@ -400,7 +405,7 @@ class getid3_bmp break; default: - $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + $info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; break; } break; @@ -475,7 +480,7 @@ class getid3_bmp break; default: - $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + $info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; break; } break; @@ -564,7 +569,7 @@ class getid3_bmp break; default: - $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + $info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; break; } break; @@ -604,14 +609,14 @@ class getid3_bmp break; default: - $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + $info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; break; } break; default: // unhandled compression type - $ThisFileInfo['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data'; + $info['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data'; break; } } diff --git a/3rdparty/getid3/module.graphic.efax.php b/3rdparty/getid3/module.graphic.efax.php new file mode 100644 index 0000000000..2a57302e15 --- /dev/null +++ b/3rdparty/getid3/module.graphic.efax.php @@ -0,0 +1,53 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.archive.efax.php // +// module for analyzing eFax files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_efax extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $efaxheader = fread($this->getid3->fp, 1024); + + $info['efax']['header']['magic'] = substr($efaxheader, 0, 2); + if ($info['efax']['header']['magic'] != "\xDC\xFE") { + $info['error'][] = 'Invalid eFax byte order identifier (expecting DC FE, found '.getid3_lib::PrintHexBytes($info['efax']['header']['magic']).') at offset '.$info['avdataoffset']; + return false; + } + $info['fileformat'] = 'efax'; + + $info['efax']['header']['filesize'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 2, 4)); + if ($info['efax']['header']['filesize'] != $info['filesize']) { + $info['error'][] = 'Probable '.(($info['efax']['header']['filesize'] > $info['filesize']) ? 'truncated' : 'corrupt').' file, expecting '.$info['efax']['header']['filesize'].' bytes, found '.$info['filesize'].' bytes'; + } + $info['efax']['header']['software1'] = rtrim(substr($efaxheader, 26, 32), "\x00"); + $info['efax']['header']['software2'] = rtrim(substr($efaxheader, 58, 32), "\x00"); + $info['efax']['header']['software3'] = rtrim(substr($efaxheader, 90, 32), "\x00"); + + $info['efax']['header']['pages'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 198, 2)); + $info['efax']['header']['data_bytes'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 202, 4)); + +$info['error'][] = 'eFax parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; +return false; + + return true; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.graphic.gif.php b/3rdparty/getid3/module.graphic.gif.php new file mode 100644 index 0000000000..0b3e1379ff --- /dev/null +++ b/3rdparty/getid3/module.graphic.gif.php @@ -0,0 +1,184 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.graphic.gif.php // +// module for analyzing GIF Image files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_gif extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'gif'; + $info['video']['dataformat'] = 'gif'; + $info['video']['lossless'] = true; + $info['video']['pixel_aspect_ratio'] = (float) 1; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $GIFheader = fread($this->getid3->fp, 13); + $offset = 0; + + $info['gif']['header']['raw']['identifier'] = substr($GIFheader, $offset, 3); + $offset += 3; + + $magic = 'GIF'; + if ($info['gif']['header']['raw']['identifier'] != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['gif']['header']['raw']['identifier']).'"'; + unset($info['fileformat']); + unset($info['gif']); + return false; + } + + $info['gif']['header']['raw']['version'] = substr($GIFheader, $offset, 3); + $offset += 3; + $info['gif']['header']['raw']['width'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2)); + $offset += 2; + $info['gif']['header']['raw']['height'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2)); + $offset += 2; + $info['gif']['header']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1)); + $offset += 1; + $info['gif']['header']['raw']['bg_color_index'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1)); + $offset += 1; + $info['gif']['header']['raw']['aspect_ratio'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1)); + $offset += 1; + + $info['video']['resolution_x'] = $info['gif']['header']['raw']['width']; + $info['video']['resolution_y'] = $info['gif']['header']['raw']['height']; + $info['gif']['version'] = $info['gif']['header']['raw']['version']; + $info['gif']['header']['flags']['global_color_table'] = (bool) ($info['gif']['header']['raw']['flags'] & 0x80); + if ($info['gif']['header']['raw']['flags'] & 0x80) { + // Number of bits per primary color available to the original image, minus 1 + $info['gif']['header']['bits_per_pixel'] = 3 * ((($info['gif']['header']['raw']['flags'] & 0x70) >> 4) + 1); + } else { + $info['gif']['header']['bits_per_pixel'] = 0; + } + $info['gif']['header']['flags']['global_color_sorted'] = (bool) ($info['gif']['header']['raw']['flags'] & 0x40); + if ($info['gif']['header']['flags']['global_color_table']) { + // the number of bytes contained in the Global Color Table. To determine that + // actual size of the color table, raise 2 to [the value of the field + 1] + $info['gif']['header']['global_color_size'] = pow(2, ($info['gif']['header']['raw']['flags'] & 0x07) + 1); + $info['video']['bits_per_sample'] = ($info['gif']['header']['raw']['flags'] & 0x07) + 1; + } else { + $info['gif']['header']['global_color_size'] = 0; + } + if ($info['gif']['header']['raw']['aspect_ratio'] != 0) { + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + $info['gif']['header']['aspect_ratio'] = ($info['gif']['header']['raw']['aspect_ratio'] + 15) / 64; + } + +// if ($info['gif']['header']['flags']['global_color_table']) { +// $GIFcolorTable = fread($this->getid3->fp, 3 * $info['gif']['header']['global_color_size']); +// $offset = 0; +// for ($i = 0; $i < $info['gif']['header']['global_color_size']; $i++) { +// $red = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1)); +// $green = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1)); +// $blue = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1)); +// $info['gif']['global_color_table'][$i] = (($red << 16) | ($green << 8) | ($blue)); +// } +// } +// +// // Image Descriptor +// while (!feof($this->getid3->fp)) { +// $NextBlockTest = fread($this->getid3->fp, 1); +// switch ($NextBlockTest) { +// +// case ',': // ',' - Image separator character +// +// $ImageDescriptorData = $NextBlockTest.fread($this->getid3->fp, 9); +// $ImageDescriptor = array(); +// $ImageDescriptor['image_left'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 1, 2)); +// $ImageDescriptor['image_top'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 3, 2)); +// $ImageDescriptor['image_width'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 5, 2)); +// $ImageDescriptor['image_height'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 7, 2)); +// $ImageDescriptor['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 9, 1)); +// $ImageDescriptor['flags']['use_local_color_map'] = (bool) ($ImageDescriptor['flags_raw'] & 0x80); +// $ImageDescriptor['flags']['image_interlaced'] = (bool) ($ImageDescriptor['flags_raw'] & 0x40); +// $info['gif']['image_descriptor'][] = $ImageDescriptor; +// +// if ($ImageDescriptor['flags']['use_local_color_map']) { +// +// $info['warning'][] = 'This version of getID3() cannot parse local color maps for GIFs'; +// return true; +// +// } +//echo 'Start of raster data: '.ftell($this->getid3->fp).'
    '; +// $RasterData = array(); +// $RasterData['code_size'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1)); +// $RasterData['block_byte_count'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1)); +// $info['gif']['raster_data'][count($info['gif']['image_descriptor']) - 1] = $RasterData; +// +// $CurrentCodeSize = $RasterData['code_size'] + 1; +// for ($i = 0; $i < pow(2, $RasterData['code_size']); $i++) { +// $DefaultDataLookupTable[$i] = chr($i); +// } +// $DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 0] = ''; // Clear Code +// $DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 1] = ''; // End Of Image Code +// +// +// $NextValue = $this->GetLSBits($CurrentCodeSize); +// echo 'Clear Code: '.$NextValue.'
    '; +// +// $NextValue = $this->GetLSBits($CurrentCodeSize); +// echo 'First Color: '.$NextValue.'
    '; +// +// $Prefix = $NextValue; +//$i = 0; +// while ($i++ < 20) { +// $NextValue = $this->GetLSBits($CurrentCodeSize); +// echo $NextValue.'
    '; +// } +//return true; +// break; +// +// case '!': +// // GIF Extension Block +// $ExtensionBlockData = $NextBlockTest.fread($this->getid3->fp, 2); +// $ExtensionBlock = array(); +// $ExtensionBlock['function_code'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 1, 1)); +// $ExtensionBlock['byte_length'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 2, 1)); +// $ExtensionBlock['data'] = fread($this->getid3->fp, $ExtensionBlock['byte_length']); +// $info['gif']['extension_blocks'][] = $ExtensionBlock; +// break; +// +// case ';': +// $info['gif']['terminator_offset'] = ftell($this->getid3->fp) - 1; +// // GIF Terminator +// break; +// +// default: +// break; +// +// +// } +// } + + return true; + } + + + function GetLSBits($bits) { + static $bitbuffer = ''; + while (strlen($bitbuffer) < $bits) { + $bitbuffer = str_pad(decbin(ord(fread($this->getid3->fp, 1))), 8, '0', STR_PAD_LEFT).$bitbuffer; + } + $value = bindec(substr($bitbuffer, 0 - $bits)); + $bitbuffer = substr($bitbuffer, 0, 0 - $bits); + + return $value; + } + +} + + +?> \ No newline at end of file diff --git a/3rdparty/getid3/module.graphic.jpg.php b/3rdparty/getid3/module.graphic.jpg.php new file mode 100644 index 0000000000..153ebcd0e6 --- /dev/null +++ b/3rdparty/getid3/module.graphic.jpg.php @@ -0,0 +1,338 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.graphic.jpg.php // +// module for analyzing JPEG Image files // +// dependencies: PHP compiled with --enable-exif (optional) // +// module.tag.xmp.php (optional) // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_jpg extends getid3_handler +{ + + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'jpg'; + $info['video']['dataformat'] = 'jpg'; + $info['video']['lossless'] = false; + $info['video']['bits_per_sample'] = 24; + $info['video']['pixel_aspect_ratio'] = (float) 1; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $imageinfo = array(); + list($width, $height, $type) = getid3_lib::GetDataImageSize(fread($this->getid3->fp, $info['filesize']), $imageinfo); + + + if (isset($imageinfo['APP13'])) { + // http://php.net/iptcparse + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html + $iptc_parsed = iptcparse($imageinfo['APP13']); + if (is_array($iptc_parsed)) { + foreach ($iptc_parsed as $iptc_key_raw => $iptc_values) { + list($iptc_record, $iptc_tagkey) = explode('#', $iptc_key_raw); + $iptc_tagkey = intval(ltrim($iptc_tagkey, '0')); + foreach ($iptc_values as $key => $value) { + $IPTCrecordName = $this->IPTCrecordName($iptc_record); + $IPTCrecordTagName = $this->IPTCrecordTagName($iptc_record, $iptc_tagkey); + if (isset($info['iptc'][$IPTCrecordName][$IPTCrecordTagName])) { + $info['iptc'][$IPTCrecordName][$IPTCrecordTagName][] = $value; + } else { + $info['iptc'][$IPTCrecordName][$IPTCrecordTagName] = array($value); + } + } + } + } + } + + $returnOK = false; + switch ($type) { + case IMG_JPG: + $info['video']['resolution_x'] = $width; + $info['video']['resolution_y'] = $height; + + if (isset($imageinfo['APP1'])) { + if (function_exists('exif_read_data')) { + if (substr($imageinfo['APP1'], 0, 4) == 'Exif') { + $info['jpg']['exif'] = @exif_read_data($info['filenamepath'], '', true, false); + } else { + $info['warning'][] = 'exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")'; + } + } else { + $info['warning'][] = 'EXIF parsing only available when '.(GETID3_OS_ISWINDOWS ? 'php_exif.dll enabled' : 'compiled with --enable-exif'); + } + } + $returnOK = true; + break; + + default: + break; + } + + + $cast_as_appropriate_keys = array('EXIF', 'IFD0', 'THUMBNAIL'); + foreach ($cast_as_appropriate_keys as $exif_key) { + if (isset($info['jpg']['exif'][$exif_key])) { + foreach ($info['jpg']['exif'][$exif_key] as $key => $value) { + $info['jpg']['exif'][$exif_key][$key] = $this->CastAsAppropriate($value); + } + } + } + + + if (isset($info['jpg']['exif']['GPS'])) { + + if (isset($info['jpg']['exif']['GPS']['GPSVersion'])) { + for ($i = 0; $i < 4; $i++) { + $version_subparts[$i] = ord(substr($info['jpg']['exif']['GPS']['GPSVersion'], $i, 1)); + } + $info['jpg']['exif']['GPS']['computed']['version'] = 'v'.implode('.', $version_subparts); + } + + if (isset($info['jpg']['exif']['GPS']['GPSDateStamp'])) { + $explodedGPSDateStamp = explode(':', $info['jpg']['exif']['GPS']['GPSDateStamp']); + $computed_time[5] = (isset($explodedGPSDateStamp[0]) ? $explodedGPSDateStamp[0] : ''); + $computed_time[3] = (isset($explodedGPSDateStamp[1]) ? $explodedGPSDateStamp[1] : ''); + $computed_time[4] = (isset($explodedGPSDateStamp[2]) ? $explodedGPSDateStamp[2] : ''); + + if (function_exists('date_default_timezone_set')) { + date_default_timezone_set('UTC'); + } else { + ini_set('date.timezone', 'UTC'); + } + + $computed_time = array(0=>0, 1=>0, 2=>0, 3=>0, 4=>0, 5=>0); + if (isset($info['jpg']['exif']['GPS']['GPSTimeStamp']) && is_array($info['jpg']['exif']['GPS']['GPSTimeStamp'])) { + foreach ($info['jpg']['exif']['GPS']['GPSTimeStamp'] as $key => $value) { + $computed_time[$key] = getid3_lib::DecimalizeFraction($value); + } + } + $info['jpg']['exif']['GPS']['computed']['timestamp'] = mktime($computed_time[0], $computed_time[1], $computed_time[2], $computed_time[3], $computed_time[4], $computed_time[5]); + } + + if (isset($info['jpg']['exif']['GPS']['GPSLatitude']) && is_array($info['jpg']['exif']['GPS']['GPSLatitude'])) { + $direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLatitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLatitudeRef'] == 'S')) ? -1 : 1); + foreach ($info['jpg']['exif']['GPS']['GPSLatitude'] as $key => $value) { + $computed_latitude[$key] = getid3_lib::DecimalizeFraction($value); + } + $info['jpg']['exif']['GPS']['computed']['latitude'] = $direction_multiplier * ($computed_latitude[0] + ($computed_latitude[1] / 60) + ($computed_latitude[2] / 3600)); + } + + if (isset($info['jpg']['exif']['GPS']['GPSLongitude']) && is_array($info['jpg']['exif']['GPS']['GPSLongitude'])) { + $direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLongitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLongitudeRef'] == 'W')) ? -1 : 1); + foreach ($info['jpg']['exif']['GPS']['GPSLongitude'] as $key => $value) { + $computed_longitude[$key] = getid3_lib::DecimalizeFraction($value); + } + $info['jpg']['exif']['GPS']['computed']['longitude'] = $direction_multiplier * ($computed_longitude[0] + ($computed_longitude[1] / 60) + ($computed_longitude[2] / 3600)); + } + + if (isset($info['jpg']['exif']['GPS']['GPSAltitude'])) { + $direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSAltitudeRef']) && ($info['jpg']['exif']['GPS']['GPSAltitudeRef'] === chr(1))) ? -1 : 1); + $info['jpg']['exif']['GPS']['computed']['altitude'] = $direction_multiplier * getid3_lib::DecimalizeFraction($info['jpg']['exif']['GPS']['GPSAltitude']); + } + + } + + + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.xmp.php', __FILE__, false)) { + if (isset($info['filenamepath'])) { + $image_xmp = new Image_XMP($info['filenamepath']); + $xmp_raw = $image_xmp->getAllTags(); + foreach ($xmp_raw as $key => $value) { + list($subsection, $tagname) = explode(':', $key); + $info['xmp'][$subsection][$tagname] = $this->CastAsAppropriate($value); + } + } + } + + if (!$returnOK) { + unset($info['fileformat']); + return false; + } + return true; + } + + + function CastAsAppropriate($value) { + if (is_array($value)) { + return $value; + } elseif (preg_match('#^[0-9]+/[0-9]+$#', $value)) { + return getid3_lib::DecimalizeFraction($value); + } elseif (preg_match('#^[0-9]+$#', $value)) { + return getid3_lib::CastAsInt($value); + } elseif (preg_match('#^[0-9\.]+$#', $value)) { + return (float) $value; + } + return $value; + } + + + function IPTCrecordName($iptc_record) { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html + static $IPTCrecordName = array(); + if (empty($IPTCrecordName)) { + $IPTCrecordName = array( + 1 => 'IPTCEnvelope', + 2 => 'IPTCApplication', + 3 => 'IPTCNewsPhoto', + 7 => 'IPTCPreObjectData', + 8 => 'IPTCObjectData', + 9 => 'IPTCPostObjectData', + ); + } + return (isset($IPTCrecordName[$iptc_record]) ? $IPTCrecordName[$iptc_record] : ''); + } + + + function IPTCrecordTagName($iptc_record, $iptc_tagkey) { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html + static $IPTCrecordTagName = array(); + if (empty($IPTCrecordTagName)) { + $IPTCrecordTagName = array( + 1 => array( // IPTC EnvelopeRecord Tags + 0 => 'EnvelopeRecordVersion', + 5 => 'Destination', + 20 => 'FileFormat', + 22 => 'FileVersion', + 30 => 'ServiceIdentifier', + 40 => 'EnvelopeNumber', + 50 => 'ProductID', + 60 => 'EnvelopePriority', + 70 => 'DateSent', + 80 => 'TimeSent', + 90 => 'CodedCharacterSet', + 100 => 'UniqueObjectName', + 120 => 'ARMIdentifier', + 122 => 'ARMVersion', + ), + 2 => array( // IPTC ApplicationRecord Tags + 0 => 'ApplicationRecordVersion', + 3 => 'ObjectTypeReference', + 4 => 'ObjectAttributeReference', + 5 => 'ObjectName', + 7 => 'EditStatus', + 8 => 'EditorialUpdate', + 10 => 'Urgency', + 12 => 'SubjectReference', + 15 => 'Category', + 20 => 'SupplementalCategories', + 22 => 'FixtureIdentifier', + 25 => 'Keywords', + 26 => 'ContentLocationCode', + 27 => 'ContentLocationName', + 30 => 'ReleaseDate', + 35 => 'ReleaseTime', + 37 => 'ExpirationDate', + 38 => 'ExpirationTime', + 40 => 'SpecialInstructions', + 42 => 'ActionAdvised', + 45 => 'ReferenceService', + 47 => 'ReferenceDate', + 50 => 'ReferenceNumber', + 55 => 'DateCreated', + 60 => 'TimeCreated', + 62 => 'DigitalCreationDate', + 63 => 'DigitalCreationTime', + 65 => 'OriginatingProgram', + 70 => 'ProgramVersion', + 75 => 'ObjectCycle', + 80 => 'By-line', + 85 => 'By-lineTitle', + 90 => 'City', + 92 => 'Sub-location', + 95 => 'Province-State', + 100 => 'Country-PrimaryLocationCode', + 101 => 'Country-PrimaryLocationName', + 103 => 'OriginalTransmissionReference', + 105 => 'Headline', + 110 => 'Credit', + 115 => 'Source', + 116 => 'CopyrightNotice', + 118 => 'Contact', + 120 => 'Caption-Abstract', + 121 => 'LocalCaption', + 122 => 'Writer-Editor', + 125 => 'RasterizedCaption', + 130 => 'ImageType', + 131 => 'ImageOrientation', + 135 => 'LanguageIdentifier', + 150 => 'AudioType', + 151 => 'AudioSamplingRate', + 152 => 'AudioSamplingResolution', + 153 => 'AudioDuration', + 154 => 'AudioOutcue', + 184 => 'JobID', + 185 => 'MasterDocumentID', + 186 => 'ShortDocumentID', + 187 => 'UniqueDocumentID', + 188 => 'OwnerID', + 200 => 'ObjectPreviewFileFormat', + 201 => 'ObjectPreviewFileVersion', + 202 => 'ObjectPreviewData', + 221 => 'Prefs', + 225 => 'ClassifyState', + 228 => 'SimilarityIndex', + 230 => 'DocumentNotes', + 231 => 'DocumentHistory', + 232 => 'ExifCameraInfo', + ), + 3 => array( // IPTC NewsPhoto Tags + 0 => 'NewsPhotoVersion', + 10 => 'IPTCPictureNumber', + 20 => 'IPTCImageWidth', + 30 => 'IPTCImageHeight', + 40 => 'IPTCPixelWidth', + 50 => 'IPTCPixelHeight', + 55 => 'SupplementalType', + 60 => 'ColorRepresentation', + 64 => 'InterchangeColorSpace', + 65 => 'ColorSequence', + 66 => 'ICC_Profile', + 70 => 'ColorCalibrationMatrix', + 80 => 'LookupTable', + 84 => 'NumIndexEntries', + 85 => 'ColorPalette', + 86 => 'IPTCBitsPerSample', + 90 => 'SampleStructure', + 100 => 'ScanningDirection', + 102 => 'IPTCImageRotation', + 110 => 'DataCompressionMethod', + 120 => 'QuantizationMethod', + 125 => 'EndPoints', + 130 => 'ExcursionTolerance', + 135 => 'BitsPerComponent', + 140 => 'MaximumDensityRange', + 145 => 'GammaCompensatedValue', + ), + 7 => array( // IPTC PreObjectData Tags + 10 => 'SizeMode', + 20 => 'MaxSubfileSize', + 90 => 'ObjectSizeAnnounced', + 95 => 'MaximumObjectSize', + ), + 8 => array( // IPTC ObjectData Tags + 10 => 'SubFile', + ), + 9 => array( // IPTC PostObjectData Tags + 10 => 'ConfirmedObjectSize', + ), + ); + + } + return (isset($IPTCrecordTagName[$iptc_record][$iptc_tagkey]) ? $IPTCrecordTagName[$iptc_record][$iptc_tagkey] : $iptc_tagkey); + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.graphic.pcd.php b/3rdparty/getid3/module.graphic.pcd.php similarity index 67% rename from apps/media/getID3/getid3/module.graphic.pcd.php rename to 3rdparty/getid3/module.graphic.pcd.php index 60efabdf61..dcbc676363 100644 --- a/apps/media/getID3/getid3/module.graphic.pcd.php +++ b/3rdparty/getid3/module.graphic.pcd.php @@ -14,34 +14,38 @@ ///////////////////////////////////////////////////////////////// -class getid3_pcd +class getid3_pcd extends getid3_handler { - function getid3_pcd(&$fd, &$ThisFileInfo, $ExtractData=0) { - $ThisFileInfo['fileformat'] = 'pcd'; - $ThisFileInfo['video']['dataformat'] = 'pcd'; - $ThisFileInfo['video']['lossless'] = false; + var $ExtractData = 0; + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'pcd'; + $info['video']['dataformat'] = 'pcd'; + $info['video']['lossless'] = false; - fseek($fd, $ThisFileInfo['avdataoffset'] + 72, SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'] + 72, SEEK_SET); - $PCDflags = fread($fd, 1); + $PCDflags = fread($this->getid3->fp, 1); $PCDisVertical = ((ord($PCDflags) & 0x01) ? true : false); if ($PCDisVertical) { - $ThisFileInfo['video']['resolution_x'] = 3072; - $ThisFileInfo['video']['resolution_y'] = 2048; + $info['video']['resolution_x'] = 3072; + $info['video']['resolution_y'] = 2048; } else { - $ThisFileInfo['video']['resolution_x'] = 2048; - $ThisFileInfo['video']['resolution_y'] = 3072; + $info['video']['resolution_x'] = 2048; + $info['video']['resolution_y'] = 3072; } - if ($ExtractData > 3) { + if ($this->ExtractData > 3) { - $ThisFileInfo['error'][] = 'Cannot extract PSD image data for detail levels above BASE (3)'; + $info['error'][] = 'Cannot extract PSD image data for detail levels above BASE (level-3) because encrypted with Kodak-proprietary compression/encryption.'; - } elseif ($ExtractData > 0) { + } elseif ($this->ExtractData > 0) { $PCD_levels[1] = array( 192, 128, 0x02000); // BASE/16 $PCD_levels[2] = array( 384, 256, 0x0B800); // BASE/4 @@ -52,7 +56,7 @@ class getid3_pcd list($PCD_width, $PCD_height, $PCD_dataOffset) = $PCD_levels[3]; - fseek($fd, $ThisFileInfo['avdataoffset'] + $PCD_dataOffset, SEEK_SET); + fseek($this->getid3->fp, $info['avdataoffset'] + $PCD_dataOffset, SEEK_SET); for ($y = 0; $y < $PCD_height; $y += 2) { // The image-data of these subtypes start at the respective offsets of 02000h, 0b800h and 30000h. @@ -61,18 +65,18 @@ class getid3_pcd // the first half of the third ‘w’ bytes contain data for the first RGB-line, the second ‘w’ bytes // and the second half of the third ‘w’ bytes contain data for a second RGB-line. - $PCD_data_Y1 = fread($fd, $PCD_width); - $PCD_data_Y2 = fread($fd, $PCD_width); - $PCD_data_Cb = fread($fd, intval(round($PCD_width / 2))); - $PCD_data_Cr = fread($fd, intval(round($PCD_width / 2))); + $PCD_data_Y1 = fread($this->getid3->fp, $PCD_width); + $PCD_data_Y2 = fread($this->getid3->fp, $PCD_width); + $PCD_data_Cb = fread($this->getid3->fp, intval(round($PCD_width / 2))); + $PCD_data_Cr = fread($this->getid3->fp, intval(round($PCD_width / 2))); for ($x = 0; $x < $PCD_width; $x++) { if ($PCDisVertical) { - $ThisFileInfo['pcd']['data'][$PCD_width - $x][$y] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); - $ThisFileInfo['pcd']['data'][$PCD_width - $x][$y + 1] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); + $info['pcd']['data'][$PCD_width - $x][$y] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); + $info['pcd']['data'][$PCD_width - $x][$y + 1] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); } else { - $ThisFileInfo['pcd']['data'][$y][$x] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); - $ThisFileInfo['pcd']['data'][$y + 1][$x] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); + $info['pcd']['data'][$y][$x] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); + $info['pcd']['data'][$y + 1][$x] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); } } } @@ -86,7 +90,7 @@ class getid3_pcd // $BMPinfo['resolution_x'] = $PCD_width; // $BMPinfo['resolution_y'] = $PCD_height; //} - //$BMPinfo['bmp']['data'] = $ThisFileInfo['pcd']['data']; + //$BMPinfo['bmp']['data'] = $info['pcd']['data']; //getid3_bmp::PlotBMP($BMPinfo); //exit; diff --git a/apps/media/getID3/getid3/module.graphic.png.php b/3rdparty/getid3/module.graphic.png.php similarity index 94% rename from apps/media/getID3/getid3/module.graphic.png.php rename to 3rdparty/getid3/module.graphic.png.php index b5b3d35941..9109c43689 100644 --- a/apps/media/getID3/getid3/module.graphic.png.php +++ b/3rdparty/getid3/module.graphic.png.php @@ -14,37 +14,38 @@ ///////////////////////////////////////////////////////////////// -class getid3_png +class getid3_png extends getid3_handler { - function getid3_png(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - // shortcut - $ThisFileInfo['png'] = array(); - $thisfile_png = &$ThisFileInfo['png']; + // shortcut + $info['png'] = array(); + $thisfile_png = &$info['png']; - $ThisFileInfo['fileformat'] = 'png'; - $ThisFileInfo['video']['dataformat'] = 'png'; - $ThisFileInfo['video']['lossless'] = false; + $info['fileformat'] = 'png'; + $info['video']['dataformat'] = 'png'; + $info['video']['lossless'] = false; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $PNGfiledata = fread($fd, GETID3_FREAD_BUFFER_SIZE); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $PNGfiledata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); $offset = 0; $PNGidentifier = substr($PNGfiledata, $offset, 8); // $89 $50 $4E $47 $0D $0A $1A $0A $offset += 8; if ($PNGidentifier != "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { - $ThisFileInfo['error'][] = 'First 8 bytes of file ('.getid3_lib::PrintHexBytes($PNGidentifier).') did not match expected PNG identifier'; - unset($ThisFileInfo['fileformat']); + $info['error'][] = 'First 8 bytes of file ('.getid3_lib::PrintHexBytes($PNGidentifier).') did not match expected PNG identifier'; + unset($info['fileformat']); return false; } - while (((ftell($fd) - (strlen($PNGfiledata) - $offset)) < $ThisFileInfo['filesize'])) { + while (((ftell($this->getid3->fp) - (strlen($PNGfiledata) - $offset)) < $info['filesize'])) { $chunk['data_length'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4)); $offset += 4; - while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && (ftell($fd) < $ThisFileInfo['filesize'])) { - $PNGfiledata .= fread($fd, GETID3_FREAD_BUFFER_SIZE); + while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && (ftell($this->getid3->fp) < $info['filesize'])) { + $PNGfiledata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size()); } $chunk['type_text'] = substr($PNGfiledata, $offset, 4); $offset += 4; @@ -80,10 +81,10 @@ class getid3_png $thisfile_png_chunk_type_text['color_type']['true_color'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x02); $thisfile_png_chunk_type_text['color_type']['alpha'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x04); - $ThisFileInfo['video']['resolution_x'] = $thisfile_png_chunk_type_text['width']; - $ThisFileInfo['video']['resolution_y'] = $thisfile_png_chunk_type_text['height']; + $info['video']['resolution_x'] = $thisfile_png_chunk_type_text['width']; + $info['video']['resolution_y'] = $thisfile_png_chunk_type_text['height']; - $ThisFileInfo['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']); + $info['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']); break; @@ -123,10 +124,10 @@ class getid3_png case 4: case 6: - $ThisFileInfo['error'][] = 'Invalid color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; + $info['error'][] = 'Invalid color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; default: - $ThisFileInfo['warning'][] = 'Unhandled color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; + $info['warning'][] = 'Unhandled color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; break; } break; @@ -429,7 +430,7 @@ class getid3_png default: //unset($chunk['data']); $thisfile_png_chunk_type_text['header'] = $chunk; - $ThisFileInfo['warning'][] = 'Unhandled chunk type: '.$chunk['type_text']; + $info['warning'][] = 'Unhandled chunk type: '.$chunk['type_text']; break; } } diff --git a/3rdparty/getid3/module.graphic.svg.php b/3rdparty/getid3/module.graphic.svg.php new file mode 100644 index 0000000000..d9d52d74d0 --- /dev/null +++ b/3rdparty/getid3/module.graphic.svg.php @@ -0,0 +1,104 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.graphic.svg.php // +// module for analyzing SVG Image files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_svg extends getid3_handler +{ + + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $SVGheader = fread($this->getid3->fp, 4096); + if (preg_match('#\<\?xml([^\>]+)\?\>#i', $SVGheader, $matches)) { + $info['svg']['xml']['raw'] = $matches; + } + if (preg_match('#\<\!DOCTYPE([^\>]+)\>#i', $SVGheader, $matches)) { + $info['svg']['doctype']['raw'] = $matches; + } + if (preg_match('#\]+)\>#i', $SVGheader, $matches)) { + $info['svg']['svg']['raw'] = $matches; + } + if (isset($info['svg']['svg']['raw'])) { + + $sections_to_fix = array('xml', 'doctype', 'svg'); + foreach ($sections_to_fix as $section_to_fix) { + if (!isset($info['svg'][$section_to_fix])) { + continue; + } + $section_data = array(); + while (preg_match('/ "([^"]+)"/', $info['svg'][$section_to_fix]['raw'][1], $matches)) { + $section_data[] = $matches[1]; + $info['svg'][$section_to_fix]['raw'][1] = str_replace($matches[0], '', $info['svg'][$section_to_fix]['raw'][1]); + } + while (preg_match('/([^\s]+)="([^"]+)"/', $info['svg'][$section_to_fix]['raw'][1], $matches)) { + $section_data[] = $matches[0]; + $info['svg'][$section_to_fix]['raw'][1] = str_replace($matches[0], '', $info['svg'][$section_to_fix]['raw'][1]); + } + $section_data = array_merge($section_data, preg_split('/[\s,]+/', $info['svg'][$section_to_fix]['raw'][1])); + foreach ($section_data as $keyvaluepair) { + $keyvaluepair = trim($keyvaluepair); + if ($keyvaluepair) { + $keyvalueexploded = explode('=', $keyvaluepair); + $key = (isset($keyvalueexploded[0]) ? $keyvalueexploded[0] : ''); + $value = (isset($keyvalueexploded[1]) ? $keyvalueexploded[1] : ''); + $info['svg'][$section_to_fix]['sections'][$key] = trim($value, '"'); + } + } + } + + $info['fileformat'] = 'svg'; + $info['video']['dataformat'] = 'svg'; + $info['video']['lossless'] = true; + //$info['video']['bits_per_sample'] = 24; + $info['video']['pixel_aspect_ratio'] = (float) 1; + + if (!empty($info['svg']['svg']['sections']['width'])) { + $info['svg']['width'] = intval($info['svg']['svg']['sections']['width']); + } + if (!empty($info['svg']['svg']['sections']['height'])) { + $info['svg']['height'] = intval($info['svg']['svg']['sections']['height']); + } + if (!empty($info['svg']['svg']['sections']['version'])) { + $info['svg']['version'] = $info['svg']['svg']['sections']['version']; + } + if (!isset($info['svg']['version']) && isset($info['svg']['doctype']['sections'])) { + foreach ($info['svg']['doctype']['sections'] as $key => $value) { + if (preg_match('#//W3C//DTD SVG ([0-9\.]+)//#i', $key, $matches)) { + $info['svg']['version'] = $matches[1]; + break; + } + } + } + + if (!empty($info['svg']['width'])) { + $info['video']['resolution_x'] = $info['svg']['width']; + } + if (!empty($info['svg']['height'])) { + $info['video']['resolution_y'] = $info['svg']['height']; + } + + return true; + } + $info['error'][] = 'Did not find expected tag'; + return false; + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.graphic.tiff.php b/3rdparty/getid3/module.graphic.tiff.php similarity index 60% rename from apps/media/getID3/getid3/module.graphic.tiff.php rename to 3rdparty/getid3/module.graphic.tiff.php index ae57cd6ad2..e977124216 100644 --- a/apps/media/getID3/getid3/module.graphic.tiff.php +++ b/3rdparty/getid3/module.graphic.tiff.php @@ -14,56 +14,57 @@ ///////////////////////////////////////////////////////////////// -class getid3_tiff +class getid3_tiff extends getid3_handler { - function getid3_tiff(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); - $TIFFheader = fread($fd, 4); + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $TIFFheader = fread($this->getid3->fp, 4); switch (substr($TIFFheader, 0, 2)) { case 'II': - $ThisFileInfo['tiff']['byte_order'] = 'Intel'; + $info['tiff']['byte_order'] = 'Intel'; break; case 'MM': - $ThisFileInfo['tiff']['byte_order'] = 'Motorola'; + $info['tiff']['byte_order'] = 'Motorola'; break; default: - $ThisFileInfo['error'][] = 'Invalid TIFF byte order identifier ('.substr($TIFFheader, 0, 2).') at offset '.$ThisFileInfo['avdataoffset']; + $info['error'][] = 'Invalid TIFF byte order identifier ('.substr($TIFFheader, 0, 2).') at offset '.$info['avdataoffset']; return false; break; } - $ThisFileInfo['fileformat'] = 'tiff'; - $ThisFileInfo['video']['dataformat'] = 'tiff'; - $ThisFileInfo['video']['lossless'] = true; - $ThisFileInfo['tiff']['ifd'] = array(); + $info['fileformat'] = 'tiff'; + $info['video']['dataformat'] = 'tiff'; + $info['video']['lossless'] = true; + $info['tiff']['ifd'] = array(); $CurrentIFD = array(); $FieldTypeByteLength = array(1=>1, 2=>1, 3=>2, 4=>4, 5=>8); - $nextIFDoffset = $this->TIFFendian2Int(fread($fd, 4), $ThisFileInfo['tiff']['byte_order']); + $nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']); while ($nextIFDoffset > 0) { $CurrentIFD['offset'] = $nextIFDoffset; - fseek($fd, $ThisFileInfo['avdataoffset'] + $nextIFDoffset, SEEK_SET); - $CurrentIFD['fieldcount'] = $this->TIFFendian2Int(fread($fd, 2), $ThisFileInfo['tiff']['byte_order']); + fseek($this->getid3->fp, $info['avdataoffset'] + $nextIFDoffset, SEEK_SET); + $CurrentIFD['fieldcount'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']); for ($i = 0; $i < $CurrentIFD['fieldcount']; $i++) { - $CurrentIFD['fields'][$i]['raw']['tag'] = $this->TIFFendian2Int(fread($fd, 2), $ThisFileInfo['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int(fread($fd, 2), $ThisFileInfo['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int(fread($fd, 4), $ThisFileInfo['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['offset'] = fread($fd, 4); + $CurrentIFD['fields'][$i]['raw']['tag'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['offset'] = fread($this->getid3->fp, 4); switch ($CurrentIFD['fields'][$i]['raw']['type']) { case 1: // BYTE An 8-bit unsigned integer. if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) { - $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 1), $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 1), $info['tiff']['byte_order']); } else { - $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']); } break; @@ -71,23 +72,23 @@ class getid3_tiff if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) { $CurrentIFD['fields'][$i]['value'] = substr($CurrentIFD['fields'][$i]['raw']['offset'], 3); } else { - $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']); } break; case 3: // SHORT A 16-bit (2-byte) unsigned integer. if ($CurrentIFD['fields'][$i]['raw']['length'] <= 2) { - $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 2), $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 2), $info['tiff']['byte_order']); } else { - $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']); } break; case 4: // LONG A 32-bit (4-byte) unsigned integer. if ($CurrentIFD['fields'][$i]['raw']['length'] <= 1) { - $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']); } else { - $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']); } break; @@ -96,13 +97,13 @@ class getid3_tiff } } - $ThisFileInfo['tiff']['ifd'][] = $CurrentIFD; + $info['tiff']['ifd'][] = $CurrentIFD; $CurrentIFD = array(); - $nextIFDoffset = $this->TIFFendian2Int(fread($fd, 4), $ThisFileInfo['tiff']['byte_order']); + $nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']); } - foreach ($ThisFileInfo['tiff']['ifd'] as $IFDid => $IFDarray) { + foreach ($info['tiff']['ifd'] as $IFDid => $IFDarray) { foreach ($IFDarray['fields'] as $key => $fieldarray) { switch ($fieldarray['raw']['tag']) { case 256: // ImageWidth @@ -110,8 +111,8 @@ class getid3_tiff case 258: // BitsPerSample case 259: // Compression if (!isset($fieldarray['value'])) { - fseek($fd, $fieldarray['offset'], SEEK_SET); - $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($fd, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); + fseek($this->getid3->fp, $fieldarray['offset'], SEEK_SET); + $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($this->getid3->fp, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); } break; @@ -124,36 +125,36 @@ class getid3_tiff case 315: // Artist case 316: // HostComputer if (isset($fieldarray['value'])) { - $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value']; + $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value']; } else { - fseek($fd, $fieldarray['offset'], SEEK_SET); - $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($fd, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); + fseek($this->getid3->fp, $fieldarray['offset'], SEEK_SET); + $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($this->getid3->fp, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); } break; } switch ($fieldarray['raw']['tag']) { case 256: // ImageWidth - $ThisFileInfo['video']['resolution_x'] = $fieldarray['value']; + $info['video']['resolution_x'] = $fieldarray['value']; break; case 257: // ImageLength - $ThisFileInfo['video']['resolution_y'] = $fieldarray['value']; + $info['video']['resolution_y'] = $fieldarray['value']; break; case 258: // BitsPerSample if (isset($fieldarray['value'])) { - $ThisFileInfo['video']['bits_per_sample'] = $fieldarray['value']; + $info['video']['bits_per_sample'] = $fieldarray['value']; } else { - $ThisFileInfo['video']['bits_per_sample'] = 0; + $info['video']['bits_per_sample'] = 0; for ($i = 0; $i < $fieldarray['raw']['length']; $i++) { - $ThisFileInfo['video']['bits_per_sample'] += $this->TIFFendian2Int(substr($ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'], $i * $FieldTypeByteLength[$fieldarray['raw']['type']], $FieldTypeByteLength[$fieldarray['raw']['type']]), $ThisFileInfo['tiff']['byte_order']); + $info['video']['bits_per_sample'] += $this->TIFFendian2Int(substr($info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'], $i * $FieldTypeByteLength[$fieldarray['raw']['type']], $FieldTypeByteLength[$fieldarray['raw']['type']]), $info['tiff']['byte_order']); } } break; case 259: // Compression - $ThisFileInfo['video']['codec'] = $this->TIFFcompressionMethod($fieldarray['value']); + $info['video']['codec'] = $this->TIFFcompressionMethod($fieldarray['value']); break; case 270: // ImageDescription @@ -163,7 +164,12 @@ class getid3_tiff case 306: // DateTime case 315: // Artist case 316: // HostComputer - @$ThisFileInfo['tiff']['comments'][$this->TIFFcommentName($fieldarray['raw']['tag'])][] = $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data']; + $TIFFcommentName = $this->TIFFcommentName($fieldarray['raw']['tag']); + if (isset($info['tiff']['comments'][$TIFFcommentName])) { + $info['tiff']['comments'][$TIFFcommentName][] = $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data']; + } else { + $info['tiff']['comments'][$TIFFcommentName] = array($info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data']); + } break; default: diff --git a/3rdparty/getid3/module.misc.cue.php b/3rdparty/getid3/module.misc.cue.php new file mode 100644 index 0000000000..c99ce24798 --- /dev/null +++ b/3rdparty/getid3/module.misc.cue.php @@ -0,0 +1,312 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.misc.cue.php // +// module for analyzing CUEsheet files // +// dependencies: NONE // +// // +///////////////////////////////////////////////////////////////// +// // +// Module originally written [2009-Mar-25] by // +// Nigel Barnes // +// Minor reformatting and similar small changes to integrate // +// into getID3 by James Heinrich // +// /// +///////////////////////////////////////////////////////////////// + +/* + * CueSheet parser by Nigel Barnes. + * + * This is a PHP conversion of CueSharp 0.5 by Wyatt O'Day (wyday.com/cuesharp) + */ + +/** + * A CueSheet class used to open and parse cuesheets. + * + */ +class getid3_cue extends getid3_handler +{ + var $cuesheet = array(); + + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'cue'; + $this->readCueSheetFilename($info['filenamepath']); + $info['cue'] = $this->cuesheet; + return true; + } + + + + function readCueSheetFilename($filename) + { + $filedata = file_get_contents($filename); + return $this->readCueSheet($filedata); + } + /** + * Parses a cue sheet file. + * + * @param string $filename - The filename for the cue sheet to open. + */ + function readCueSheet(&$filedata) + { + $cue_lines = array(); + foreach (explode("\n", str_replace("\r", null, $filedata)) as $line) + { + if ( (strlen($line) > 0) && ($line[0] != '#')) + { + $cue_lines[] = trim($line); + } + } + $this->parseCueSheet($cue_lines); + + return $this->cuesheet; + } + + /** + * Parses the cue sheet array. + * + * @param array $file - The cuesheet as an array of each line. + */ + function parseCueSheet($file) + { + //-1 means still global, all others are track specific + $track_on = -1; + + for ($i=0; $i < count($file); $i++) + { + list($key) = explode(' ', strtolower($file[$i]), 2); + switch ($key) + { + case 'catalog': + case 'cdtextfile': + case 'isrc': + case 'performer': + case 'songwriter': + case 'title': + $this->parseString($file[$i], $track_on); + break; + case 'file': + $currentFile = $this->parseFile($file[$i]); + break; + case 'flags': + $this->parseFlags($file[$i], $track_on); + break; + case 'index': + case 'postgap': + case 'pregap': + $this->parseIndex($file[$i], $track_on); + break; + case 'rem': + $this->parseComment($file[$i], $track_on); + break; + case 'track': + $track_on++; + $this->parseTrack($file[$i], $track_on); + if (isset($currentFile)) // if there's a file + { + $this->cuesheet['tracks'][$track_on]['datafile'] = $currentFile; + } + break; + default: + //save discarded junk and place string[] with track it was found in + $this->parseGarbage($file[$i], $track_on); + break; + } + } + } + + /** + * Parses the REM command. + * + * @param string $line - The line in the cue file that contains the TRACK command. + * @param integer $track_on - The track currently processing. + */ + function parseComment($line, $track_on) + { + $explodedline = explode(' ', $line, 3); + $comment_REM = (isset($explodedline[0]) ? $explodedline[0] : ''); + $comment_type = (isset($explodedline[1]) ? $explodedline[1] : ''); + $comment_data = (isset($explodedline[2]) ? $explodedline[2] : ''); + if (($comment_REM == 'REM') && $comment_type) { + $comment_type = strtolower($comment_type); + $commment_data = trim($comment_data, ' "'); + if ($track_on != -1) { + $this->cuesheet['tracks'][$track_on]['comments'][$comment_type][] = $comment_data; + } else { + $this->cuesheet['comments'][$comment_type][] = $comment_data; + } + } + } + + /** + * Parses the FILE command. + * + * @param string $line - The line in the cue file that contains the FILE command. + * @return array - Array of FILENAME and TYPE of file.. + */ + function parseFile($line) + { + $line = substr($line, strpos($line, ' ') + 1); + $type = strtolower(substr($line, strrpos($line, ' '))); + + //remove type + $line = substr($line, 0, strrpos($line, ' ') - 1); + + //if quotes around it, remove them. + $line = trim($line, '"'); + + return array('filename'=>$line, 'type'=>$type); + } + + /** + * Parses the FLAG command. + * + * @param string $line - The line in the cue file that contains the TRACK command. + * @param integer $track_on - The track currently processing. + */ + function parseFlags($line, $track_on) + { + if ($track_on != -1) + { + foreach (explode(' ', strtolower($line)) as $type) + { + switch ($type) + { + case 'flags': + // first entry in this line + $this->cuesheet['tracks'][$track_on]['flags'] = array( + '4ch' => false, + 'data' => false, + 'dcp' => false, + 'pre' => false, + 'scms' => false, + ); + break; + case 'data': + case 'dcp': + case '4ch': + case 'pre': + case 'scms': + $this->cuesheet['tracks'][$track_on]['flags'][$type] = true; + break; + default: + break; + } + } + } + } + + /** + * Collect any unidentified data. + * + * @param string $line - The line in the cue file that contains the TRACK command. + * @param integer $track_on - The track currently processing. + */ + function parseGarbage($line, $track_on) + { + if ( strlen($line) > 0 ) + { + if ($track_on == -1) + { + $this->cuesheet['garbage'][] = $line; + } + else + { + $this->cuesheet['tracks'][$track_on]['garbage'][] = $line; + } + } + } + + /** + * Parses the INDEX command of a TRACK. + * + * @param string $line - The line in the cue file that contains the TRACK command. + * @param integer $track_on - The track currently processing. + */ + function parseIndex($line, $track_on) + { + $type = strtolower(substr($line, 0, strpos($line, ' '))); + $line = substr($line, strpos($line, ' ') + 1); + + if ($type == 'index') + { + //read the index number + $number = intval(substr($line, 0, strpos($line, ' '))); + $line = substr($line, strpos($line, ' ') + 1); + } + + //extract the minutes, seconds, and frames + $explodedline = explode(':', $line); + $minutes = (isset($explodedline[0]) ? $explodedline[0] : ''); + $seconds = (isset($explodedline[1]) ? $explodedline[1] : ''); + $frames = (isset($explodedline[2]) ? $explodedline[2] : ''); + + switch ($type) { + case 'index': + $this->cuesheet['tracks'][$track_on][$type][$number] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames)); + break; + case 'pregap': + case 'postgap': + $this->cuesheet['tracks'][$track_on][$type] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames)); + break; + } + } + + function parseString($line, $track_on) + { + $category = strtolower(substr($line, 0, strpos($line, ' '))); + $line = substr($line, strpos($line, ' ') + 1); + + //get rid of the quotes + $line = trim($line, '"'); + + switch ($category) + { + case 'catalog': + case 'cdtextfile': + case 'isrc': + case 'performer': + case 'songwriter': + case 'title': + if ($track_on == -1) + { + $this->cuesheet[$category] = $line; + } + else + { + $this->cuesheet['tracks'][$track_on][$category] = $line; + } + break; + default: + break; + } + } + + /** + * Parses the TRACK command. + * + * @param string $line - The line in the cue file that contains the TRACK command. + * @param integer $track_on - The track currently processing. + */ + function parseTrack($line, $track_on) + { + $line = substr($line, strpos($line, ' ') + 1); + $track = ltrim(substr($line, 0, strpos($line, ' ')), '0'); + + //find the data type. + $datatype = strtolower(substr($line, strpos($line, ' ') + 1)); + + $this->cuesheet['tracks'][$track_on] = array('track_number'=>$track, 'datatype'=>$datatype); + } + +} + +?> diff --git a/3rdparty/getid3/module.misc.exe.php b/3rdparty/getid3/module.misc.exe.php new file mode 100644 index 0000000000..108e62ecd7 --- /dev/null +++ b/3rdparty/getid3/module.misc.exe.php @@ -0,0 +1,61 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.misc.exe.php // +// module for analyzing EXE files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_exe extends getid3_handler +{ + + function Analyze() { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $EXEheader = fread($this->getid3->fp, 28); + + $magic = 'MZ'; + if (substr($EXEheader, 0, 2) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($EXEheader, 0, 2)).'"'; + return false; + } + + $info['fileformat'] = 'exe'; + $info['exe']['mz']['magic'] = 'MZ'; + + $info['exe']['mz']['raw']['last_page_size'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 2, 2)); + $info['exe']['mz']['raw']['page_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 4, 2)); + $info['exe']['mz']['raw']['relocation_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 6, 2)); + $info['exe']['mz']['raw']['header_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 8, 2)); + $info['exe']['mz']['raw']['min_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 10, 2)); + $info['exe']['mz']['raw']['max_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 12, 2)); + $info['exe']['mz']['raw']['initial_ss'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 14, 2)); + $info['exe']['mz']['raw']['initial_sp'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 16, 2)); + $info['exe']['mz']['raw']['checksum'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 18, 2)); + $info['exe']['mz']['raw']['cs_ip'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 20, 4)); + $info['exe']['mz']['raw']['relocation_table_offset'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 24, 2)); + $info['exe']['mz']['raw']['overlay_number'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 26, 2)); + + $info['exe']['mz']['byte_size'] = (($info['exe']['mz']['raw']['page_count'] - 1)) * 512 + $info['exe']['mz']['raw']['last_page_size']; + $info['exe']['mz']['header_size'] = $info['exe']['mz']['raw']['header_paragraphs'] * 16; + $info['exe']['mz']['memory_minimum'] = $info['exe']['mz']['raw']['min_memory_paragraphs'] * 16; + $info['exe']['mz']['memory_recommended'] = $info['exe']['mz']['raw']['max_memory_paragraphs'] * 16; + +$info['error'][] = 'EXE parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; +return false; + + } + +} + + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/module.misc.iso.php b/3rdparty/getid3/module.misc.iso.php similarity index 79% rename from apps/media/getID3/getid3/module.misc.iso.php rename to 3rdparty/getid3/module.misc.iso.php index 94df9294f4..727fdf8701 100644 --- a/apps/media/getID3/getid3/module.misc.iso.php +++ b/3rdparty/getid3/module.misc.iso.php @@ -14,25 +14,27 @@ ///////////////////////////////////////////////////////////////// -class getid3_iso +class getid3_iso extends getid3_handler { - function getid3_iso($fd, &$ThisFileInfo) { - $ThisFileInfo['fileformat'] = 'iso'; + function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'iso'; for ($i = 16; $i <= 19; $i++) { - fseek($fd, 2048 * $i, SEEK_SET); - $ISOheader = fread($fd, 2048); + fseek($this->getid3->fp, 2048 * $i, SEEK_SET); + $ISOheader = fread($this->getid3->fp, 2048); if (substr($ISOheader, 1, 5) == 'CD001') { switch (ord($ISOheader{0})) { case 1: - $ThisFileInfo['iso']['primary_volume_descriptor']['offset'] = 2048 * $i; - $this->ParsePrimaryVolumeDescriptor($ISOheader, $ThisFileInfo); + $info['iso']['primary_volume_descriptor']['offset'] = 2048 * $i; + $this->ParsePrimaryVolumeDescriptor($ISOheader); break; case 2: - $ThisFileInfo['iso']['supplementary_volume_descriptor']['offset'] = 2048 * $i; - $this->ParseSupplementaryVolumeDescriptor($ISOheader, $ThisFileInfo); + $info['iso']['supplementary_volume_descriptor']['offset'] = 2048 * $i; + $this->ParseSupplementaryVolumeDescriptor($ISOheader); break; default: @@ -42,35 +44,33 @@ class getid3_iso } } - $this->ParsePathTable($fd, $ThisFileInfo); - - $ThisFileInfo['iso']['files'] = array(); - foreach ($ThisFileInfo['iso']['path_table']['directories'] as $directorynum => $directorydata) { - - $ThisFileInfo['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($fd, $directorydata, $ThisFileInfo); + $this->ParsePathTable(); + $info['iso']['files'] = array(); + foreach ($info['iso']['path_table']['directories'] as $directorynum => $directorydata) { + $info['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($directorydata); } return true; - } - function ParsePrimaryVolumeDescriptor(&$ISOheader, &$ThisFileInfo) { + function ParsePrimaryVolumeDescriptor(&$ISOheader) { // ISO integer values are stored *BOTH* Little-Endian AND Big-Endian format!! // ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field // shortcuts - $ThisFileInfo['iso']['primary_volume_descriptor']['raw'] = array(); - $thisfile_iso_primaryVD = &$ThisFileInfo['iso']['primary_volume_descriptor']; + $info = &$this->getid3->info; + $info['iso']['primary_volume_descriptor']['raw'] = array(); + $thisfile_iso_primaryVD = &$info['iso']['primary_volume_descriptor']; $thisfile_iso_primaryVD_raw = &$thisfile_iso_primaryVD['raw']; $thisfile_iso_primaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1)); $thisfile_iso_primaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5); if ($thisfile_iso_primaryVD_raw['standard_identifier'] != 'CD001') { - $ThisFileInfo['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_primaryVD['offset'] + 1).'), found "'.$thisfile_iso_primaryVD_raw['standard_identifier'].'" instead'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['iso']); + $info['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_primaryVD['offset'] + 1).'), found "'.$thisfile_iso_primaryVD_raw['standard_identifier'].'" instead'; + unset($info['fileformat']); + unset($info['iso']); return false; } @@ -121,29 +121,30 @@ class getid3_iso $thisfile_iso_primaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_expiration_date_time']); $thisfile_iso_primaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_effective_date_time']); - if (($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048) > $ThisFileInfo['filesize']) { - $ThisFileInfo['error'][] = 'Volume Space Size ('.($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048).' bytes) is larger than the file size ('.$ThisFileInfo['filesize'].' bytes) (truncated file?)'; + if (($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048) > $info['filesize']) { + $info['error'][] = 'Volume Space Size ('.($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048).' bytes) is larger than the file size ('.$info['filesize'].' bytes) (truncated file?)'; } return true; } - function ParseSupplementaryVolumeDescriptor(&$ISOheader, &$ThisFileInfo) { + function ParseSupplementaryVolumeDescriptor(&$ISOheader) { // ISO integer values are stored Both-Endian format!! // ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field // shortcuts - $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw'] = array(); - $thisfile_iso_supplementaryVD = &$ThisFileInfo['iso']['supplementary_volume_descriptor']; + $info = &$this->getid3->info; + $info['iso']['supplementary_volume_descriptor']['raw'] = array(); + $thisfile_iso_supplementaryVD = &$info['iso']['supplementary_volume_descriptor']; $thisfile_iso_supplementaryVD_raw = &$thisfile_iso_supplementaryVD['raw']; $thisfile_iso_supplementaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1)); $thisfile_iso_supplementaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5); if ($thisfile_iso_supplementaryVD_raw['standard_identifier'] != 'CD001') { - $ThisFileInfo['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_supplementaryVD['offset'] + 1).'), found "'.$thisfile_iso_supplementaryVD_raw['standard_identifier'].'" instead'; - unset($ThisFileInfo['fileformat']); - unset($ThisFileInfo['iso']); + $info['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_supplementaryVD['offset'] + 1).'), found "'.$thisfile_iso_supplementaryVD_raw['standard_identifier'].'" instead'; + unset($info['fileformat']); + unset($info['iso']); return false; } @@ -199,62 +200,63 @@ class getid3_iso $thisfile_iso_supplementaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_expiration_date_time']); $thisfile_iso_supplementaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_effective_date_time']); - if (($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']) > $ThisFileInfo['filesize']) { - $ThisFileInfo['error'][] = 'Volume Space Size ('.($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']).' bytes) is larger than the file size ('.$ThisFileInfo['filesize'].' bytes) (truncated file?)'; + if (($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']) > $info['filesize']) { + $info['error'][] = 'Volume Space Size ('.($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']).' bytes) is larger than the file size ('.$info['filesize'].' bytes) (truncated file?)'; } return true; } - function ParsePathTable($fd, &$ThisFileInfo) { - if (!isset($ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']) && !isset($ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_l_location'])) { + function ParsePathTable() { + $info = &$this->getid3->info; + if (!isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']) && !isset($info['iso']['primary_volume_descriptor']['raw']['path_table_l_location'])) { return false; } - if (isset($ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'])) { - $PathTableLocation = $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']; - $PathTableSize = $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_size']; + if (isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'])) { + $PathTableLocation = $info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']; + $PathTableSize = $info['iso']['supplementary_volume_descriptor']['raw']['path_table_size']; $TextEncoding = 'UTF-16BE'; // Big-Endian Unicode } else { - $PathTableLocation = $ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_l_location']; - $PathTableSize = $ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_size']; + $PathTableLocation = $info['iso']['primary_volume_descriptor']['raw']['path_table_l_location']; + $PathTableSize = $info['iso']['primary_volume_descriptor']['raw']['path_table_size']; $TextEncoding = 'ISO-8859-1'; // Latin-1 } - if (($PathTableLocation * 2048) > $ThisFileInfo['filesize']) { - $ThisFileInfo['error'][] = 'Path Table Location specifies an offset ('.($PathTableLocation * 2048).') beyond the end-of-file ('.$ThisFileInfo['filesize'].')'; + if (($PathTableLocation * 2048) > $info['filesize']) { + $info['error'][] = 'Path Table Location specifies an offset ('.($PathTableLocation * 2048).') beyond the end-of-file ('.$info['filesize'].')'; return false; } - $ThisFileInfo['iso']['path_table']['offset'] = $PathTableLocation * 2048; - fseek($fd, $ThisFileInfo['iso']['path_table']['offset'], SEEK_SET); - $ThisFileInfo['iso']['path_table']['raw'] = fread($fd, $PathTableSize); + $info['iso']['path_table']['offset'] = $PathTableLocation * 2048; + fseek($this->getid3->fp, $info['iso']['path_table']['offset'], SEEK_SET); + $info['iso']['path_table']['raw'] = fread($this->getid3->fp, $PathTableSize); $offset = 0; $pathcounter = 1; while ($offset < $PathTableSize) { // shortcut - $ThisFileInfo['iso']['path_table']['directories'][$pathcounter] = array(); - $thisfile_iso_pathtable_directories_current = &$ThisFileInfo['iso']['path_table']['directories'][$pathcounter]; + $info['iso']['path_table']['directories'][$pathcounter] = array(); + $thisfile_iso_pathtable_directories_current = &$info['iso']['path_table']['directories'][$pathcounter]; - $thisfile_iso_pathtable_directories_current['length'] = getid3_lib::LittleEndian2Int(substr($ThisFileInfo['iso']['path_table']['raw'], $offset, 1)); + $thisfile_iso_pathtable_directories_current['length'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 1)); $offset += 1; - $thisfile_iso_pathtable_directories_current['extended_length'] = getid3_lib::LittleEndian2Int(substr($ThisFileInfo['iso']['path_table']['raw'], $offset, 1)); + $thisfile_iso_pathtable_directories_current['extended_length'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 1)); $offset += 1; - $thisfile_iso_pathtable_directories_current['location_logical'] = getid3_lib::LittleEndian2Int(substr($ThisFileInfo['iso']['path_table']['raw'], $offset, 4)); + $thisfile_iso_pathtable_directories_current['location_logical'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 4)); $offset += 4; - $thisfile_iso_pathtable_directories_current['parent_directory'] = getid3_lib::LittleEndian2Int(substr($ThisFileInfo['iso']['path_table']['raw'], $offset, 2)); + $thisfile_iso_pathtable_directories_current['parent_directory'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 2)); $offset += 2; - $thisfile_iso_pathtable_directories_current['name'] = substr($ThisFileInfo['iso']['path_table']['raw'], $offset, $thisfile_iso_pathtable_directories_current['length']); + $thisfile_iso_pathtable_directories_current['name'] = substr($info['iso']['path_table']['raw'], $offset, $thisfile_iso_pathtable_directories_current['length']); $offset += $thisfile_iso_pathtable_directories_current['length'] + ($thisfile_iso_pathtable_directories_current['length'] % 2); - $thisfile_iso_pathtable_directories_current['name_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $ThisFileInfo['encoding'], $thisfile_iso_pathtable_directories_current['name']); + $thisfile_iso_pathtable_directories_current['name_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $info['encoding'], $thisfile_iso_pathtable_directories_current['name']); $thisfile_iso_pathtable_directories_current['location_bytes'] = $thisfile_iso_pathtable_directories_current['location_logical'] * 2048; if ($pathcounter == 1) { $thisfile_iso_pathtable_directories_current['full_path'] = '/'; } else { - $thisfile_iso_pathtable_directories_current['full_path'] = $ThisFileInfo['iso']['path_table']['directories'][$thisfile_iso_pathtable_directories_current['parent_directory']]['full_path'].$thisfile_iso_pathtable_directories_current['name_ascii'].'/'; + $thisfile_iso_pathtable_directories_current['full_path'] = $info['iso']['path_table']['directories'][$thisfile_iso_pathtable_directories_current['parent_directory']]['full_path'].$thisfile_iso_pathtable_directories_current['name_ascii'].'/'; } $FullPathArray[] = $thisfile_iso_pathtable_directories_current['full_path']; @@ -265,19 +267,20 @@ class getid3_iso } - function ParseDirectoryRecord(&$fd, $directorydata, &$ThisFileInfo) { - if (isset($ThisFileInfo['iso']['supplementary_volume_descriptor'])) { + function ParseDirectoryRecord($directorydata) { + $info = &$this->getid3->info; + if (isset($info['iso']['supplementary_volume_descriptor'])) { $TextEncoding = 'UTF-16BE'; // Big-Endian Unicode } else { $TextEncoding = 'ISO-8859-1'; // Latin-1 } - fseek($fd, $directorydata['location_bytes'], SEEK_SET); - $DirectoryRecordData = fread($fd, 1); + fseek($this->getid3->fp, $directorydata['location_bytes'], SEEK_SET); + $DirectoryRecordData = fread($this->getid3->fp, 1); while (ord($DirectoryRecordData{0}) > 33) { - $DirectoryRecordData .= fread($fd, ord($DirectoryRecordData{0}) - 1); + $DirectoryRecordData .= fread($this->getid3->fp, ord($DirectoryRecordData{0}) - 1); $ThisDirectoryRecord['raw']['length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 0, 1)); $ThisDirectoryRecord['raw']['extended_attribute_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 1, 1)); @@ -291,7 +294,7 @@ class getid3_iso $ThisDirectoryRecord['raw']['file_identifier_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 32, 1)); $ThisDirectoryRecord['raw']['file_identifier'] = substr($DirectoryRecordData, 33, $ThisDirectoryRecord['raw']['file_identifier_length']); - $ThisDirectoryRecord['file_identifier_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $ThisFileInfo['encoding'], $ThisDirectoryRecord['raw']['file_identifier']); + $ThisDirectoryRecord['file_identifier_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $info['encoding'], $ThisDirectoryRecord['raw']['file_identifier']); $ThisDirectoryRecord['filesize'] = $ThisDirectoryRecord['raw']['filesize']; $ThisDirectoryRecord['offset_bytes'] = $ThisDirectoryRecord['raw']['offset_logical'] * 2048; @@ -307,11 +310,11 @@ class getid3_iso $ThisDirectoryRecord['filename'] = $directorydata['full_path']; } else { $ThisDirectoryRecord['filename'] = $directorydata['full_path'].$this->ISOstripFilenameVersion($ThisDirectoryRecord['file_identifier_ascii']); - $ThisFileInfo['iso']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['iso']['files'], getid3_lib::CreateDeepArray($ThisDirectoryRecord['filename'], '/', $ThisDirectoryRecord['filesize'])); + $info['iso']['files'] = getid3_lib::array_merge_clobber($info['iso']['files'], getid3_lib::CreateDeepArray($ThisDirectoryRecord['filename'], '/', $ThisDirectoryRecord['filesize'])); } $DirectoryRecord[] = $ThisDirectoryRecord; - $DirectoryRecordData = fread($fd, 1); + $DirectoryRecordData = fread($this->getid3->fp, 1); } return $DirectoryRecord; diff --git a/apps/media/getID3/getid3/module.misc.msoffice.php b/3rdparty/getid3/module.misc.msoffice.php similarity index 56% rename from apps/media/getID3/getid3/module.misc.msoffice.php rename to 3rdparty/getid3/module.misc.msoffice.php index cb65abf223..8fea108534 100644 --- a/apps/media/getID3/getid3/module.misc.msoffice.php +++ b/3rdparty/getid3/module.misc.msoffice.php @@ -14,15 +14,23 @@ ///////////////////////////////////////////////////////////////// -class getid3_doc +class getid3_msoffice extends getid3_handler { - function getid3_doc(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'doc'; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $DOCFILEheader = fread($this->getid3->fp, 8); + $magic = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"; + if (substr($DOCFILEheader, 0, 8) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($DOCFILEheader, 0, 8)).' instead.'; + return false; + } + $info['fileformat'] = 'msoffice'; - $ThisFileInfo['error'][] = 'MS Office (.doc, .xls, etc) parsing not enabled in this version of getID3()'; - return false; +$info['error'][] = 'MS Office (.doc, .xls, etc) parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; +return false; } diff --git a/apps/media/getID3/getid3/module.misc.par2.php b/3rdparty/getid3/module.misc.par2.php similarity index 81% rename from apps/media/getID3/getid3/module.misc.par2.php rename to 3rdparty/getid3/module.misc.par2.php index 9f0e22180f..d8b0a388b7 100644 --- a/apps/media/getID3/getid3/module.misc.par2.php +++ b/3rdparty/getid3/module.misc.par2.php @@ -14,14 +14,15 @@ ///////////////////////////////////////////////////////////////// -class getid3_par2 +class getid3_par2 extends getid3_handler { - function getid3_par2(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'par2'; + $info['fileformat'] = 'par2'; - $ThisFileInfo['error'][] = 'PAR2 parsing not enabled in this version of getID3()'; + $info['error'][] = 'PAR2 parsing not enabled in this version of getID3()'; return false; } diff --git a/apps/media/getID3/getid3/module.misc.pdf.php b/3rdparty/getid3/module.misc.pdf.php similarity index 79% rename from apps/media/getID3/getid3/module.misc.pdf.php rename to 3rdparty/getid3/module.misc.pdf.php index c6e3294b69..38ed646a9e 100644 --- a/apps/media/getID3/getid3/module.misc.pdf.php +++ b/3rdparty/getid3/module.misc.pdf.php @@ -14,14 +14,15 @@ ///////////////////////////////////////////////////////////////// -class getid3_pdf +class getid3_pdf extends getid3_handler { - function getid3_pdf(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - $ThisFileInfo['fileformat'] = 'pdf'; + $info['fileformat'] = 'pdf'; - $ThisFileInfo['error'][] = 'PDF parsing not enabled in this version of getID3()'; + $info['error'][] = 'PDF parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; return false; } diff --git a/apps/media/getID3/getid3/module.tag.apetag.php b/3rdparty/getid3/module.tag.apetag.php similarity index 54% rename from apps/media/getID3/getid3/module.tag.apetag.php rename to 3rdparty/getid3/module.tag.apetag.php index 2b67e14b4a..6522475f51 100644 --- a/apps/media/getID3/getid3/module.tag.apetag.php +++ b/3rdparty/getid3/module.tag.apetag.php @@ -13,13 +13,16 @@ // /// ///////////////////////////////////////////////////////////////// -class getid3_apetag +class getid3_apetag extends getid3_handler { + var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory + var $overrideendoffset = 0; - function getid3_apetag(&$fd, &$ThisFileInfo, $overrideendoffset=0) { + function Analyze() { + $info = &$this->getid3->info; - if ($ThisFileInfo['filesize'] >= pow(2, 31)) { - $ThisFileInfo['warning'][] = 'Unable to check for APEtags because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($info['filesize'])) { + $info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; return false; } @@ -27,69 +30,69 @@ class getid3_apetag $apetagheadersize = 32; $lyrics3tagsize = 10; - if ($overrideendoffset == 0) { + if ($this->overrideendoffset == 0) { - fseek($fd, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END); - $APEfooterID3v1 = fread($fd, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize); + fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END); + $APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize); //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) { if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') { // APE tag found before ID3v1 - $ThisFileInfo['ape']['tag_offset_end'] = $ThisFileInfo['filesize'] - $id3v1tagsize; + $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize; //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) { } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') { // APE tag found, no ID3v1 - $ThisFileInfo['ape']['tag_offset_end'] = $ThisFileInfo['filesize']; + $info['ape']['tag_offset_end'] = $info['filesize']; } } else { - fseek($fd, $overrideendoffset - $apetagheadersize, SEEK_SET); - if (fread($fd, 8) == 'APETAGEX') { - $ThisFileInfo['ape']['tag_offset_end'] = $overrideendoffset; + fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET); + if (fread($this->getid3->fp, 8) == 'APETAGEX') { + $info['ape']['tag_offset_end'] = $this->overrideendoffset; } } - if (!isset($ThisFileInfo['ape']['tag_offset_end'])) { + if (!isset($info['ape']['tag_offset_end'])) { // APE tag not found - unset($ThisFileInfo['ape']); + unset($info['ape']); return false; } // shortcut - $thisfile_ape = &$ThisFileInfo['ape']; + $thisfile_ape = &$info['ape']; - fseek($fd, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET); - $APEfooterData = fread($fd, 32); + fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET); + $APEfooterData = fread($this->getid3->fp, 32); if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) { - $ThisFileInfo['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']; + $info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']; return false; } if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) { - fseek($fd, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET); - $thisfile_ape['tag_offset_start'] = ftell($fd); - $APEtagData = fread($fd, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize); + fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET); + $thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp); + $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize); } else { $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize']; - fseek($fd, $thisfile_ape['tag_offset_start'], SEEK_SET); - $APEtagData = fread($fd, $thisfile_ape['footer']['raw']['tagsize']); + fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET); + $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']); } - $ThisFileInfo['avdataend'] = $thisfile_ape['tag_offset_start']; + $info['avdataend'] = $thisfile_ape['tag_offset_start']; - if (isset($ThisFileInfo['id3v1']['tag_offset_start']) && ($ThisFileInfo['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) { - $ThisFileInfo['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data'; - unset($ThisFileInfo['id3v1']); - foreach ($ThisFileInfo['warning'] as $key => $value) { + if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) { + $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data'; + unset($info['id3v1']); + foreach ($info['warning'] as $key => $value) { if ($value == 'Some ID3v1 fields do not use NULL characters for padding') { - unset($ThisFileInfo['warning'][$key]); - sort($ThisFileInfo['warning']); + unset($info['warning'][$key]); + sort($info['warning']); break; } } @@ -100,14 +103,14 @@ class getid3_apetag if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) { $offset += $apetagheadersize; } else { - $ThisFileInfo['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']; + $info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']; return false; } } // shortcut - $ThisFileInfo['replay_gain'] = array(); - $thisfile_replaygain = &$ThisFileInfo['replay_gain']; + $info['replay_gain'] = array(); + $thisfile_replaygain = &$info['replay_gain']; for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) { $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); @@ -115,7 +118,7 @@ class getid3_apetag $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); $offset += 4; if (strstr(substr($APEtagData, $offset), "\x00") === false) { - $ThisFileInfo['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset); + $info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset); return false; } $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset; @@ -125,6 +128,8 @@ class getid3_apetag $thisfile_ape['items'][$item_key] = array(); $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key]; + $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset; + $offset += ($ItemKeyLength + 1); // skip 0x00 terminator $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size); $offset += $value_size; @@ -150,7 +155,7 @@ class getid3_apetag $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! $thisfile_replaygain['track']['originator'] = 'unspecified'; if ($thisfile_replaygain['track']['peak'] <= 0) { - $ThisFileInfo['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; } break; @@ -163,7 +168,7 @@ class getid3_apetag $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! $thisfile_replaygain['album']['originator'] = 'unspecified'; if ($thisfile_replaygain['album']['peak'] <= 0) { - $ThisFileInfo['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; } break; @@ -187,23 +192,100 @@ class getid3_apetag break; case 'tracknumber': - foreach ($thisfile_ape_items_current['data'] as $comment) { - $thisfile_ape['comments']['track'][] = $comment; + if (is_array($thisfile_ape_items_current['data'])) { + foreach ($thisfile_ape_items_current['data'] as $comment) { + $thisfile_ape['comments']['track'][] = $comment; + } } break; + case 'cover art (artist)': + case 'cover art (back)': + case 'cover art (band logo)': + case 'cover art (band)': + case 'cover art (colored fish)': + case 'cover art (composer)': + case 'cover art (conductor)': + case 'cover art (front)': + case 'cover art (icon)': + case 'cover art (illustration)': + case 'cover art (lead)': + case 'cover art (leaflet)': + case 'cover art (lyricist)': + case 'cover art (media)': + case 'cover art (movie scene)': + case 'cover art (other icon)': + case 'cover art (other)': + case 'cover art (performance)': + case 'cover art (publisher logo)': + case 'cover art (recording)': + case 'cover art (studio)': + // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html + list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2); + $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00"); + $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']); + + $thisfile_ape_items_current['image_mime'] = ''; + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo); + $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + + do { + if ($this->inline_attachments === false) { + // skip entirely + unset($thisfile_ape_items_current['data']); + break; + } + if ($this->inline_attachments === true) { + // great + } elseif (is_int($this->inline_attachments)) { + if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) { + // too big, skip + $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)'; + unset($thisfile_ape_items_current['data']); + break; + } + } elseif (is_string($this->inline_attachments)) { + $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { + // cannot write, skip + $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; + unset($thisfile_ape_items_current['data']); + break; + } + } + // if we get this far, must be OK + if (is_string($this->inline_attachments)) { + $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset']; + if (!file_exists($destination_filename) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $thisfile_ape_items_current['data']); + } else { + $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $thisfile_ape_items_current['data_filename'] = $destination_filename; + unset($thisfile_ape_items_current['data']); + } else { + if (!isset($info['ape']['comments']['picture'])) { + $info['ape']['comments']['picture'] = array(); + } + $info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']); + } + } while (false); + break; + default: - foreach ($thisfile_ape_items_current['data'] as $comment) { - $thisfile_ape['comments'][strtolower($item_key)][] = $comment; + if (is_array($thisfile_ape_items_current['data'])) { + foreach ($thisfile_ape_items_current['data'] as $comment) { + $thisfile_ape['comments'][strtolower($item_key)][] = $comment; + } } break; } } if (empty($thisfile_replaygain)) { - unset($ThisFileInfo['replay_gain']); + unset($info['replay_gain']); } - return true; } diff --git a/apps/media/getID3/getid3/module.tag.id3v1.php b/3rdparty/getid3/module.tag.id3v1.php similarity index 86% rename from apps/media/getID3/getid3/module.tag.id3v1.php rename to 3rdparty/getid3/module.tag.id3v1.php index 6fd2dd84a7..a9932d13f3 100644 --- a/apps/media/getID3/getid3/module.tag.id3v1.php +++ b/3rdparty/getid3/module.tag.id3v1.php @@ -14,23 +14,24 @@ ///////////////////////////////////////////////////////////////// -class getid3_id3v1 +class getid3_id3v1 extends getid3_handler { - function getid3_id3v1(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; - if ($ThisFileInfo['filesize'] >= pow(2, 31)) { - $ThisFileInfo['warning'][] = 'Unable to check for ID3v1 because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($info['filesize'])) { + $info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; return false; } - fseek($fd, -256, SEEK_END); - $preid3v1 = fread($fd, 128); - $id3v1tag = fread($fd, 128); + fseek($this->getid3->fp, -256, SEEK_END); + $preid3v1 = fread($this->getid3->fp, 128); + $id3v1tag = fread($this->getid3->fp, 128); if (substr($id3v1tag, 0, 3) == 'TAG') { - $ThisFileInfo['avdataend'] = $ThisFileInfo['filesize'] - 128; + $info['avdataend'] = $info['filesize'] - 128; $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30)); $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30)); @@ -51,7 +52,7 @@ class getid3_id3v1 if (!empty($ParsedID3v1['genre'])) { unset($ParsedID3v1['genreid']); } - if (empty($ParsedID3v1['genre']) || (@$ParsedID3v1['genre'] == 'Unknown')) { + if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) { unset($ParsedID3v1['genre']); } @@ -67,17 +68,17 @@ class getid3_id3v1 $ParsedID3v1['year'], (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false), $ParsedID3v1['comment'], - @$ParsedID3v1['track']); + (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : '')); $ParsedID3v1['padding_valid'] = true; if ($id3v1tag !== $GoodFormatID3v1tag) { $ParsedID3v1['padding_valid'] = false; - $ThisFileInfo['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding'; + $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding'; } - $ParsedID3v1['tag_offset_end'] = $ThisFileInfo['filesize']; + $ParsedID3v1['tag_offset_end'] = $info['filesize']; $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128; - $ThisFileInfo['id3v1'] = $ParsedID3v1; + $info['id3v1'] = $ParsedID3v1; } if (substr($preid3v1, 0, 3) == 'TAG') { @@ -93,19 +94,19 @@ class getid3_id3v1 // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch } else { // APE and Lyrics3 footers not found - assume double ID3v1 - $ThisFileInfo['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes'; - $ThisFileInfo['avdataend'] -= 128; + $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes'; + $info['avdataend'] -= 128; } } return true; } - function cutfield($str) { + static function cutfield($str) { return trim(substr($str, 0, strcspn($str, "\x00"))); } - function ArrayOfGenres($allowSCMPXextended=false) { + static function ArrayOfGenres($allowSCMPXextended=false) { static $GenreLookup = array( 0 => 'Blues', 1 => 'Classic Rock', @@ -251,7 +252,7 @@ class getid3_id3v1 141 => 'Christian Rock', 142 => 'Merengue', 143 => 'Salsa', - 144 => 'Trash Metal', + 144 => 'Thrash Metal', 145 => 'Anime', 146 => 'JPop', 147 => 'Synthpop', @@ -289,12 +290,15 @@ class getid3_id3v1 return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup); } - function LookupGenreName($genreid, $allowSCMPXextended=true) { + static function LookupGenreName($genreid, $allowSCMPXextended=true) { switch ($genreid) { case 'RX': case 'CR': break; default: + if (!is_numeric($genreid)) { + return false; + } $genreid = intval($genreid); // to handle 3 or '3' or '03' break; } @@ -302,28 +306,25 @@ class getid3_id3v1 return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); } - function LookupGenreID($genre, $allowSCMPXextended=false) { + static function LookupGenreID($genre, $allowSCMPXextended=false) { $GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended); $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); foreach ($GenreLookup as $key => $value) { - foreach ($GenreLookup as $key => $value) { - if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { - return $key; - } + if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { + return $key; } - return false; } - return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); + return false; } - function StandardiseID3v1GenreName($OriginalGenre) { + static function StandardiseID3v1GenreName($OriginalGenre) { if (($GenreID = getid3_id3v1::LookupGenreID($OriginalGenre)) !== false) { return getid3_id3v1::LookupGenreName($GenreID); } return $OriginalGenre; } - function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { + static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { $ID3v1Tag = 'TAG'; $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT); diff --git a/apps/media/getID3/getid3/module.tag.id3v2.php b/3rdparty/getid3/module.tag.id3v2.php similarity index 79% rename from apps/media/getID3/getid3/module.tag.id3v2.php rename to 3rdparty/getid3/module.tag.id3v2.php index 701d72800c..56adeb9506 100644 --- a/apps/media/getID3/getid3/module.tag.id3v2.php +++ b/3rdparty/getid3/module.tag.id3v2.php @@ -15,10 +15,14 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); -class getid3_id3v2 +class getid3_id3v2 extends getid3_handler { + var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory + var $StartingOffset = 0; + + function Analyze() { + $info = &$this->getid3->info; - function getid3_id3v2(&$fd, &$ThisFileInfo, $StartingOffset=0) { // Overall tag structure: // +-----------------------------+ // | Header (10 bytes) | @@ -42,14 +46,14 @@ class getid3_id3v2 // shortcuts - $ThisFileInfo['id3v2']['header'] = true; - $thisfile_id3v2 = &$ThisFileInfo['id3v2']; + $info['id3v2']['header'] = true; + $thisfile_id3v2 = &$info['id3v2']; $thisfile_id3v2['flags'] = array(); $thisfile_id3v2_flags = &$thisfile_id3v2['flags']; - fseek($fd, $StartingOffset, SEEK_SET); - $header = fread($fd, 10); + fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET); + $header = fread($this->getid3->fp, 10); if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { $thisfile_id3v2['majorversion'] = ord($header{3}); @@ -60,14 +64,14 @@ class getid3_id3v2 } else { - unset($ThisFileInfo['id3v2']); + unset($info['id3v2']); return false; } if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists) - $ThisFileInfo['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']; + $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']; return false; } @@ -98,7 +102,7 @@ class getid3_id3v2 $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length - $thisfile_id3v2['tag_offset_start'] = $StartingOffset; + $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset; $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength']; @@ -120,18 +124,18 @@ class getid3_id3v2 // Flags $xx xx $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header - if (@$thisfile_id3v2['exthead']['length']) { + if (!empty($thisfile_id3v2['exthead']['length'])) { $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4); } - if (@$thisfile_id3v2_flags['isfooter']) { + if (!empty($thisfile_id3v2_flags['isfooter'])) { $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio } if ($sizeofframes > 0) { - $framedata = fread($fd, $sizeofframes); // read all frames from file into $framedata variable + $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x) - if (@$thisfile_id3v2_flags['unsynch'] && ($id3v2_majorversion <= 3)) { + if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) { $framedata = $this->DeUnsynchronise($framedata); } // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead @@ -141,12 +145,12 @@ class getid3_id3v2 // the frame header [S:4.1.2] indicates unsynchronisation. - //$framedataoffset = 10 + (@$thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present) + //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present) $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header // Extended Header - if (@$thisfile_id3v2_flags['exthead']) { + if (!empty($thisfile_id3v2_flags['exthead'])) { $extended_header_offset = 0; if ($id3v2_majorversion == 3) { @@ -191,33 +195,56 @@ class getid3_id3v2 // d - Tag restrictions // Flag data length $01 - $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 1); + $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true); $extended_header_offset += 4; - $thisfile_id3v2['exthead']['flag_bytes'] = 1; + $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1 + $extended_header_offset += 1; + $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; - $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x4000); - $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x2000); - $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x1000); + $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40); + $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20); + $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10); + + if ($thisfile_id3v2['exthead']['flags']['update']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0 + $extended_header_offset += 1; + } if ($thisfile_id3v2['exthead']['flags']['crc']) { - $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 5), 1); - $extended_header_offset += 5; + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5 + $extended_header_offset += 1; + $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false); + $extended_header_offset += $ext_header_chunk_length; } + if ($thisfile_id3v2['exthead']['flags']['restrictions']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1 + $extended_header_offset += 1; + // %ppqrrstt $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); $extended_header_offset += 1; - $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw && 0xC0) >> 6; // p - Tag size restrictions - $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw && 0x20) >> 5; // q - Text encoding restrictions - $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw && 0x18) >> 3; // r - Text fields size restrictions - $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw && 0x04) >> 2; // s - Image encoding restrictions - $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw && 0x03) >> 0; // t - Image size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions + + $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']); } + if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) { + $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')'; + } } + $framedataoffset += $extended_header_offset; $framedata = substr($framedata, $extended_header_offset); } // end extended header @@ -233,7 +260,7 @@ class getid3_id3v2 if ($framedata{$i} != "\x00") { $thisfile_id3v2['padding']['valid'] = false; $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; - $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; break; } } @@ -273,7 +300,7 @@ class getid3_id3v2 } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) { // MP3ext known broken frames - "ok" for the purposes of this test } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) { - $ThisFileInfo['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'; + $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'; $id3v2_majorversion = 3; $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer } @@ -295,7 +322,7 @@ class getid3_id3v2 if ($framedata{$i} != "\x00") { $thisfile_id3v2['padding']['valid'] = false; $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; - $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; break; } } @@ -303,7 +330,7 @@ class getid3_id3v2 } if ($frame_name == 'COM ') { - $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; $frame_name = 'COMM'; } if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { @@ -315,7 +342,7 @@ class getid3_id3v2 $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size); $parsedFrame['dataoffset'] = $framedataoffset; - $this->ParseID3v2Frame($parsedFrame, $ThisFileInfo); + $this->ParseID3v2Frame($parsedFrame); $thisfile_id3v2[$frame_name][] = $parsedFrame; $framedata = substr($framedata, $frame_size); @@ -328,28 +355,28 @@ class getid3_id3v2 // next frame is valid, just skip the current frame $framedata = substr($framedata, $frame_size); - $ThisFileInfo['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; + $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; } else { // next frame is invalid too, abort processing //unset($framedata); $framedata = null; - $ThisFileInfo['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; + $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; } } elseif ($frame_size == strlen($framedata)) { // this is the last frame, just skip - $ThisFileInfo['warning'][] = 'This was the last ID3v2 frame.'; + $info['warning'][] = 'This was the last ID3v2 frame.'; } else { // next frame is invalid too, abort processing //unset($framedata); $framedata = null; - $ThisFileInfo['warning'][] = 'Invalid ID3v2 frame size, aborting.'; + $info['warning'][] = 'Invalid ID3v2 frame size, aborting.'; } if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { @@ -362,21 +389,21 @@ class getid3_id3v2 case "\x00".'MP': case ' MP': case 'MP3': - $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'; + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'; break; default: - $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'; + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'; break; } - } elseif ($frame_size > strlen(@$framedata)){ + } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) { - $ThisFileInfo['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.strlen($framedata).')).'; + $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).'; } else { - $ThisFileInfo['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'; + $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'; } @@ -397,7 +424,7 @@ class getid3_id3v2 // ID3v2 size 4 * %0xxxxxxx if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { - $footer = fread($fd, 10); + $footer = fread($this->getid3->fp, 10); if (substr($footer, 0, 3) == '3DI') { $thisfile_id3v2['footer'] = true; $thisfile_id3v2['majorversion_footer'] = ord($footer{3}); @@ -417,7 +444,7 @@ class getid3_id3v2 if (isset($thisfile_id3v2['comments']['genre'])) { foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { unset($thisfile_id3v2['comments']['genre'][$key]); - $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], $this->ParseID3v2GenreString($value)); + $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value))); } } @@ -429,15 +456,39 @@ class getid3_id3v2 } } - if (!isset($thisfile_id3v2['comments']['year']) && ereg('^([0-9]{4})', trim(@$thisfile_id3v2['comments']['recording_time'][0]), $matches)) { + if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) { $thisfile_id3v2['comments']['year'] = array($matches[1]); } + if (!empty($thisfile_id3v2['TXXX'])) { + // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames + foreach ($thisfile_id3v2['TXXX'] as $txxx_array) { + switch ($txxx_array['description']) { + case 'replaygain_track_gain': + if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) { + $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); + } + break; + case 'replaygain_track_peak': + if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) { + $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']); + } + break; + case 'replaygain_album_gain': + if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) { + $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); + } + break; + } + } + } + + // Set avdataoffset - $ThisFileInfo['avdataoffset'] = $thisfile_id3v2['headerlength']; + $info['avdataoffset'] = $thisfile_id3v2['headerlength']; if (isset($thisfile_id3v2['footer'])) { - $ThisFileInfo['avdataoffset'] += 10; + $info['avdataoffset'] += 10; } return true; @@ -448,68 +499,30 @@ class getid3_id3v2 // Parse genres into arrays of genreName and genreID // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' // ID3v2.4.x: '21' $00 'Eurodisco' $00 - - $genrestring = trim($genrestring); - $returnarray = array(); - if (strpos($genrestring, "\x00") !== false) { - $unprocessed = trim($genrestring); // trailing nulls will cause an infinite loop. - $genrestring = ''; - while (strpos($unprocessed, "\x00") !== false) { - // convert null-seperated v2.4-format into v2.3 ()-seperated format - $endpos = strpos($unprocessed, "\x00"); - $genrestring .= '('.substr($unprocessed, 0, $endpos).')'; - $unprocessed = substr($unprocessed, $endpos + 1); - } - unset($unprocessed); - } elseif (eregi('^([0-9]+|CR|RX)$', $genrestring)) { - // some tagging program (including some that use TagLib) fail to include null byte after numeric genre - $genrestring = '('.$genrestring.')'; - } - if (getid3_id3v1::LookupGenreID($genrestring)) { - - $returnarray['genre'][] = $genrestring; - - } else { - - while (strpos($genrestring, '(') !== false) { - - $startpos = strpos($genrestring, '('); - $endpos = strpos($genrestring, ')'); - if (substr($genrestring, $startpos + 1, 1) == '(') { - $genrestring = substr($genrestring, 0, $startpos).substr($genrestring, $startpos + 1); - $endpos--; - } - $element = substr($genrestring, $startpos + 1, $endpos - ($startpos + 1)); - $genrestring = substr($genrestring, 0, $startpos).substr($genrestring, $endpos + 1); - if (getid3_id3v1::LookupGenreName($element)) { // $element is a valid genre id/abbreviation - - if (empty($returnarray['genre']) || !in_array(getid3_id3v1::LookupGenreName($element), $returnarray['genre'])) { // avoid duplicate entires - $returnarray['genre'][] = getid3_id3v1::LookupGenreName($element); - } - + $clean_genres = array(); + if (strpos($genrestring, "\x00") === false) { + $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); + } + $genre_elements = explode("\x00", $genrestring); + foreach ($genre_elements as $element) { + $element = trim($element); + if ($element) { + if (preg_match('#^[0-9]{1,3}#', $element)) { + $clean_genres[] = getid3_id3v1::LookupGenreName($element); } else { - - if (empty($returnarray['genre']) || !in_array($element, $returnarray['genre'])) { // avoid duplicate entires - $returnarray['genre'][] = $element; - } - + $clean_genres[] = str_replace('((', '(', $element); } } } - if ($genrestring) { - if (empty($returnarray['genre']) || !in_array($genrestring, $returnarray['genre'])) { // avoid duplicate entires - $returnarray['genre'][] = $genrestring; - } - } - - return $returnarray; + return $clean_genres; } - function ParseID3v2Frame(&$parsedFrame, &$ThisFileInfo) { + function ParseID3v2Frame(&$parsedFrame) { // shortcuts - $id3v2_majorversion = $ThisFileInfo['id3v2']['majorversion']; + $info = &$this->getid3->info; + $id3v2_majorversion = $info['id3v2']['majorversion']; $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']); if (empty($parsedFrame['framenamelong'])) { @@ -547,21 +560,36 @@ class getid3_id3v2 if ($parsedFrame['flags']['Unsynchronisation']) { $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']); } + + if ($parsedFrame['flags']['DataLengthIndicator']) { + $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1); + $parsedFrame['data'] = substr($parsedFrame['data'], 4); + } } // Frame-level de-compression if ($parsedFrame['flags']['compression']) { $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); if (!function_exists('gzuncompress')) { - $ThisFileInfo['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; - } elseif ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { - $parsedFrame['data'] = $decompresseddata; + $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; } else { - $ThisFileInfo['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { + //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { + $parsedFrame['data'] = $decompresseddata; + unset($decompresseddata); + } else { + $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + } } } } + if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { + if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { + $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data'; + } + } + if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion'; @@ -573,7 +601,7 @@ class getid3_id3v2 default: break; } - $ThisFileInfo['warning'][] = $warning; + $info['warning'][] = $warning; } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier @@ -582,13 +610,9 @@ class getid3_id3v2 //
    // Owner identifier $00 // Identifier - - $frame_terminatorpos = strpos($parsedFrame['data'], "\x00"); - $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); - $parsedFrame['ownerid'] = $frame_idstring; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); - unset($parsedFrame['data']); - + $exploded = explode("\x00", $parsedFrame['data'], 2); + $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : ''); + $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : ''); } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame @@ -603,11 +627,11 @@ class getid3_id3v2 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -619,9 +643,9 @@ class getid3_id3v2 $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data'])); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); } - unset($parsedFrame['data']); + //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame @@ -634,7 +658,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); @@ -643,13 +667,9 @@ class getid3_id3v2 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - - // remove possible terminating \x00 (put by encoding id or software bug) - $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); - if ($string[strlen($string) - 1] == "\x00") { - $string = substr($string, 0, strlen($string) - 1); - } - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; + $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + $string = rtrim($string, "\x00"); // remove possible terminating null (put by encoding id or software bug) + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; unset($string); } @@ -665,11 +685,11 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -697,7 +717,7 @@ class getid3_id3v2 $parsedFrame['url'] = $frame_urldata; $parsedFrame['description'] = $frame_description; if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['url']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']); } unset($parsedFrame['data']); @@ -711,7 +731,7 @@ class getid3_id3v2 $parsedFrame['url'] = trim($parsedFrame['data']); if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; } unset($parsedFrame['data']); @@ -726,14 +746,14 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } @@ -744,7 +764,7 @@ class getid3_id3v2 // CD TOC if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; } @@ -808,7 +828,7 @@ class getid3_id3v2 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes - (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes // There may only be one 'SYTC' frame in each tag //
    // Time stamp format $xx @@ -845,13 +865,13 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -867,7 +887,7 @@ class getid3_id3v2 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); $parsedFrame['description'] = $frame_description; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } unset($parsedFrame['data']); @@ -891,7 +911,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; @@ -942,20 +962,20 @@ class getid3_id3v2 if (strlen($parsedFrame['data']) < 5) { - $ThisFileInfo['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']; + $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']; } else { $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -971,7 +991,7 @@ class getid3_id3v2 $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = $frame_text; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } } @@ -996,23 +1016,29 @@ class getid3_id3v2 } $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); $parsedFrame['description'] = $frame_idstring; - while (strlen($frame_remainingdata)) { + $RVA2channelcounter = 0; + while (strlen($frame_remainingdata) >= 5) { $frame_offset = 0; $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1)); - $parsedFrame[$frame_channeltypeid]['channeltypeid'] = $frame_channeltypeid; - $parsedFrame[$frame_channeltypeid]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); - $parsedFrame[$frame_channeltypeid]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed + $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid; + $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); + $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed $frame_offset += 2; - $parsedFrame[$frame_channeltypeid]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); - $frame_bytespeakvolume = ceil($parsedFrame[$frame_channeltypeid]['bitspeakvolume'] / 8); - $parsedFrame[$frame_channeltypeid]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); + $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); + if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) { + $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value'; + break; + } + $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8); + $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume); + $RVA2channelcounter++; } unset($parsedFrame['data']); } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only) - (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) // There may only be one 'RVA' frame in each tag //
    // ID3v2.2 => Increment/decrement %000000ba @@ -1114,7 +1140,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_idstring) === 0) { $frame_idstring = ''; @@ -1208,7 +1234,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { @@ -1216,7 +1242,7 @@ class getid3_id3v2 if (strtolower($frame_imagetype) == 'ima') { // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net) - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; @@ -1231,7 +1257,7 @@ class getid3_id3v2 } } if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) { - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; @@ -1241,40 +1267,88 @@ class getid3_id3v2 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 - } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (ord($frame_description) === 0) { - $frame_description = ''; - } - $parsedFrame['encodingid'] = $frame_textencoding; - $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - - if ($id3v2_majorversion == 2) { - $parsedFrame['imagetype'] = $frame_imagetype; + if ($frame_offset >= $parsedFrame['datalength']) { + $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset); } else { - $parsedFrame['mime'] = $frame_mimetype; - } - $parsedFrame['picturetypeid'] = $frame_picturetype; - $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); - $parsedFrame['description'] = $frame_description; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); - - $imageinfo = array(); - $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); - if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { - $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); - if ($imagechunkcheck[0]) { - $parsedFrame['image_width'] = $imagechunkcheck[0]; + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - if ($imagechunkcheck[1]) { - $parsedFrame['image_height'] = $imagechunkcheck[1]; + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; } - $parsedFrame['image_bytes'] = strlen($parsedFrame['data']); - } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + if ($id3v2_majorversion == 2) { + $parsedFrame['imagetype'] = $frame_imagetype; + } else { + $parsedFrame['mime'] = $frame_mimetype; + } + $parsedFrame['picturetypeid'] = $frame_picturetype; + $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['datalength'] = strlen($parsedFrame['data']); + + $parsedFrame['image_mime'] = ''; + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); + if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { + $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); + if ($imagechunkcheck[0]) { + $parsedFrame['image_width'] = $imagechunkcheck[0]; + } + if ($imagechunkcheck[1]) { + $parsedFrame['image_height'] = $imagechunkcheck[1]; + } + } + + do { + if ($this->inline_attachments === false) { + // skip entirely + unset($parsedFrame['data']); + break; + } + if ($this->inline_attachments === true) { + // great + } elseif (is_int($this->inline_attachments)) { + if ($this->inline_attachments < $parsedFrame['data_length']) { + // too big, skip + $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'; + unset($parsedFrame['data']); + break; + } + } elseif (is_string($this->inline_attachments)) { + $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { + // cannot write, skip + $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; + unset($parsedFrame['data']); + break; + } + } + // if we get this far, must be OK + if (is_string($this->inline_attachments)) { + $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; + if (!file_exists($destination_filename) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $parsedFrame['data']); + } else { + $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $parsedFrame['data_filename'] = $destination_filename; + unset($parsedFrame['data']); + } else { + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + if (!isset($info['id3v2']['comments']['picture'])) { + $info['id3v2']['comments']['picture'] = array(); + } + $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']); + } + } + } while (false); + } } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object @@ -1290,18 +1364,18 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; } $frame_offset = $frame_terminatorpos + strlen("\x00"); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_filename) === 0) { @@ -1309,9 +1383,9 @@ class getid3_id3v2 } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -1341,7 +1415,7 @@ class getid3_id3v2 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter - (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter // There may be more than one 'POPM' frame in each tag, // but only one with the same email address //
    @@ -1350,16 +1424,16 @@ class getid3_id3v2 // Counter $xx xx xx xx (xx ...) $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_emailaddress) === 0) { $frame_emailaddress = ''; } $frame_offset = $frame_terminatorpos + strlen("\x00"); $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); - $parsedFrame['email'] = $frame_emailaddress; - $parsedFrame['rating'] = $frame_rating; + $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); + $parsedFrame['email'] = $frame_emailaddress; + $parsedFrame['rating'] = $frame_rating; unset($parsedFrame['data']); @@ -1390,11 +1464,11 @@ class getid3_id3v2 // Encrypted datablock $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; @@ -1418,7 +1492,7 @@ class getid3_id3v2 // Encryption info $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid == ''; @@ -1452,7 +1526,7 @@ class getid3_id3v2 $frame_offset += 4; } - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_url) === 0) { $frame_url = ''; @@ -1462,7 +1536,7 @@ class getid3_id3v2 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); } unset($parsedFrame['data']); @@ -1490,7 +1564,7 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; @@ -1501,7 +1575,7 @@ class getid3_id3v2 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } unset($parsedFrame['data']); @@ -1517,12 +1591,12 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); @@ -1557,10 +1631,10 @@ class getid3_id3v2 $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; } - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); $frame_rawpricearray = explode('/', $frame_pricestring); @@ -1573,15 +1647,15 @@ class getid3_id3v2 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8); $frame_offset += 8; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_sellername) === 0) { @@ -1589,9 +1663,9 @@ class getid3_id3v2 } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); - $frame_terminatorpos = @strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { - $frame_terminatorpos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00 + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { @@ -1599,7 +1673,7 @@ class getid3_id3v2 } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen("\x00"); @@ -1629,7 +1703,7 @@ class getid3_id3v2 // Encryption data $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; @@ -1652,7 +1726,7 @@ class getid3_id3v2 // Group dependent data $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; @@ -1672,7 +1746,7 @@ class getid3_id3v2 // The private data $frame_offset = 0; - $frame_terminatorpos = @strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; @@ -1763,11 +1837,11 @@ class getid3_id3v2 $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); - $ThisFileInfo['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; - $ThisFileInfo['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; - $ThisFileInfo['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; - $ThisFileInfo['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; - $ThisFileInfo['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; + $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; + $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; + $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; + $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; + $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; unset($parsedFrame['data']); @@ -1781,6 +1855,52 @@ class getid3_id3v2 return str_replace("\xFF\x00", "\xFF", $data); } + function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { + static $LookupExtendedHeaderRestrictionsTagSizeLimits = array( + 0x00 => 'No more than 128 frames and 1 MB total tag size', + 0x01 => 'No more than 64 frames and 128 KB total tag size', + 0x02 => 'No more than 32 frames and 40 KB total tag size', + 0x03 => 'No more than 32 frames and 4 KB total tag size', + ); + return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : ''); + } + + function LookupExtendedHeaderRestrictionsTextEncodings($index) { + static $LookupExtendedHeaderRestrictionsTextEncodings = array( + 0x00 => 'No restrictions', + 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8', + ); + return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : ''); + } + + function LookupExtendedHeaderRestrictionsTextFieldSize($index) { + static $LookupExtendedHeaderRestrictionsTextFieldSize = array( + 0x00 => 'No restrictions', + 0x01 => 'No string is longer than 1024 characters', + 0x02 => 'No string is longer than 128 characters', + 0x03 => 'No string is longer than 30 characters', + ); + return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : ''); + } + + function LookupExtendedHeaderRestrictionsImageEncoding($index) { + static $LookupExtendedHeaderRestrictionsImageEncoding = array( + 0x00 => 'No restrictions', + 0x01 => 'Images are encoded only with PNG or JPEG', + ); + return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : ''); + } + + function LookupExtendedHeaderRestrictionsImageSizeSize($index) { + static $LookupExtendedHeaderRestrictionsImageSizeSize = array( + 0x00 => 'No restrictions', + 0x01 => 'All images are 256x256 pixels or smaller', + 0x02 => 'All images are 64x64 pixels or smaller', + 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise', + ); + return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : ''); + } + function LookupCurrencyUnits($currencyid) { $begin = __LINE__; @@ -2175,7 +2295,7 @@ class getid3_id3v2 - function LanguageLookup($languagecode, $casesensitive=false) { + static function LanguageLookup($languagecode, $casesensitive=false) { if (!$casesensitive) { $languagecode = strtolower($languagecode); @@ -2631,10 +2751,10 @@ class getid3_id3v2 } - function ETCOEventLookup($index) { - if (($index >= 0x17) && ($index <= 0xDF)) { - return 'reserved for future use'; - } + static function ETCOEventLookup($index) { + if (($index >= 0x17) && ($index <= 0xDF)) { + return 'reserved for future use'; + } if (($index >= 0xE0) && ($index <= 0xEF)) { return 'not predefined synch 0-F'; } @@ -2674,7 +2794,7 @@ class getid3_id3v2 return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); } - function SYTLContentTypeLookup($index) { + static function SYTLContentTypeLookup($index) { static $SYTLContentTypeLookup = array( 0x00 => 'other', 0x01 => 'lyrics', @@ -2690,7 +2810,7 @@ class getid3_id3v2 return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); } - function APICPictureTypeLookup($index, $returnarray=false) { + static function APICPictureTypeLookup($index, $returnarray=false) { static $APICPictureTypeLookup = array( 0x00 => 'Other', 0x01 => '32x32 pixels \'file icon\' (PNG only)', @@ -2720,7 +2840,7 @@ class getid3_id3v2 return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : ''); } - function COMRReceivedAsLookup($index) { + static function COMRReceivedAsLookup($index) { static $COMRReceivedAsLookup = array( 0x00 => 'Other', 0x01 => 'Standard CD album with other songs', @@ -2736,7 +2856,7 @@ class getid3_id3v2 return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : ''); } - function RVA2ChannelTypeLookup($index) { + static function RVA2ChannelTypeLookup($index) { static $RVA2ChannelTypeLookup = array( 0x00 => 'Other', 0x01 => 'Master volume', @@ -2752,7 +2872,7 @@ class getid3_id3v2 return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : ''); } - function FrameNameLongLookup($framename) { + static function FrameNameLongLookup($framename) { $begin = __LINE__; @@ -2936,7 +3056,7 @@ class getid3_id3v2 } - function FrameNameShortLookup($framename) { + static function FrameNameShortLookup($framename) { $begin = __LINE__; @@ -2947,8 +3067,8 @@ class getid3_id3v2 ASPI audio_seek_point_index BUF recommended_buffer_size CNT play_counter - COM comments - COMM comments + COM comment + COMM comment COMR commercial_frame CRA audio_encryption CRM encrypted_meta_frame @@ -3115,40 +3235,47 @@ class getid3_id3v2 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short'); } - function TextEncodingTerminatorLookup($encoding) { + static function TextEncodingTerminatorLookup($encoding) { // http://www.id3.org/id3v2.4.0-structure.txt // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: - // $00 ISO-8859-1. Terminated with $00. - // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. - // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. - // $03 UTF-8 encoded Unicode. Terminated with $00. - - static $TextEncodingTerminatorLookup = array(0=>"\x00", 1=>"\x00\x00", 2=>"\x00\x00", 3=>"\x00", 255=>"\x00\x00"); - - return @$TextEncodingTerminatorLookup[$encoding]; + static $TextEncodingTerminatorLookup = array( + 0 => "\x00", // $00 ISO-8859-1. Terminated with $00. + 1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00. + 255 => "\x00\x00" + ); + return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : ''); } - function TextEncodingNameLookup($encoding) { + static function TextEncodingNameLookup($encoding) { // http://www.id3.org/id3v2.4.0-structure.txt - static $TextEncodingNameLookup = array(0=>'ISO-8859-1', 1=>'UTF-16', 2=>'UTF-16BE', 3=>'UTF-8', 255=>'UTF-16BE'); + // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: + static $TextEncodingNameLookup = array( + 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00. + 1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00. + 255 => 'UTF-16BE' + ); return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1'); } - function IsValidID3v2FrameName($framename, $id3v2majorversion) { + static function IsValidID3v2FrameName($framename, $id3v2majorversion) { switch ($id3v2majorversion) { case 2: - return ereg('[A-Z][A-Z0-9]{2}', $framename); + return preg_match('#[A-Z][A-Z0-9]{2}#', $framename); break; case 3: case 4: - return ereg('[A-Z][A-Z0-9]{3}', $framename); + return preg_match('#[A-Z][A-Z0-9]{3}#', $framename); break; } return false; } - function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { + static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { for ($i = 0; $i < strlen($numberstring); $i++) { if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) { if (($numberstring{$i} == '.') && $allowdecimal) { @@ -3163,11 +3290,11 @@ class getid3_id3v2 return true; } - function IsValidDateStampString($datestamp) { + static function IsValidDateStampString($datestamp) { if (strlen($datestamp) != 8) { return false; } - if (!$this->IsANumber($datestamp, false)) { + if (!self::IsANumber($datestamp, false)) { return false; } $year = substr($datestamp, 0, 4); @@ -3191,7 +3318,7 @@ class getid3_id3v2 return true; } - function ID3v2HeaderLength($majorversion) { + static function ID3v2HeaderLength($majorversion) { return (($majorversion == 2) ? 6 : 10); } diff --git a/apps/media/getID3/getid3/module.tag.lyrics3.php b/3rdparty/getid3/module.tag.lyrics3.php similarity index 61% rename from apps/media/getID3/getid3/module.tag.lyrics3.php rename to 3rdparty/getid3/module.tag.lyrics3.php index 67dba43eb5..aaff717891 100644 --- a/apps/media/getID3/getid3/module.tag.lyrics3.php +++ b/3rdparty/getid3/module.tag.lyrics3.php @@ -14,19 +14,21 @@ ///////////////////////////////////////////////////////////////// -class getid3_lyrics3 +class getid3_lyrics3 extends getid3_handler { - function getid3_lyrics3(&$fd, &$ThisFileInfo) { + function Analyze() { + $info = &$this->getid3->info; + // http://www.volweb.cz/str/tags.htm - if ($ThisFileInfo['filesize'] >= pow(2, 31)) { - $ThisFileInfo['warning'][] = 'Unable to check for Lyrics3 because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($info['filesize'])) { + $info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; return false; } - fseek($fd, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - LYRICSEND - [Lyrics3size] - $lyrics3_id3v1 = fread($fd, 128 + 9 + 6); + fseek($this->getid3->fp, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size] + $lyrics3_id3v1 = fread($this->getid3->fp, 128 + 9 + 6); $lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size $lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200 $id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1 @@ -35,7 +37,7 @@ class getid3_lyrics3 // Lyrics3v1, ID3v1, no APE $lyrics3size = 5100; - $lyrics3offset = $ThisFileInfo['filesize'] - 128 - $lyrics3size; + $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; $lyrics3version = 1; } elseif ($lyrics3end == 'LYRICS200') { @@ -43,49 +45,49 @@ class getid3_lyrics3 // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); - $lyrics3offset = $ThisFileInfo['filesize'] - 128 - $lyrics3size; + $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; $lyrics3version = 2; } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) { // Lyrics3v1, no ID3v1, no APE $lyrics3size = 5100; - $lyrics3offset = $ThisFileInfo['filesize'] - $lyrics3size; + $lyrics3offset = $info['filesize'] - $lyrics3size; $lyrics3version = 1; - $lyrics3offset = $ThisFileInfo['filesize'] - $lyrics3size; + $lyrics3offset = $info['filesize'] - $lyrics3size; } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) { // Lyrics3v2, no ID3v1, no APE $lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' - $lyrics3offset = $ThisFileInfo['filesize'] - $lyrics3size; + $lyrics3offset = $info['filesize'] - $lyrics3size; $lyrics3version = 2; } else { - if (isset($ThisFileInfo['ape']['tag_offset_start']) && ($ThisFileInfo['ape']['tag_offset_start'] > 15)) { + if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) { - fseek($fd, $ThisFileInfo['ape']['tag_offset_start'] - 15, SEEK_SET); - $lyrics3lsz = fread($fd, 6); - $lyrics3end = fread($fd, 9); + fseek($this->getid3->fp, $info['ape']['tag_offset_start'] - 15, SEEK_SET); + $lyrics3lsz = fread($this->getid3->fp, 6); + $lyrics3end = fread($this->getid3->fp, 9); if ($lyrics3end == 'LYRICSEND') { // Lyrics3v1, APE, maybe ID3v1 $lyrics3size = 5100; - $lyrics3offset = $ThisFileInfo['ape']['tag_offset_start'] - $lyrics3size; - $ThisFileInfo['avdataend'] = $lyrics3offset; + $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; + $info['avdataend'] = $lyrics3offset; $lyrics3version = 1; - $ThisFileInfo['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; + $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; } elseif ($lyrics3end == 'LYRICS200') { // Lyrics3v2, APE, maybe ID3v1 $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' - $lyrics3offset = $ThisFileInfo['ape']['tag_offset_start'] - $lyrics3size; + $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; $lyrics3version = 2; - $ThisFileInfo['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; + $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; } @@ -94,14 +96,24 @@ class getid3_lyrics3 } if (isset($lyrics3offset)) { - $ThisFileInfo['avdataend'] = $lyrics3offset; - $this->getLyrics3Data($ThisFileInfo, $fd, $lyrics3offset, $lyrics3version, $lyrics3size); + $info['avdataend'] = $lyrics3offset; + $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size); - if (!isset($ThisFileInfo['ape'])) { - $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; + if (!isset($info['ape'])) { + $GETID3_ERRORARRAY = &$info['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, false)) { - $tag = new getid3_apetag($fd, $ThisFileInfo, $ThisFileInfo['lyrics3']['tag_offset_start']); - unset($tag); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_apetag = new getid3_apetag($getid3_temp); + $getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start']; + $getid3_apetag->Analyze(); + if (!empty($getid3_temp->info['ape'])) { + $info['ape'] = $getid3_temp->info['ape']; + } + if (!empty($getid3_temp->info['replay_gain'])) { + $info['replay_gain'] = $getid3_temp->info['replay_gain']; + } + unset($getid3_temp, $getid3_apetag); } } @@ -110,43 +122,46 @@ class getid3_lyrics3 return true; } - function getLyrics3Data(&$ThisFileInfo, &$fd, $endoffset, $version, $length) { + function getLyrics3Data($endoffset, $version, $length) { // http://www.volweb.cz/str/tags.htm - if ($endoffset >= pow(2, 31)) { - $ThisFileInfo['warning'][] = 'Unable to check for Lyrics3 because file is larger than 2GB'; + $info = &$this->getid3->info; + + if (!getid3_lib::intValueSupported($endoffset)) { + $info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; return false; } - fseek($fd, $endoffset, SEEK_SET); + fseek($this->getid3->fp, $endoffset, SEEK_SET); if ($length <= 0) { return false; } - $rawdata = fread($fd, $length); + $rawdata = fread($this->getid3->fp, $length); + + $ParsedLyrics3['raw']['lyrics3version'] = $version; + $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; + $ParsedLyrics3['tag_offset_start'] = $endoffset; + $ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1; if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') { if (strpos($rawdata, 'LYRICSBEGIN') !== false) { - $ThisFileInfo['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version; - $ThisFileInfo['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN'); - $ParsedLyrics3['tag_offset_start'] = $ThisFileInfo['avdataend']; + $info['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version; + $info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN'); $rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN')); $length = strlen($rawdata); + $ParsedLyrics3['tag_offset_start'] = $info['avdataend']; + $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; } else { - $ThisFileInfo['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead'; + $info['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead'; return false; } } - $ParsedLyrics3['raw']['lyrics3version'] = $version; - $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; - $ParsedLyrics3['tag_offset_start'] = $endoffset; - $ParsedLyrics3['tag_offset_end'] = $endoffset + $length; - switch ($version) { case 1: @@ -154,7 +169,7 @@ class getid3_lyrics3 $ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9)); $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); } else { - $ThisFileInfo['error'][] = '"LYRICSEND" expected at '.(ftell($fd) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; + $info['error'][] = '"LYRICSEND" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; return false; } break; @@ -192,9 +207,9 @@ class getid3_lyrics3 foreach ($imagestrings as $key => $imagestring) { if (strpos($imagestring, '||') !== false) { $imagearray = explode('||', $imagestring); - $ParsedLyrics3['images'][$key]['filename'] = @$imagearray[0]; - $ParsedLyrics3['images'][$key]['description'] = @$imagearray[1]; - $ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(@$imagearray[2]); + $ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : ''); + $ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : ''); + $ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : ''); } } } @@ -202,37 +217,37 @@ class getid3_lyrics3 $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); } } else { - $ThisFileInfo['error'][] = '"LYRICS200" expected at '.(ftell($fd) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; + $info['error'][] = '"LYRICS200" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; return false; } break; default: - $ThisFileInfo['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)'; + $info['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)'; return false; break; } - if (isset($ThisFileInfo['id3v1']['tag_offset_start']) && ($ThisFileInfo['id3v1']['tag_offset_start'] < $ParsedLyrics3['tag_offset_end'])) { - $ThisFileInfo['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data'; - unset($ThisFileInfo['id3v1']); - foreach ($ThisFileInfo['warning'] as $key => $value) { + if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) { + $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data'; + unset($info['id3v1']); + foreach ($info['warning'] as $key => $value) { if ($value == 'Some ID3v1 fields do not use NULL characters for padding') { - unset($ThisFileInfo['warning'][$key]); - sort($ThisFileInfo['warning']); + unset($info['warning'][$key]); + sort($info['warning']); break; } } } - $ThisFileInfo['lyrics3'] = $ParsedLyrics3; + $info['lyrics3'] = $ParsedLyrics3; return true; } function Lyrics3Timestamp2Seconds($rawtimestamp) { - if (ereg('^\\[([0-9]{2}):([0-9]{2})\\]$', $rawtimestamp, $regs)) { + if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) { return (int) (($regs[1] * 60) + $regs[2]); } return false; @@ -243,7 +258,7 @@ class getid3_lyrics3 foreach ($lyricsarray as $key => $lyricline) { $regs = array(); unset($thislinetimestamps); - while (ereg('^(\\[[0-9]{2}:[0-9]{2}\\])', $lyricline, $regs)) { + while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) { $thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]); $lyricline = str_replace($regs[0], '', $lyricline); } diff --git a/3rdparty/getid3/module.tag.xmp.php b/3rdparty/getid3/module.tag.xmp.php new file mode 100644 index 0000000000..141fd09d16 --- /dev/null +++ b/3rdparty/getid3/module.tag.xmp.php @@ -0,0 +1,766 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.tag.xmp.php // +// module for analyzing XMP metadata (e.g. in JPEG files) // +// dependencies: NONE // +// // +///////////////////////////////////////////////////////////////// +// // +// Module originally written [2009-Mar-26] by // +// Nigel Barnes // +// Bundled into getID3 with permission // +// called by getID3 in module.graphic.jpg.php // +// /// +///////////////////////////////////////////////////////////////// + +/************************************************************************************************** + * SWISScenter Source Nigel Barnes + * + * Provides functions for reading information from the 'APP1' Extensible Metadata + * Platform (XMP) segment of JPEG format files. + * This XMP segment is XML based and contains the Resource Description Framework (RDF) + * data, which itself can contain the Dublin Core Metadata Initiative (DCMI) information. + * + * This code uses segments from the JPEG Metadata Toolkit project by Evan Hunter. + *************************************************************************************************/ +class Image_XMP +{ + /** + * @var string + * The name of the image file that contains the XMP fields to extract and modify. + * @see Image_XMP() + */ + var $_sFilename = null; + + /** + * @var array + * The XMP fields that were extracted from the image or updated by this class. + * @see getAllTags() + */ + var $_aXMP = array(); + + /** + * @var boolean + * True if an APP1 segment was found to contain XMP metadata. + * @see isValid() + */ + var $_bXMPParse = false; + + /** + * Returns the status of XMP parsing during instantiation + * + * You'll normally want to call this method before trying to get XMP fields. + * + * @return boolean + * Returns true if an APP1 segment was found to contain XMP metadata. + */ + function isValid() + { + return $this->_bXMPParse; + } + + /** + * Get a copy of all XMP tags extracted from the image + * + * @return array - An array of XMP fields as it extracted by the XMPparse() function + */ + function getAllTags() + { + return $this->_aXMP; + } + + /** + * Reads all the JPEG header segments from an JPEG image file into an array + * + * @param string $filename - the filename of the JPEG file to read + * @return array $headerdata - Array of JPEG header segments + * @return boolean FALSE - if headers could not be read + */ + function _get_jpeg_header_data($filename) + { + // prevent refresh from aborting file operations and hosing file + ignore_user_abort(true); + + // Attempt to open the jpeg file - the at symbol supresses the error message about + // not being able to open files. The file_exists would have been used, but it + // does not work with files fetched over http or ftp. + if (is_readable($filename) && is_file($filename) && ($filehnd = fopen($filename, 'rb'))) { + // great + } else { + return false; + } + + // Read the first two characters + $data = fread($filehnd, 2); + + // Check that the first two characters are 0xFF 0xD8 (SOI - Start of image) + if ($data != "\xFF\xD8") + { + // No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return; + echo '

    This probably is not a JPEG file

    '."\n"; + fclose($filehnd); + return false; + } + + // Read the third character + $data = fread($filehnd, 2); + + // Check that the third character is 0xFF (Start of first segment header) + if ($data{0} != "\xFF") + { + // NO FF found - close file and return - JPEG is probably corrupted + fclose($filehnd); + return false; + } + + // Flag that we havent yet hit the compressed image data + $hit_compressed_image_data = false; + + // Cycle through the file until, one of: 1) an EOI (End of image) marker is hit, + // 2) we have hit the compressed image data (no more headers are allowed after data) + // 3) or end of file is hit + + while (($data{1} != "\xD9") && (!$hit_compressed_image_data) && (!feof($filehnd))) + { + // Found a segment to look at. + // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them + if ((ord($data{1}) < 0xD0) || (ord($data{1}) > 0xD7)) + { + // Segment isn't a Restart marker + // Read the next two bytes (size) + $sizestr = fread($filehnd, 2); + + // convert the size bytes to an integer + $decodedsize = unpack('nsize', $sizestr); + + // Save the start position of the data + $segdatastart = ftell($filehnd); + + // Read the segment data with length indicated by the previously read size + $segdata = fread($filehnd, $decodedsize['size'] - 2); + + // Store the segment information in the output array + $headerdata[] = array( + 'SegType' => ord($data{1}), + 'SegName' => $GLOBALS['JPEG_Segment_Names'][ord($data{1})], + 'SegDataStart' => $segdatastart, + 'SegData' => $segdata, + ); + } + + // If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows + if ($data{1} == "\xDA") + { + // Flag that we have hit the compressed image data - exit loop as no more headers available. + $hit_compressed_image_data = true; + } + else + { + // Not an SOS - Read the next two bytes - should be the segment marker for the next segment + $data = fread($filehnd, 2); + + // Check that the first byte of the two is 0xFF as it should be for a marker + if ($data{0} != "\xFF") + { + // NO FF found - close file and return - JPEG is probably corrupted + fclose($filehnd); + return false; + } + } + } + + // Close File + fclose($filehnd); + // Alow the user to abort from now on + ignore_user_abort(false); + + // Return the header data retrieved + return $headerdata; + } + + + /** + * Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string. + * + * @param string $filename - the filename of the JPEG file to read + * @return string $xmp_data - the string of raw XML text + * @return boolean FALSE - if an APP 1 XMP segment could not be found, or if an error occured + */ + function _get_XMP_text($filename) + { + //Get JPEG header data + $jpeg_header_data = $this->_get_jpeg_header_data($filename); + + //Cycle through the header segments + for ($i = 0; $i < count($jpeg_header_data); $i++) + { + // If we find an APP1 header, + if (strcmp($jpeg_header_data[$i]['SegName'], 'APP1') == 0) + { + // And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) , + if (strncmp($jpeg_header_data[$i]['SegData'], 'http://ns.adobe.com/xap/1.0/'."\x00", 29) == 0) + { + // Found a XMP/RDF block + // Return the XMP text + $xmp_data = substr($jpeg_header_data[$i]['SegData'], 29); + + return trim($xmp_data); // trim() should not be neccesary, but some files found in the wild with null-terminated block (known samples from Apple Aperture) causes problems elsewhere (see http://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153) + } + } + } + return false; + } + + /** + * Parses a string containing XMP data (XML), and returns an array + * which contains all the XMP (XML) information. + * + * @param string $xml_text - a string containing the XMP data (XML) to be parsed + * @return array $xmp_array - an array containing all xmp details retrieved. + * @return boolean FALSE - couldn't parse the XMP data + */ + function read_XMP_array_from_text($xmltext) + { + // Check if there actually is any text to parse + if (trim($xmltext) == '') + { + return false; + } + + // Create an instance of a xml parser to parse the XML text + $xml_parser = xml_parser_create('UTF-8'); + + // Change: Fixed problem that caused the whitespace (especially newlines) to be destroyed when converting xml text to an xml array, as of revision 1.10 + + // We would like to remove unneccessary white space, but this will also + // remove things like newlines ( ) in the XML values, so white space + // will have to be removed later + if (xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 0) == false) + { + // Error setting case folding - destroy the parser and return + xml_parser_free($xml_parser); + return false; + } + + // to use XML code correctly we have to turn case folding + // (uppercasing) off. XML is case sensitive and upper + // casing is in reality XML standards violation + if (xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0) == false) + { + // Error setting case folding - destroy the parser and return + xml_parser_free($xml_parser); + return false; + } + + // Parse the XML text into a array structure + if (xml_parse_into_struct($xml_parser, $xmltext, $values, $tags) == 0) + { + // Error Parsing XML - destroy the parser and return + xml_parser_free($xml_parser); + return false; + } + + // Destroy the xml parser + xml_parser_free($xml_parser); + + // Clear the output array + $xmp_array = array(); + + // The XMP data has now been parsed into an array ... + + // Cycle through each of the array elements + $current_property = ''; // current property being processed + $container_index = -1; // -1 = no container open, otherwise index of container content + foreach ($values as $xml_elem) + { + // Syntax and Class names + switch ($xml_elem['tag']) + { + case 'x:xmpmeta': + // only defined attribute is x:xmptk written by Adobe XMP Toolkit; value is the version of the toolkit + break; + + case 'rdf:RDF': + // required element immediately within x:xmpmeta; no data here + break; + + case 'rdf:Description': + switch ($xml_elem['type']) + { + case 'open': + case 'complete': + if (array_key_exists('attributes', $xml_elem)) + { + // rdf:Description may contain wanted attributes + foreach (array_keys($xml_elem['attributes']) as $key) + { + // Check whether we want this details from this attribute + if (in_array($key, $GLOBALS['XMP_tag_captions'])) + { + // Attribute wanted + $xmp_array[$key] = $xml_elem['attributes'][$key]; + } + } + } + case 'cdata': + case 'close': + break; + } + + case 'rdf:ID': + case 'rdf:nodeID': + // Attributes are ignored + break; + + case 'rdf:li': + // Property member + if ($xml_elem['type'] == 'complete') + { + if (array_key_exists('attributes', $xml_elem)) + { + // If Lang Alt (language alternatives) then ensure we take the default language + if (isset($xml_elem['attributes']['xml:lang']) && ($xml_elem['attributes']['xml:lang'] != 'x-default')) + { + break; + } + } + if ($current_property != '') + { + $xmp_array[$current_property][$container_index] = (isset($xml_elem['value']) ? $xml_elem['value'] : ''); + $container_index += 1; + } + //else unidentified attribute!! + } + break; + + case 'rdf:Seq': + case 'rdf:Bag': + case 'rdf:Alt': + // Container found + switch ($xml_elem['type']) + { + case 'open': + $container_index = 0; + break; + case 'close': + $container_index = -1; + break; + case 'cdata': + break; + } + break; + + default: + // Check whether we want the details from this attribute + if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions'])) + { + switch ($xml_elem['type']) + { + case 'open': + // open current element + $current_property = $xml_elem['tag']; + break; + + case 'close': + // close current element + $current_property = ''; + break; + + case 'complete': + // store attribute value + $xmp_array[$xml_elem['tag']] = (isset($xml_elem['value']) ? $xml_elem['value'] : ''); + break; + + case 'cdata': + // ignore + break; + } + } + break; + } + + } + return $xmp_array; + } + + + /** + * Constructor + * + * @param string - Name of the image file to access and extract XMP information from. + */ + function Image_XMP($sFilename) + { + $this->_sFilename = $sFilename; + + if (is_file($this->_sFilename)) + { + // Get XMP data + $xmp_data = $this->_get_XMP_text($sFilename); + if ($xmp_data) + { + $this->_aXMP = $this->read_XMP_array_from_text($xmp_data); + $this->_bXMPParse = true; + } + } + } + +} + +/** +* Global Variable: XMP_tag_captions +* +* The Property names of all known XMP fields. +* Note: this is a full list with unrequired properties commented out. +*/ +$GLOBALS['XMP_tag_captions'] = array( +// IPTC Core + 'Iptc4xmpCore:CiAdrCity', + 'Iptc4xmpCore:CiAdrCtry', + 'Iptc4xmpCore:CiAdrExtadr', + 'Iptc4xmpCore:CiAdrPcode', + 'Iptc4xmpCore:CiAdrRegion', + 'Iptc4xmpCore:CiEmailWork', + 'Iptc4xmpCore:CiTelWork', + 'Iptc4xmpCore:CiUrlWork', + 'Iptc4xmpCore:CountryCode', + 'Iptc4xmpCore:CreatorContactInfo', + 'Iptc4xmpCore:IntellectualGenre', + 'Iptc4xmpCore:Location', + 'Iptc4xmpCore:Scene', + 'Iptc4xmpCore:SubjectCode', +// Dublin Core Schema + 'dc:contributor', + 'dc:coverage', + 'dc:creator', + 'dc:date', + 'dc:description', + 'dc:format', + 'dc:identifier', + 'dc:language', + 'dc:publisher', + 'dc:relation', + 'dc:rights', + 'dc:source', + 'dc:subject', + 'dc:title', + 'dc:type', +// XMP Basic Schema + 'xmp:Advisory', + 'xmp:BaseURL', + 'xmp:CreateDate', + 'xmp:CreatorTool', + 'xmp:Identifier', + 'xmp:Label', + 'xmp:MetadataDate', + 'xmp:ModifyDate', + 'xmp:Nickname', + 'xmp:Rating', + 'xmp:Thumbnails', + 'xmpidq:Scheme', +// XMP Rights Management Schema + 'xmpRights:Certificate', + 'xmpRights:Marked', + 'xmpRights:Owner', + 'xmpRights:UsageTerms', + 'xmpRights:WebStatement', +// These are not in spec but Photoshop CS seems to use them + 'xap:Advisory', + 'xap:BaseURL', + 'xap:CreateDate', + 'xap:CreatorTool', + 'xap:Identifier', + 'xap:MetadataDate', + 'xap:ModifyDate', + 'xap:Nickname', + 'xap:Rating', + 'xap:Thumbnails', + 'xapidq:Scheme', + 'xapRights:Certificate', + 'xapRights:Copyright', + 'xapRights:Marked', + 'xapRights:Owner', + 'xapRights:UsageTerms', + 'xapRights:WebStatement', +// XMP Media Management Schema + 'xapMM:DerivedFrom', + 'xapMM:DocumentID', + 'xapMM:History', + 'xapMM:InstanceID', + 'xapMM:ManagedFrom', + 'xapMM:Manager', + 'xapMM:ManageTo', + 'xapMM:ManageUI', + 'xapMM:ManagerVariant', + 'xapMM:RenditionClass', + 'xapMM:RenditionParams', + 'xapMM:VersionID', + 'xapMM:Versions', + 'xapMM:LastURL', + 'xapMM:RenditionOf', + 'xapMM:SaveID', +// XMP Basic Job Ticket Schema + 'xapBJ:JobRef', +// XMP Paged-Text Schema + 'xmpTPg:MaxPageSize', + 'xmpTPg:NPages', + 'xmpTPg:Fonts', + 'xmpTPg:Colorants', + 'xmpTPg:PlateNames', +// Adobe PDF Schema + 'pdf:Keywords', + 'pdf:PDFVersion', + 'pdf:Producer', +// Photoshop Schema + 'photoshop:AuthorsPosition', + 'photoshop:CaptionWriter', + 'photoshop:Category', + 'photoshop:City', + 'photoshop:Country', + 'photoshop:Credit', + 'photoshop:DateCreated', + 'photoshop:Headline', + 'photoshop:History', +// Not in XMP spec + 'photoshop:Instructions', + 'photoshop:Source', + 'photoshop:State', + 'photoshop:SupplementalCategories', + 'photoshop:TransmissionReference', + 'photoshop:Urgency', +// EXIF Schemas + 'tiff:ImageWidth', + 'tiff:ImageLength', + 'tiff:BitsPerSample', + 'tiff:Compression', + 'tiff:PhotometricInterpretation', + 'tiff:Orientation', + 'tiff:SamplesPerPixel', + 'tiff:PlanarConfiguration', + 'tiff:YCbCrSubSampling', + 'tiff:YCbCrPositioning', + 'tiff:XResolution', + 'tiff:YResolution', + 'tiff:ResolutionUnit', + 'tiff:TransferFunction', + 'tiff:WhitePoint', + 'tiff:PrimaryChromaticities', + 'tiff:YCbCrCoefficients', + 'tiff:ReferenceBlackWhite', + 'tiff:DateTime', + 'tiff:ImageDescription', + 'tiff:Make', + 'tiff:Model', + 'tiff:Software', + 'tiff:Artist', + 'tiff:Copyright', + 'exif:ExifVersion', + 'exif:FlashpixVersion', + 'exif:ColorSpace', + 'exif:ComponentsConfiguration', + 'exif:CompressedBitsPerPixel', + 'exif:PixelXDimension', + 'exif:PixelYDimension', + 'exif:MakerNote', + 'exif:UserComment', + 'exif:RelatedSoundFile', + 'exif:DateTimeOriginal', + 'exif:DateTimeDigitized', + 'exif:ExposureTime', + 'exif:FNumber', + 'exif:ExposureProgram', + 'exif:SpectralSensitivity', + 'exif:ISOSpeedRatings', + 'exif:OECF', + 'exif:ShutterSpeedValue', + 'exif:ApertureValue', + 'exif:BrightnessValue', + 'exif:ExposureBiasValue', + 'exif:MaxApertureValue', + 'exif:SubjectDistance', + 'exif:MeteringMode', + 'exif:LightSource', + 'exif:Flash', + 'exif:FocalLength', + 'exif:SubjectArea', + 'exif:FlashEnergy', + 'exif:SpatialFrequencyResponse', + 'exif:FocalPlaneXResolution', + 'exif:FocalPlaneYResolution', + 'exif:FocalPlaneResolutionUnit', + 'exif:SubjectLocation', + 'exif:SensingMethod', + 'exif:FileSource', + 'exif:SceneType', + 'exif:CFAPattern', + 'exif:CustomRendered', + 'exif:ExposureMode', + 'exif:WhiteBalance', + 'exif:DigitalZoomRatio', + 'exif:FocalLengthIn35mmFilm', + 'exif:SceneCaptureType', + 'exif:GainControl', + 'exif:Contrast', + 'exif:Saturation', + 'exif:Sharpness', + 'exif:DeviceSettingDescription', + 'exif:SubjectDistanceRange', + 'exif:ImageUniqueID', + 'exif:GPSVersionID', + 'exif:GPSLatitude', + 'exif:GPSLongitude', + 'exif:GPSAltitudeRef', + 'exif:GPSAltitude', + 'exif:GPSTimeStamp', + 'exif:GPSSatellites', + 'exif:GPSStatus', + 'exif:GPSMeasureMode', + 'exif:GPSDOP', + 'exif:GPSSpeedRef', + 'exif:GPSSpeed', + 'exif:GPSTrackRef', + 'exif:GPSTrack', + 'exif:GPSImgDirectionRef', + 'exif:GPSImgDirection', + 'exif:GPSMapDatum', + 'exif:GPSDestLatitude', + 'exif:GPSDestLongitude', + 'exif:GPSDestBearingRef', + 'exif:GPSDestBearing', + 'exif:GPSDestDistanceRef', + 'exif:GPSDestDistance', + 'exif:GPSProcessingMethod', + 'exif:GPSAreaInformation', + 'exif:GPSDifferential', + 'stDim:w', + 'stDim:h', + 'stDim:unit', + 'xapGImg:height', + 'xapGImg:width', + 'xapGImg:format', + 'xapGImg:image', + 'stEvt:action', + 'stEvt:instanceID', + 'stEvt:parameters', + 'stEvt:softwareAgent', + 'stEvt:when', + 'stRef:instanceID', + 'stRef:documentID', + 'stRef:versionID', + 'stRef:renditionClass', + 'stRef:renditionParams', + 'stRef:manager', + 'stRef:managerVariant', + 'stRef:manageTo', + 'stRef:manageUI', + 'stVer:comments', + 'stVer:event', + 'stVer:modifyDate', + 'stVer:modifier', + 'stVer:version', + 'stJob:name', + 'stJob:id', + 'stJob:url', +// Exif Flash + 'exif:Fired', + 'exif:Return', + 'exif:Mode', + 'exif:Function', + 'exif:RedEyeMode', +// Exif OECF/SFR + 'exif:Columns', + 'exif:Rows', + 'exif:Names', + 'exif:Values', +// Exif CFAPattern + 'exif:Columns', + 'exif:Rows', + 'exif:Values', +// Exif DeviceSettings + 'exif:Columns', + 'exif:Rows', + 'exif:Settings', +); + + +/** +* Global Variable: JPEG_Segment_Names +* +* The names of the JPEG segment markers, indexed by their marker number +*/ +$GLOBALS['JPEG_Segment_Names'] = array( + 0x01 => 'TEM', + 0x02 => 'RES', + 0xC0 => 'SOF0', + 0xC1 => 'SOF1', + 0xC2 => 'SOF2', + 0xC3 => 'SOF4', + 0xC4 => 'DHT', + 0xC5 => 'SOF5', + 0xC6 => 'SOF6', + 0xC7 => 'SOF7', + 0xC8 => 'JPG', + 0xC9 => 'SOF9', + 0xCA => 'SOF10', + 0xCB => 'SOF11', + 0xCC => 'DAC', + 0xCD => 'SOF13', + 0xCE => 'SOF14', + 0xCF => 'SOF15', + 0xD0 => 'RST0', + 0xD1 => 'RST1', + 0xD2 => 'RST2', + 0xD3 => 'RST3', + 0xD4 => 'RST4', + 0xD5 => 'RST5', + 0xD6 => 'RST6', + 0xD7 => 'RST7', + 0xD8 => 'SOI', + 0xD9 => 'EOI', + 0xDA => 'SOS', + 0xDB => 'DQT', + 0xDC => 'DNL', + 0xDD => 'DRI', + 0xDE => 'DHP', + 0xDF => 'EXP', + 0xE0 => 'APP0', + 0xE1 => 'APP1', + 0xE2 => 'APP2', + 0xE3 => 'APP3', + 0xE4 => 'APP4', + 0xE5 => 'APP5', + 0xE6 => 'APP6', + 0xE7 => 'APP7', + 0xE8 => 'APP8', + 0xE9 => 'APP9', + 0xEA => 'APP10', + 0xEB => 'APP11', + 0xEC => 'APP12', + 0xED => 'APP13', + 0xEE => 'APP14', + 0xEF => 'APP15', + 0xF0 => 'JPG0', + 0xF1 => 'JPG1', + 0xF2 => 'JPG2', + 0xF3 => 'JPG3', + 0xF4 => 'JPG4', + 0xF5 => 'JPG5', + 0xF6 => 'JPG6', + 0xF7 => 'JPG7', + 0xF8 => 'JPG8', + 0xF9 => 'JPG9', + 0xFA => 'JPG10', + 0xFB => 'JPG11', + 0xFC => 'JPG12', + 0xFD => 'JPG13', + 0xFE => 'COM', +); + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/write.apetag.php b/3rdparty/getid3/write.apetag.php similarity index 92% rename from apps/media/getID3/getid3/write.apetag.php rename to 3rdparty/getid3/write.apetag.php index 189160aff8..2b553699fd 100644 --- a/apps/media/getID3/getid3/write.apetag.php +++ b/3rdparty/getid3/write.apetag.php @@ -21,9 +21,9 @@ class getid3_write_apetag var $filename; var $tag_data; - var $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + var $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here function getid3_write_apetag() { return true; @@ -56,7 +56,7 @@ class getid3_write_apetag } if ($APEtag = $this->GenerateAPEtag()) { - if ($fp = @fopen($this->filename, 'a+b')) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) { $oldignoreuserabort = ignore_user_abort(true); flock($fp, LOCK_EX); @@ -86,9 +86,7 @@ class getid3_write_apetag fclose($fp); ignore_user_abort($oldignoreuserabort); return true; - } - return false; } return false; } @@ -97,7 +95,7 @@ class getid3_write_apetag $getID3 = new getID3; $ThisFileInfo = $getID3->analyze($this->filename); if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['ape']['tag_offset_end'])) { - if ($fp = @fopen($this->filename, 'a+b')) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) { flock($fp, LOCK_EX); $oldignoreuserabort = ignore_user_abort(true); @@ -120,7 +118,6 @@ class getid3_write_apetag ignore_user_abort($oldignoreuserabort); return true; - } return false; } @@ -204,7 +201,7 @@ class getid3_write_apetag } function CleanAPEtagItemKey($itemkey) { - $itemkey = eregi_replace("[^\x20-\x7E]", '', $itemkey); + $itemkey = preg_replace("#[^\x20-\x7E]#i", '', $itemkey); // http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html switch (strtoupper($itemkey)) { diff --git a/apps/media/getID3/getid3/write.id3v1.php b/3rdparty/getid3/write.id3v1.php similarity index 53% rename from apps/media/getID3/getid3/write.id3v1.php rename to 3rdparty/getid3/write.id3v1.php index 3c2b7a402c..cecccd8ac5 100644 --- a/apps/media/getID3/getid3/write.id3v1.php +++ b/3rdparty/getid3/write.id3v1.php @@ -18,6 +18,7 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE_ class getid3_write_id3v1 { var $filename; + var $filesize; var $tag_data; var $warnings = array(); // any non-critical errors will be stored here var $errors = array(); // any critical errors will be stored here @@ -27,15 +28,14 @@ class getid3_write_id3v1 } function WriteID3v1() { - if ((filesize($this->filename) >= (pow(2, 31) - 128)) || (filesize($this->filename) < 0)) { - $this->errors[] = 'Unable to write ID3v1 because file is larger than 2GB'; - return false; - } - // File MUST be writeable - CHMOD(646) at least - if (is_writeable($this->filename)) { - if ($fp_source = @fopen($this->filename, 'r+b')) { - + if (!empty($this->filename) && is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename)) { + $this->setRealFileSize(); + if (($this->filesize <= 0) || !getid3_lib::intValueSupported($this->filesize)) { + $this->errors[] = 'Unable to WriteID3v1('.$this->filename.') because filesize ('.$this->filesize.') is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; + return false; + } + if ($fp_source = fopen($this->filename, 'r+b')) { fseek($fp_source, -128, SEEK_END); if (fread($fp_source, 3) == 'TAG') { fseek($fp_source, -128, SEEK_END); // overwrite existing ID3v1 tag @@ -45,19 +45,19 @@ class getid3_write_id3v1 $this->tag_data['track'] = (isset($this->tag_data['track']) ? $this->tag_data['track'] : (isset($this->tag_data['track_number']) ? $this->tag_data['track_number'] : (isset($this->tag_data['tracknumber']) ? $this->tag_data['tracknumber'] : ''))); $new_id3v1_tag_data = getid3_id3v1::GenerateID3v1Tag( - @$this->tag_data['title'], - @$this->tag_data['artist'], - @$this->tag_data['album'], - @$this->tag_data['year'], - @$this->tag_data['genreid'], - @$this->tag_data['comment'], - @$this->tag_data['track']); + (isset($this->tag_data['title'] ) ? $this->tag_data['title'] : ''), + (isset($this->tag_data['artist'] ) ? $this->tag_data['artist'] : ''), + (isset($this->tag_data['album'] ) ? $this->tag_data['album'] : ''), + (isset($this->tag_data['year'] ) ? $this->tag_data['year'] : ''), + (isset($this->tag_data['genreid']) ? $this->tag_data['genreid'] : ''), + (isset($this->tag_data['comment']) ? $this->tag_data['comment'] : ''), + (isset($this->tag_data['track'] ) ? $this->tag_data['track'] : '')); fwrite($fp_source, $new_id3v1_tag_data, 128); fclose($fp_source); return true; } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "r+b"'; + $this->errors[] = 'Could not fopen('.$this->filename.', "r+b")'; return false; } } @@ -71,11 +71,12 @@ class getid3_write_id3v1 // Initialize getID3 engine $getID3 = new getID3; + $getID3->option_tag_id3v2 = false; + $getID3->option_tag_apetag = false; + $getID3->option_tags_html = false; + $getID3->option_extra_info = false; + $getID3->option_tag_id3v1 = true; $ThisFileInfo = $getID3->analyze($this->filename); - if ($ThisFileInfo['filesize'] >= (pow(2, 31) - 128)) { - // cannot write tags on files > 2GB - return false; - } if (isset($ThisFileInfo['tags']['id3v1'])) { foreach ($ThisFileInfo['tags']['id3v1'] as $key => $value) { $id3v1data[$key] = implode(',', $value); @@ -87,18 +88,18 @@ class getid3_write_id3v1 } function RemoveID3v1() { - if ($ThisFileInfo['filesize'] >= pow(2, 31)) { - $this->errors[] = 'Unable to write ID3v1 because file is larger than 2GB'; - return false; - } - // File MUST be writeable - CHMOD(646) at least - if (is_writeable($this->filename)) { - if ($fp_source = @fopen($this->filename, 'r+b')) { + if (!empty($this->filename) && is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename)) { + $this->setRealFileSize(); + if (($this->filesize <= 0) || !getid3_lib::intValueSupported($this->filesize)) { + $this->errors[] = 'Unable to RemoveID3v1('.$this->filename.') because filesize ('.$this->filesize.') is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; + return false; + } + if ($fp_source = fopen($this->filename, 'r+b')) { fseek($fp_source, -128, SEEK_END); if (fread($fp_source, 3) == 'TAG') { - ftruncate($fp_source, filesize($this->filename) - 128); + ftruncate($fp_source, $this->filesize - 128); } else { // no ID3v1 tag to begin with - do nothing } @@ -106,7 +107,7 @@ class getid3_write_id3v1 return true; } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "r+b"'; + $this->errors[] = 'Could not fopen('.$this->filename.', "r+b")'; } } else { $this->errors[] = $this->filename.' is not writeable'; @@ -114,6 +115,24 @@ class getid3_write_id3v1 return false; } + function setRealFileSize() { + if (PHP_INT_MAX > 2147483647) { + $this->filesize = filesize($this->filename); + return true; + } + // 32-bit PHP will not return correct values for filesize() if file is >=2GB + // but getID3->analyze() has workarounds to get actual filesize + $getID3 = new getID3; + $getID3->option_tag_id3v1 = false; + $getID3->option_tag_id3v2 = false; + $getID3->option_tag_apetag = false; + $getID3->option_tags_html = false; + $getID3->option_extra_info = false; + $ThisFileInfo = $getID3->analyze($this->filename); + $this->filesize = $ThisFileInfo['filesize']; + return true; + } + } ?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/write.id3v2.php b/3rdparty/getid3/write.id3v2.php similarity index 95% rename from apps/media/getID3/getid3/write.id3v2.php rename to 3rdparty/getid3/write.id3v2.php index 32546d18af..ee7c5de2df 100644 --- a/apps/media/getID3/getid3/write.id3v2.php +++ b/3rdparty/getid3/write.id3v2.php @@ -19,6 +19,7 @@ class getid3_write_id3v2 { var $filename; var $tag_data; + var $fread_buffer_size = 32768; // read buffer size in bytes var $paddedlength = 4096; // minimum length of ID3v2 tag in bytes var $majorversion = 3; // ID3v2 major version (2, 3 (recommended), 4) var $minorversion = 0; // ID3v2 minor version - always 0 @@ -36,12 +37,12 @@ class getid3_write_id3v2 // File MUST be writeable - CHMOD(646) at least. It's best if the // directory is also writeable, because that method is both faster and less susceptible to errors. - if (is_writeable($this->filename) || (!file_exists($this->filename) && is_writeable(dirname($this->filename)))) { + if (!empty($this->filename) && (is_writeable($this->filename) || (!file_exists($this->filename) && is_writeable(dirname($this->filename))))) { // Initialize getID3 engine $getID3 = new getID3; $OldThisFileInfo = $getID3->analyze($this->filename); - if ($OldThisFileInfo['filesize'] >= pow(2, 31)) { - $this->errors[] = 'Unable to write ID3v2 because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { + $this->errors[] = 'Unable to write ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; fclose($fp_source); return false; } @@ -51,7 +52,7 @@ class getid3_write_id3v2 $this->tag_data = $this->array_join_merge($OldThisFileInfo['id3v2'], $this->tag_data); } } - $this->paddedlength = max(@$OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength); + $this->paddedlength = (isset($OldThisFileInfo['id3v2']['headerlength']) ? max($OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength) : $this->paddedlength); if ($NewID3v2Tag = $this->GenerateID3v2Tag()) { @@ -60,36 +61,31 @@ class getid3_write_id3v2 // best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary) if (file_exists($this->filename)) { - ob_start(); - if ($fp = fopen($this->filename, 'r+b')) { + if (is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'r+b'))) { rewind($fp); fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag)); fclose($fp); } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "r+b" - '.strip_tags(ob_get_contents()); + $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")'; } - @ob_end_clean(); } else { - ob_start(); - if ($fp = fopen($this->filename, 'wb')) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'wb'))) { rewind($fp); fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag)); fclose($fp); } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "wb" - '.strip_tags(ob_get_contents()); + $this->errors[] = 'Could not fopen("'.$this->filename.'", "wb")'; } - @ob_end_clean(); } } else { - if ($tempfilename = tempnam('*', 'getID3')) { - ob_start(); - if ($fp_source = fopen($this->filename, 'rb')) { - if ($fp_temp = fopen($tempfilename, 'wb')) { + if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) { + if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { + if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) { fwrite($fp_temp, $NewID3v2Tag, strlen($NewID3v2Tag)); @@ -98,7 +94,7 @@ class getid3_write_id3v2 fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); } - while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + while ($buffer = fread($fp_source, $this->fread_buffer_size)) { fwrite($fp_temp, $buffer, strlen($buffer)); } @@ -106,22 +102,16 @@ class getid3_write_id3v2 fclose($fp_source); copy($tempfilename, $this->filename); unlink($tempfilename); - @ob_end_clean(); return true; } else { - - $this->errors[] = 'Could not open '.$tempfilename.' mode "wb" - '.strip_tags(ob_get_contents()); - + $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")'; } fclose($fp_source); } else { - - $this->errors[] = 'Could not open '.$this->filename.' mode "rb" - '.strip_tags(ob_get_contents()); - + $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; } - @ob_end_clean(); } return false; @@ -138,7 +128,7 @@ class getid3_write_id3v2 } return true; } else { - $this->errors[] = '!is_writeable('.$this->filename.')'; + $this->errors[] = 'WriteID3v2() failed: !is_writeable('.$this->filename.')'; } return false; } @@ -150,12 +140,13 @@ class getid3_write_id3v2 // preferred method - only one copying operation, minimal chance of corrupting // original file if script is interrupted, but required directory to be writeable - if ($fp_source = @fopen($this->filename, 'rb')) { + if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { + // Initialize getID3 engine $getID3 = new getID3; $OldThisFileInfo = $getID3->analyze($this->filename); - if ($OldThisFileInfo['filesize'] >= pow(2, 31)) { - $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { + $this->errors[] = 'Unable to remove ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; fclose($fp_source); return false; } @@ -163,17 +154,17 @@ class getid3_write_id3v2 if ($OldThisFileInfo['avdataoffset'] !== false) { fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); } - if ($fp_temp = @fopen($this->filename.'getid3tmp', 'w+b')) { - while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp_temp = fopen($this->filename.'getid3tmp', 'w+b'))) { + while ($buffer = fread($fp_source, $this->fread_buffer_size)) { fwrite($fp_temp, $buffer, strlen($buffer)); } fclose($fp_temp); } else { - $this->errors[] = 'Could not open '.$this->filename.'getid3tmp mode "w+b"'; + $this->errors[] = 'Could not fopen("'.$this->filename.'getid3tmp", "w+b")'; } fclose($fp_source); } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "rb"'; + $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; } if (file_exists($this->filename)) { unlink($this->filename); @@ -184,12 +175,13 @@ class getid3_write_id3v2 // less desirable alternate method - double-copies the file, overwrites original file // and could corrupt source file if the script is interrupted or an error occurs. - if ($fp_source = @fopen($this->filename, 'rb')) { + if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { + // Initialize getID3 engine $getID3 = new getID3; $OldThisFileInfo = $getID3->analyze($this->filename); - if ($OldThisFileInfo['filesize'] >= pow(2, 31)) { - $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB'; + if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { + $this->errors[] = 'Unable to remove ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; fclose($fp_source); return false; } @@ -198,26 +190,26 @@ class getid3_write_id3v2 fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); } if ($fp_temp = tmpfile()) { - while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + while ($buffer = fread($fp_source, $this->fread_buffer_size)) { fwrite($fp_temp, $buffer, strlen($buffer)); } fclose($fp_source); - if ($fp_source = @fopen($this->filename, 'wb')) { + if (is_writable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'wb'))) { rewind($fp_temp); - while ($buffer = fread($fp_temp, GETID3_FREAD_BUFFER_SIZE)) { + while ($buffer = fread($fp_temp, $this->fread_buffer_size)) { fwrite($fp_source, $buffer, strlen($buffer)); } fseek($fp_temp, -128, SEEK_END); fclose($fp_source); } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "wb"'; + $this->errors[] = 'Could not fopen("'.$this->filename.'", "wb")'; } fclose($fp_temp); } else { $this->errors[] = 'Could not create tmpfile()'; } } else { - $this->errors[] = 'Could not open '.$this->filename.' mode "rb"'; + $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; } } else { @@ -237,25 +229,25 @@ class getid3_write_id3v2 switch ($this->majorversion) { case 4: // %abcd0000 - $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation - $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header - $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator - $flag .= (@$flags['footer'] ? '1' : '0'); // d - Footer present + $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation + $flag .= (!empty($flags['extendedheader'] ) ? '1' : '0'); // b - Extended header + $flag .= (!empty($flags['experimental'] ) ? '1' : '0'); // c - Experimental indicator + $flag .= (!empty($flags['footer'] ) ? '1' : '0'); // d - Footer present $flag .= '0000'; break; case 3: // %abc00000 - $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation - $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header - $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator + $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation + $flag .= (!empty($flags['extendedheader'] ) ? '1' : '0'); // b - Extended header + $flag .= (!empty($flags['experimental'] ) ? '1' : '0'); // c - Experimental indicator $flag .= '00000'; break; case 2: // %ab000000 - $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation - $flag .= (@$flags['compression'] ? '1' : '0'); // b - Compression + $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation + $flag .= (!empty($flags['compression'] ) ? '1' : '0'); // b - Compression $flag .= '000000'; break; @@ -777,7 +769,7 @@ class getid3_write_id3v2 $framedata .= chr($source_data_array['encodingid']); $framedata .= str_replace("\x00", '', $source_data_array['mime'])."\x00"; $framedata .= chr($source_data_array['picturetypeid']); - $framedata .= @$source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= (!empty($source_data_array['description']) ? $source_data_array['description'] : '').getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); $framedata .= $source_data_array['data']; } break; @@ -1146,7 +1138,9 @@ class getid3_write_id3v2 break; default: - if ($frame_name{0} == 'T') { + if ((($this->majorversion == 2) && (strlen($frame_name) != 3)) || (($this->majorversion > 2) && (strlen($frame_name) != 4))) { + $this->errors[] = 'Invalid frame name "'.$frame_name.'" for ID3v2.'.$this->majorversion; + } elseif ($frame_name{0} == 'T') { // 4.2. T??? Text information frames // Text encoding $xx // Information @@ -1624,7 +1618,9 @@ class getid3_write_id3v2 if (!$footer && ($this->paddedlength > (strlen($tagstring) + getid3_id3v2::ID3v2HeaderLength($this->majorversion)))) { // pad up to $paddedlength bytes if unpadded tag is shorter than $paddedlength // "Furthermore it MUST NOT have any padding when a tag footer is added to the tag." - $tagstring .= @str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)); + if (($this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)) > 0) { + $tagstring .= str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)); + } } if ($this->id3v2_use_unsynchronisation && (substr($tagstring, strlen($tagstring) - 1, 1) == "\xFF")) { // special unsynchronisation case: @@ -1822,7 +1818,7 @@ class getid3_write_id3v2 // hashes -> merge based on keys $keys = array_merge(array_keys($arr1), array_keys($arr2)); foreach ($keys as $key) { - $new_array[$key] = $this->array_join_merge(@$arr1[$key], @$arr2[$key]); + $new_array[$key] = $this->array_join_merge((isset($arr1[$key]) ? $arr1[$key] : ''), (isset($arr2[$key]) ? $arr2[$key] : '')); } } else { // two real arrays -> merge @@ -1880,15 +1876,15 @@ class getid3_write_id3v2 if ($parts = $this->safe_parse_url($url)) { if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) { return false; - } elseif (!eregi("^[[:alnum:]]([-.]?[0-9a-z])*\.[a-z]{2,3}$", $parts['host'], $regs) && !IsValidDottedIP($parts['host'])) { + } elseif (!preg_match('#^[[:alnum:]]([-.]?[0-9a-z])*\\.[a-z]{2,3}$#i', $parts['host'], $regs) && !preg_match('#^[0-9]{1,3}(\\.[0-9]{1,3}){3}$#', $parts['host'])) { return false; - } elseif (!eregi("^([[:alnum:]-]|[\_])*$", $parts['user'], $regs)) { + } elseif (!preg_match('#^([[:alnum:]-]|[\\_])*$#i', $parts['user'], $regs)) { return false; - } elseif (!eregi("^([[:alnum:]-]|[\_])*$", $parts['pass'], $regs)) { + } elseif (!preg_match('#^([[:alnum:]-]|[\\_])*$#i', $parts['pass'], $regs)) { return false; - } elseif (!eregi("^[[:alnum:]/_\.@~-]*$", $parts['path'], $regs)) { + } elseif (!preg_match('#^[[:alnum:]/_\\.@~-]*$#i', $parts['path'], $regs)) { return false; - } elseif (!eregi("^[[:alnum:]?&=+:;_()%#/,\.-]*$", $parts['query'], $regs)) { + } elseif (!empty($parts['query']) && !preg_match('#^[[:alnum:]?&=+:;_()%\\#/,\\.-]*$#i', $parts['query'], $regs)) { return false; } else { return true; @@ -1897,7 +1893,7 @@ class getid3_write_id3v2 return false; } - function ID3v2ShortFrameNameLookup($majorversion, $long_description) { + static function ID3v2ShortFrameNameLookup($majorversion, $long_description) { $long_description = str_replace(' ', '_', strtolower(trim($long_description))); static $ID3v2ShortFrameNameLookup = array(); if (empty($ID3v2ShortFrameNameLookup)) { @@ -2045,7 +2041,7 @@ class getid3_write_id3v2 $ID3v2ShortFrameNameLookup[4]['title_sort_order'] = 'TSOT'; $ID3v2ShortFrameNameLookup[4]['set_subtitle'] = 'TSST'; } - return @$ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)]; + return (isset($ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)]) ? $ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)] : ''); } diff --git a/apps/media/getID3/getid3/write.lyrics3.php b/3rdparty/getid3/write.lyrics3.php similarity index 91% rename from apps/media/getID3/getid3/write.lyrics3.php rename to 3rdparty/getid3/write.lyrics3.php index 6b8a47d6a1..fa49cd16ea 100644 --- a/apps/media/getID3/getid3/write.lyrics3.php +++ b/3rdparty/getid3/write.lyrics3.php @@ -30,13 +30,12 @@ class getid3_write_lyrics3 $this->errors[] = 'WriteLyrics3() not yet functional - cannot write Lyrics3'; return false; } - function DeleteLyrics3() { // Initialize getID3 engine $getID3 = new getID3; $ThisFileInfo = $getID3->analyze($this->filename); if (isset($ThisFileInfo['lyrics3']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) { - if ($fp = @fopen($this->filename, 'a+b')) { + if (is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) { flock($fp, LOCK_EX); $oldignoreuserabort = ignore_user_abort(true); @@ -61,18 +60,14 @@ class getid3_write_lyrics3 return true; } else { - - $this->errors[] = 'Cannot open "'.$this->filename.'" in "a+b" mode'; + $this->errors[] = 'Cannot fopen('.$this->filename.', "a+b")'; return false; - } } // no Lyrics3 present return true; } - - } ?> \ No newline at end of file diff --git a/3rdparty/getid3/write.metaflac.php b/3rdparty/getid3/write.metaflac.php new file mode 100644 index 0000000000..dfd6950aa7 --- /dev/null +++ b/3rdparty/getid3/write.metaflac.php @@ -0,0 +1,163 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.metaflac.php // +// module for writing metaflac tags // +// dependencies: /helperapps/metaflac.exe // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_write_metaflac +{ + + var $filename; + var $tag_data; + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_metaflac() { + return true; + } + + function WriteMetaFLAC() { + + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not written'; + return false; + } + + // Create file with new comments + $tempcommentsfilename = tempnam(GETID3_TEMP_DIR, 'getID3'); + if (is_writable($tempcommentsfilename) && is_file($tempcommentsfilename) && ($fpcomments = fopen($tempcommentsfilename, 'wb'))) { + foreach ($this->tag_data as $key => $value) { + foreach ($value as $commentdata) { + fwrite($fpcomments, $this->CleanmetaflacName($key).'='.$commentdata."\n"); + } + } + fclose($fpcomments); + + } else { + $this->errors[] = 'failed to open temporary tags file, tags not written - fopen("'.$tempcommentsfilename.'", "wb")'; + return false; + } + + $oldignoreuserabort = ignore_user_abort(true); + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'metaflac.exe')) { + //$commandline = '"'.GETID3_HELPERAPPSDIR.'metaflac.exe" --no-utf8-convert --remove-all-tags --import-tags-from="'.$tempcommentsfilename.'" "'.str_replace('/', '\\', $this->filename).'"'; + // metaflac works fine if you copy-paste the above commandline into a command prompt, + // but refuses to work with `backtick` if there are "doublequotes" present around BOTH + // the metaflac pathname and the target filename. For whatever reason...?? + // The solution is simply ensure that the metaflac pathname has no spaces, + // and therefore does not need to be quoted + + // On top of that, if error messages are not always captured properly under Windows + // To at least see if there was a problem, compare file modification timestamps before and after writing + clearstatcache(); + $timestampbeforewriting = filemtime($this->filename); + + $commandline = GETID3_HELPERAPPSDIR.'metaflac.exe --no-utf8-convert --remove-all-tags --import-tags-from='.escapeshellarg($tempcommentsfilename).' '.escapeshellarg($this->filename).' 2>&1'; + $metaflacError = `$commandline`; + + if (empty($metaflacError)) { + clearstatcache(); + if ($timestampbeforewriting == filemtime($this->filename)) { + $metaflacError = 'File modification timestamp has not changed - it looks like the tags were not written'; + } + } + } else { + $metaflacError = 'metaflac.exe not found in '.GETID3_HELPERAPPSDIR; + } + + } else { + + // It's simpler on *nix + $commandline = 'metaflac --no-utf8-convert --remove-all-tags --import-tags-from='.escapeshellarg($tempcommentsfilename).' '.escapeshellarg($this->filename).' 2>&1'; + $metaflacError = `$commandline`; + + } + + // Remove temporary comments file + unlink($tempcommentsfilename); + ignore_user_abort($oldignoreuserabort); + + if (!empty($metaflacError)) { + + $this->errors[] = 'System call to metaflac failed with this message returned: '."\n\n".$metaflacError; + return false; + + } + + return true; + } + + + function DeleteMetaFLAC() { + + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not deleted'; + return false; + } + + $oldignoreuserabort = ignore_user_abort(true); + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'metaflac.exe')) { + // To at least see if there was a problem, compare file modification timestamps before and after writing + clearstatcache(); + $timestampbeforewriting = filemtime($this->filename); + + $commandline = GETID3_HELPERAPPSDIR.'metaflac.exe --remove-all-tags "'.$this->filename.'" 2>&1'; + $metaflacError = `$commandline`; + + if (empty($metaflacError)) { + clearstatcache(); + if ($timestampbeforewriting == filemtime($this->filename)) { + $metaflacError = 'File modification timestamp has not changed - it looks like the tags were not deleted'; + } + } + } else { + $metaflacError = 'metaflac.exe not found in '.GETID3_HELPERAPPSDIR; + } + + } else { + + // It's simpler on *nix + $commandline = 'metaflac --remove-all-tags "'.$this->filename.'" 2>&1'; + $metaflacError = `$commandline`; + + } + + ignore_user_abort($oldignoreuserabort); + + if (!empty($metaflacError)) { + $this->errors[] = 'System call to metaflac failed with this message returned: '."\n\n".$metaflacError; + return false; + } + return true; + } + + + function CleanmetaflacName($originalcommentname) { + // A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. + // ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through + // 0x7A inclusive (a-z). + + // replace invalid chars with a space, return uppercase text + // Thanks Chris Bolt for improving this function + // note: *reg_replace() replaces nulls with empty string (not space) + return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname))); + + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/getid3/write.php b/3rdparty/getid3/write.php similarity index 79% rename from apps/media/getID3/getid3/write.php rename to 3rdparty/getid3/write.php index 73e261036f..16b19c7d73 100644 --- a/apps/media/getID3/getid3/write.php +++ b/3rdparty/getid3/write.php @@ -20,10 +20,10 @@ ///////////////////////////////////////////////////////////////// if (!defined('GETID3_INCLUDEPATH')) { - die('getid3.php MUST be included before calling getid3_writetags'); + throw new Exception('getid3.php MUST be included before calling getid3_writetags'); } if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { - die('write.php depends on getid3.lib.php, which is missing.'); + throw new Exception('write.php depends on getid3.lib.php, which is missing.'); } @@ -51,7 +51,7 @@ class getid3_writetags var $tagformats = array(); // array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 'metaflac', 'real') var $tag_data = array(array()); // 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis') var $tag_encoding = 'ISO-8859-1'; // text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', ) - var $overwrite_tags = true; // if true will erase existing tag data and write only passed data; if false will merge passed data with existing tag data + var $overwrite_tags = true; // if true will erase existing tag data and write only passed data; if false will merge passed data with existing tag data var $remove_other_tags = false; // if true will erase remove all existing tags and only write those passed in $tagformats; if false will ignore any tags not mentioned in $tagformats var $id3v2_tag_language = 'eng'; // ISO-639-2 3-character language code needed for some ID3v2 frames (http://www.id3.org/iso639-2.html) @@ -98,7 +98,7 @@ class getid3_writetags $this->ThisFileInfo = $getID3->analyze($this->filename); // check for what file types are allowed on this fileformat - switch (@$this->ThisFileInfo['fileformat']) { + switch (isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '') { case 'mp3': case 'mp2': case 'mp1': @@ -119,7 +119,7 @@ class getid3_writetags break; case 'ogg': - switch (@$this->ThisFileInfo['audio']['dataformat']) { + switch (isset($this->ThisFileInfo['audio']['dataformat']) ? $this->ThisFileInfo['audio']['dataformat'] : '') { case 'flac': //$AllowedTagFormats = array('metaflac'); $this->errors[] = 'metaflac is not (yet) compatible with OggFLAC files'; @@ -141,10 +141,8 @@ class getid3_writetags } foreach ($this->tagformats as $requested_tag_format) { if (!in_array($requested_tag_format, $AllowedTagFormats)) { - $errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.@$this->ThisFileInfo['fileformat']; - if (@$this->ThisFileInfo['fileformat'] != @$this->ThisFileInfo['audio']['dataformat']) { - $errormessage .= '.'.@$this->ThisFileInfo['audio']['dataformat']; - } + $errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.(isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : ''); + $errormessage .= (isset($this->ThisFileInfo['audio']['dataformat']) ? '.'.$this->ThisFileInfo['audio']['dataformat'] : ''); $errormessage .= '" files'; $this->errors[] = $errormessage; return false; @@ -216,7 +214,7 @@ class getid3_writetags // Validation of supplied data if (!is_array($this->tag_data)) { - $this->errors[] = '$tag_data is not an array in WriteTags()'; + $this->errors[] = '$this->tag_data is not an array in WriteTags()'; return false; } // convert supplied data array keys to upper case, if they're not already @@ -258,7 +256,7 @@ class getid3_writetags if (($ape_writer->tag_data = $this->FormatDataForAPE()) !== false) { $ape_writer->filename = $this->filename; if (($success = $ape_writer->WriteAPEtag()) === false) { - $this->errors[] = 'WriteAPEtag() failed with message(s):
    • '.trim(implode('
    • ', $ape_writer->errors)).'
    '; + $this->errors[] = 'WriteAPEtag() failed with message(s):
    • '.str_replace("\n", '
    • ', htmlentities(trim(implode("\n", $ape_writer->errors)))).'
    '; } } else { $this->errors[] = 'FormatDataForAPE() failed'; @@ -270,7 +268,7 @@ class getid3_writetags if (($id3v1_writer->tag_data = $this->FormatDataForID3v1()) !== false) { $id3v1_writer->filename = $this->filename; if (($success = $id3v1_writer->WriteID3v1()) === false) { - $this->errors[] = 'WriteID3v1() failed with message(s):
    • '.trim(implode('
    • ', $id3v1_writer->errors)).'
    '; + $this->errors[] = 'WriteID3v1() failed with message(s):
    • '.str_replace("\n", '
    • ', htmlentities(trim(implode("\n", $id3v1_writer->errors)))).'
    '; } } else { $this->errors[] = 'FormatDataForID3v1() failed'; @@ -286,7 +284,7 @@ class getid3_writetags if (($id3v2_writer->tag_data = $this->FormatDataForID3v2($id3v2_writer->majorversion)) !== false) { $id3v2_writer->filename = $this->filename; if (($success = $id3v2_writer->WriteID3v2()) === false) { - $this->errors[] = 'WriteID3v2() failed with message(s):
    • '.trim(implode('
    • ', $id3v2_writer->errors)).'
    '; + $this->errors[] = 'WriteID3v2() failed with message(s):
    • '.str_replace("\n", '
    • ', htmlentities(trim(implode("\n", $id3v2_writer->errors)))).'
    '; } } else { $this->errors[] = 'FormatDataForID3v2() failed'; @@ -298,7 +296,7 @@ class getid3_writetags if (($vorbiscomment_writer->tag_data = $this->FormatDataForVorbisComment()) !== false) { $vorbiscomment_writer->filename = $this->filename; if (($success = $vorbiscomment_writer->WriteVorbisComment()) === false) { - $this->errors[] = 'WriteVorbisComment() failed with message(s):
    • '.trim(implode('
    • ', $vorbiscomment_writer->errors)).'
    '; + $this->errors[] = 'WriteVorbisComment() failed with message(s):
    • '.str_replace("\n", '
    • ', htmlentities(trim(implode("\n", $vorbiscomment_writer->errors)))).'
    '; } } else { $this->errors[] = 'FormatDataForVorbisComment() failed'; @@ -310,7 +308,7 @@ class getid3_writetags if (($metaflac_writer->tag_data = $this->FormatDataForMetaFLAC()) !== false) { $metaflac_writer->filename = $this->filename; if (($success = $metaflac_writer->WriteMetaFLAC()) === false) { - $this->errors[] = 'WriteMetaFLAC() failed with message(s):
    • '.trim(implode('
    • ', $metaflac_writer->errors)).'
    '; + $this->errors[] = 'WriteMetaFLAC() failed with message(s):
    • '.str_replace("\n", '
    • ', htmlentities(trim(implode("\n", $metaflac_writer->errors)))).'
    '; } } else { $this->errors[] = 'FormatDataForMetaFLAC() failed'; @@ -322,7 +320,7 @@ class getid3_writetags if (($real_writer->tag_data = $this->FormatDataForReal()) !== false) { $real_writer->filename = $this->filename; if (($success = $real_writer->WriteReal()) === false) { - $this->errors[] = 'WriteReal() failed with message(s):
    • '.trim(implode('
    • ', $real_writer->errors)).'
    '; + $this->errors[] = 'WriteReal() failed with message(s):
    • '.str_replace("\n", '
    • ', htmlentities(trim(implode("\n", $real_writer->errors)))).'
    '; } } else { $this->errors[] = 'FormatDataForReal() failed'; @@ -421,6 +419,7 @@ class getid3_writetags if ($this->overwrite_tags) { // do nothing - ignore previous data } else { +throw new Exception('$this->overwrite_tags=false is known to be buggy in this version of getID3. Will be fixed in the near future, check www.getid3.org for a newer version.'); if (!isset($this->ThisFileInfo['tags'][$TagFormat])) { return false; } @@ -466,14 +465,12 @@ class getid3_writetags } } } - - $tag_data_id3v1['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TITLE'])); - $tag_data_id3v1['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ARTIST'])); - $tag_data_id3v1['album'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ALBUM'])); - $tag_data_id3v1['year'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['YEAR'])); - $tag_data_id3v1['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COMMENT'])); - - $tag_data_id3v1['track'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TRACKNUMBER']))); + $tag_data_id3v1['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array()))); + $tag_data_id3v1['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array()))); + $tag_data_id3v1['album'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ALBUM'] ) ? $this->tag_data['ALBUM'] : array()))); + $tag_data_id3v1['year'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['YEAR'] ) ? $this->tag_data['YEAR'] : array()))); + $tag_data_id3v1['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array()))); + $tag_data_id3v1['track'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TRACKNUMBER']) ? $this->tag_data['TRACKNUMBER'] : array())))); if ($tag_data_id3v1['track'] <= 0) { $tag_data_id3v1['track'] = ''; } @@ -520,9 +517,35 @@ class getid3_writetags } else { // source encoding is NOT valid in ID3v2 - convert it to an ID3v2-valid encoding first if ($id3v2_majorversion < 4) { - // convert data from other encoding to UTF-16 - $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1; - $tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16', $value); + // convert data from other encoding to UTF-16 (with BOM) + // note: some software, notably Windows Media Player and iTunes are broken and treat files tagged with UTF-16BE (with BOM) as corrupt + // therefore we force data to UTF-16LE and manually prepend the BOM + $ID3v2_tag_data_converted = false; + if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'ISO-8859-1')) { + // great, leave data as-is for minimum compatability problems + $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0; + $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value; + $ID3v2_tag_data_converted = true; + } + if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'UTF-8')) { + do { + // if UTF-8 string does not include any characters above chr(127) then it is identical to ISO-8859-1 + for ($i = 0; $i < strlen($value); $i++) { + if (ord($value{$i}) > 127) { + break 2; + } + } + $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0; + $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value; + $ID3v2_tag_data_converted = true; + } while (false); + } + if (!$ID3v2_tag_data_converted) { + $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1; + //$tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16', $value); // output is UTF-16LE+BOM or UTF-16BE+BOM depending on system architecture + $tag_data_id3v2[$ID3v2_framename][$key]['data'] = "\xFF\xFE".getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16LE', $value); // force LittleEndian order version of UTF-16 + $ID3v2_tag_data_converted = true; + } } else { // convert data from other encoding to UTF-8 @@ -578,10 +601,10 @@ class getid3_writetags } function FormatDataForReal() { - $tag_data_real['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TITLE'])); - $tag_data_real['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ARTIST'])); - $tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COPYRIGHT'])); - $tag_data_real['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COMMENT'])); + $tag_data_real['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array()))); + $tag_data_real['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array()))); + $tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COPYRIGHT']) ? $this->tag_data['COPYRIGHT'] : array()))); + $tag_data_real['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array()))); $this->MergeExistingTagData('real', $tag_data_real); return $tag_data_real; diff --git a/3rdparty/getid3/write.real.php b/3rdparty/getid3/write.real.php new file mode 100644 index 0000000000..ad37e74adb --- /dev/null +++ b/3rdparty/getid3/write.real.php @@ -0,0 +1,275 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.real.php // +// module for writing RealAudio/RealVideo tags // +// dependencies: module.tag.real.php // +// /// +///////////////////////////////////////////////////////////////// + +class getid3_write_real +{ + var $filename; + var $tag_data = array(); + var $fread_buffer_size = 32768; // read buffer size in bytes + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + var $paddedlength = 512; // minimum length of CONT tag in bytes + + function getid3_write_real() { + return true; + } + + function WriteReal() { + // File MUST be writeable - CHMOD(646) at least + if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) { + + // Initialize getID3 engine + $getID3 = new getID3; + $OldThisFileInfo = $getID3->analyze($this->filename); + if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) { + $this->errors[] = 'Cannot write Real tags on old-style file format'; + fclose($fp_source); + return false; + } + + if (empty($OldThisFileInfo['real']['chunks'])) { + $this->errors[] = 'Cannot write Real tags because cannot find DATA chunk in file'; + fclose($fp_source); + return false; + } + foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) { + $oldChunkInfo[$chunkarray['name']] = $chunkarray; + } + if (!empty($oldChunkInfo['CONT']['length'])) { + $this->paddedlength = max($oldChunkInfo['CONT']['length'], $this->paddedlength); + } + + $new_CONT_tag_data = $this->GenerateCONTchunk(); + $new_PROP_tag_data = $this->GeneratePROPchunk($OldThisFileInfo['real']['chunks'], $new_CONT_tag_data); + $new__RMF_tag_data = $this->GenerateRMFchunk($OldThisFileInfo['real']['chunks']); + + if (isset($oldChunkInfo['.RMF']['length']) && ($oldChunkInfo['.RMF']['length'] == strlen($new__RMF_tag_data))) { + fseek($fp_source, $oldChunkInfo['.RMF']['offset'], SEEK_SET); + fwrite($fp_source, $new__RMF_tag_data); + } else { + $this->errors[] = 'new .RMF tag ('.strlen($new__RMF_tag_data).' bytes) different length than old .RMF tag ('.$oldChunkInfo['.RMF']['length'].' bytes)'; + fclose($fp_source); + return false; + } + + if (isset($oldChunkInfo['PROP']['length']) && ($oldChunkInfo['PROP']['length'] == strlen($new_PROP_tag_data))) { + fseek($fp_source, $oldChunkInfo['PROP']['offset'], SEEK_SET); + fwrite($fp_source, $new_PROP_tag_data); + } else { + $this->errors[] = 'new PROP tag ('.strlen($new_PROP_tag_data).' bytes) different length than old PROP tag ('.$oldChunkInfo['PROP']['length'].' bytes)'; + fclose($fp_source); + return false; + } + + if (isset($oldChunkInfo['CONT']['length']) && ($oldChunkInfo['CONT']['length'] == strlen($new_CONT_tag_data))) { + + // new data length is same as old data length - just overwrite + fseek($fp_source, $oldChunkInfo['CONT']['offset'], SEEK_SET); + fwrite($fp_source, $new_CONT_tag_data); + fclose($fp_source); + return true; + + } else { + + if (empty($oldChunkInfo['CONT'])) { + // no existing CONT chunk + $BeforeOffset = $oldChunkInfo['DATA']['offset']; + $AfterOffset = $oldChunkInfo['DATA']['offset']; + } else { + // new data is longer than old data + $BeforeOffset = $oldChunkInfo['CONT']['offset']; + $AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length']; + } + if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) { + if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) { + + rewind($fp_source); + fwrite($fp_temp, fread($fp_source, $BeforeOffset)); + fwrite($fp_temp, $new_CONT_tag_data); + fseek($fp_source, $AfterOffset, SEEK_SET); + while ($buffer = fread($fp_source, $this->fread_buffer_size)) { + fwrite($fp_temp, $buffer, strlen($buffer)); + } + fclose($fp_temp); + + if (copy($tempfilename, $this->filename)) { + unlink($tempfilename); + fclose($fp_source); + return true; + } + unlink($tempfilename); + $this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')'; + + } else { + $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")'; + } + } + fclose($fp_source); + return false; + + } + + } + $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")'; + return false; + } + + function GenerateRMFchunk(&$chunks) { + $oldCONTexists = false; + foreach ($chunks as $key => $chunk) { + $chunkNameKeys[$chunk['name']] = $key; + if ($chunk['name'] == 'CONT') { + $oldCONTexists = true; + } + } + $newHeadersCount = $chunks[$chunkNameKeys['.RMF']]['headers_count'] + ($oldCONTexists ? 0 : 1); + + $RMFchunk = "\x00\x00"; // object version + $RMFchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['.RMF']]['file_version'], 4); + $RMFchunk .= getid3_lib::BigEndian2String($newHeadersCount, 4); + + $RMFchunk = '.RMF'.getid3_lib::BigEndian2String(strlen($RMFchunk) + 8, 4).$RMFchunk; // .RMF chunk identifier + chunk length + return $RMFchunk; + } + + function GeneratePROPchunk(&$chunks, &$new_CONT_tag_data) { + $old_CONT_length = 0; + $old_DATA_offset = 0; + $old_INDX_offset = 0; + foreach ($chunks as $key => $chunk) { + $chunkNameKeys[$chunk['name']] = $key; + if ($chunk['name'] == 'CONT') { + $old_CONT_length = $chunk['length']; + } elseif ($chunk['name'] == 'DATA') { + if (!$old_DATA_offset) { + $old_DATA_offset = $chunk['offset']; + } + } elseif ($chunk['name'] == 'INDX') { + if (!$old_INDX_offset) { + $old_INDX_offset = $chunk['offset']; + } + } + } + $CONTdelta = strlen($new_CONT_tag_data) - $old_CONT_length; + + $PROPchunk = "\x00\x00"; // object version + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_bit_rate'], 4); + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_bit_rate'], 4); + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_packet_size'], 4); + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_packet_size'], 4); + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_packets'], 4); + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['duration'], 4); + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['preroll'], 4); + $PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_INDX_offset + $CONTdelta), 4); + $PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_DATA_offset + $CONTdelta), 4); + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_streams'], 2); + $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['flags_raw'], 2); + + $PROPchunk = 'PROP'.getid3_lib::BigEndian2String(strlen($PROPchunk) + 8, 4).$PROPchunk; // PROP chunk identifier + chunk length + return $PROPchunk; + } + + function GenerateCONTchunk() { + foreach ($this->tag_data as $key => $value) { + // limit each value to 0xFFFF bytes + $this->tag_data[$key] = substr($value, 0, 65535); + } + + $CONTchunk = "\x00\x00"; // object version + + $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : 0), 2); + $CONTchunk .= (!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : ''); + + $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : 0), 2); + $CONTchunk .= (!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : ''); + + $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : 0), 2); + $CONTchunk .= (!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : ''); + + $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : 0), 2); + $CONTchunk .= (!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : ''); + + if ($this->paddedlength > (strlen($CONTchunk) + 8)) { + $CONTchunk .= str_repeat("\x00", $this->paddedlength - strlen($CONTchunk) - 8); + } + + $CONTchunk = 'CONT'.getid3_lib::BigEndian2String(strlen($CONTchunk) + 8, 4).$CONTchunk; // CONT chunk identifier + chunk length + + return $CONTchunk; + } + + function RemoveReal() { + // File MUST be writeable - CHMOD(646) at least + if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) { + + // Initialize getID3 engine + $getID3 = new getID3; + $OldThisFileInfo = $getID3->analyze($this->filename); + if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) { + $this->errors[] = 'Cannot remove Real tags from old-style file format'; + fclose($fp_source); + return false; + } + + if (empty($OldThisFileInfo['real']['chunks'])) { + $this->errors[] = 'Cannot remove Real tags because cannot find DATA chunk in file'; + fclose($fp_source); + return false; + } + foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) { + $oldChunkInfo[$chunkarray['name']] = $chunkarray; + } + + if (empty($oldChunkInfo['CONT'])) { + // no existing CONT chunk + fclose($fp_source); + return true; + } + + $BeforeOffset = $oldChunkInfo['CONT']['offset']; + $AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length']; + if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) { + if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) { + + rewind($fp_source); + fwrite($fp_temp, fread($fp_source, $BeforeOffset)); + fseek($fp_source, $AfterOffset, SEEK_SET); + while ($buffer = fread($fp_source, $this->fread_buffer_size)) { + fwrite($fp_temp, $buffer, strlen($buffer)); + } + fclose($fp_temp); + + if (copy($tempfilename, $this->filename)) { + unlink($tempfilename); + fclose($fp_source); + return true; + } + unlink($tempfilename); + $this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')'; + + } else { + $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")'; + } + } + fclose($fp_source); + return false; + } + $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")'; + return false; + } + +} + +?> \ No newline at end of file diff --git a/3rdparty/getid3/write.vorbiscomment.php b/3rdparty/getid3/write.vorbiscomment.php new file mode 100644 index 0000000000..ac8dc69343 --- /dev/null +++ b/3rdparty/getid3/write.vorbiscomment.php @@ -0,0 +1,121 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.vorbiscomment.php // +// module for writing VorbisComment tags // +// dependencies: /helperapps/vorbiscomment.exe // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_write_vorbiscomment +{ + + var $filename; + var $tag_data; + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_vorbiscomment() { + return true; + } + + function WriteVorbisComment() { + + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call vorbiscomment, tags not written'; + return false; + } + + // Create file with new comments + $tempcommentsfilename = tempnam(GETID3_TEMP_DIR, 'getID3'); + if (is_writable($tempcommentsfilename) && is_file($tempcommentsfilename) && ($fpcomments = fopen($tempcommentsfilename, 'wb'))) { + + foreach ($this->tag_data as $key => $value) { + foreach ($value as $commentdata) { + fwrite($fpcomments, $this->CleanVorbisCommentName($key).'='.$commentdata."\n"); + } + } + fclose($fpcomments); + + } else { + $this->errors[] = 'failed to open temporary tags file "'.$tempcommentsfilename.'", tags not written'; + return false; + } + + $oldignoreuserabort = ignore_user_abort(true); + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { + //$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w --raw -c "'.$tempcommentsfilename.'" "'.str_replace('/', '\\', $this->filename).'"'; + // vorbiscomment works fine if you copy-paste the above commandline into a command prompt, + // but refuses to work with `backtick` if there are "doublequotes" present around BOTH + // the metaflac pathname and the target filename. For whatever reason...?? + // The solution is simply ensure that the metaflac pathname has no spaces, + // and therefore does not need to be quoted + + // On top of that, if error messages are not always captured properly under Windows + // To at least see if there was a problem, compare file modification timestamps before and after writing + clearstatcache(); + $timestampbeforewriting = filemtime($this->filename); + + $commandline = GETID3_HELPERAPPSDIR.'vorbiscomment.exe -w --raw -c "'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1'; + $VorbiscommentError = `$commandline`; + + if (empty($VorbiscommentError)) { + clearstatcache(); + if ($timestampbeforewriting == filemtime($this->filename)) { + $VorbiscommentError = 'File modification timestamp has not changed - it looks like the tags were not written'; + } + } + } else { + $VorbiscommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; + } + + } else { + + $commandline = 'vorbiscomment -w --raw -c "'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1'; + $VorbiscommentError = `$commandline`; + + } + + // Remove temporary comments file + unlink($tempcommentsfilename); + ignore_user_abort($oldignoreuserabort); + + if (!empty($VorbiscommentError)) { + + $this->errors[] = 'system call to vorbiscomment failed with message: '."\n\n".$VorbiscommentError; + return false; + + } + + return true; + } + + function DeleteVorbisComment() { + $this->tag_data = array(array()); + return $this->WriteVorbisComment(); + } + + function CleanVorbisCommentName($originalcommentname) { + // A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. + // ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through + // 0x7A inclusive (a-z). + + // replace invalid chars with a space, return uppercase text + // Thanks Chris Bolt for improving this function + // note: *reg_replace() replaces nulls with empty string (not space) + return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname))); + + } + +} + +?> \ No newline at end of file diff --git a/apps/media/getID3/changelog.txt b/apps/media/getID3/changelog.txt deleted file mode 100644 index e1a3d6fcf5..0000000000 --- a/apps/media/getID3/changelog.txt +++ /dev/null @@ -1,2435 +0,0 @@ -///////////////////////////////////////////////////////////////// -/// getID3() by James Heinrich // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -///////////////////////////////////////////////////////////////// -// // -// changelog.txt - part of getID3() // -// See readme.txt for more details // -// /// -///////////////////////////////////////////////////////////////// - - » denotes a major feature addition/change - ¤ denotes a change in the returned structure - ! denotes a cry for help from developers -* Bugfix: denotes a fixed bug - -Version History -=============== - -1.7.9: [2009-03-08] James Heinrich - » Added DSS (Digital Speech Standard) support - new file: module.audio.dss.php - (thanks luke*wilkinsØdtsam*com) - » Added MPC (Musepack) SV8 support - (thanks WaldoMonster) - ¤ some MPC [header] keys renamed to be the same between SV7/SV8 - ¤ start aligning demos CSS styling with v2.x styles - new file: demos/getid3.css - ¤ JPEG now returns parsed IPTC tags in [iptc] - ¤ getid3_lib::GetDataImageSize now requires $imageinfo parameter - ¤ better support for Matroska files with AC3/DTS/MP3/OGG audio - (support still lacking for AAC) - ¤ standardize ID3v2 TCMP key to 'part_of_a_set' between reading - and writing (thanks aaron_stormØyahoo*com) - ¤ added ID3v2 keys 'TCMP','TCP' to for writing iTunes-style tags - (thanks aaron_stormØyahoo*com) - ¤ back-ported PICTURE tag handling in FLAC tags - (thanks WaldoMonster) - ¤ added alternate method to get [video][frame_rate] from QuickTime - * added partial support for "TCMP"/"TCP" ID3v2 frames (iTunes - non-standard part-of-a-compilation tag) - (thanks aaron_stormØyahoo*com) - * slightly improved scanning through FLV files speed - (thanks franki) - * faster Matroska scanning by stopping at cluster chunks once - needed header chunks are found (much faster for large files) - * added workaround for broken tagging programs that miss terminating - null byte for numeric ID3v2.4 genres - (thanks yam655Øgmail*com) - * Bugfix: MultiByteCharString2HTML() did not escape common HTML - special characters like & and ? - * Bugfix: cleaned up some malformed HTML errors in demo.browse.php - * Bugfix: under Windows files >2GB might not be processed due to - "dir" command not finding file with double directory slashes - * Bugfix: "MODule (assorted sub-formats)" was falsely matching - some random files (e.g. JPEGs) (thanks qwertywin) - * Bugfix: suppress PHP_notice on failed SWF-compressed - decompression failure (thanks mkron) - - -1.7.8b3: [2008-07-13] James Heinrich - » Experimental partial support for files > 2GB (gets filesize - from shell call to "dir" or "ls", parse files with PHP only - up to 2GB limit). See readme.txt for details on what formats - work properly and other limitations - » Initial support for Matroska. Has only been tested with a - limited number of sample files, please report any bugs - » Experimental support for PHP-RAR reading. Known buggy, disabled - by default, enable with care - ¤ getid3_lib::CastAsInt() now returns ints up to 2^31 (not 2^30) - ¤ Quicktime: [video] now returns [frame_rate] and [fourcc] for MP4 - video files - * MP3: headerless VBR files now only have up to 10 blocks of 5000 - frames each scanned by default and bitrate extrapolated from that - distribution for speed (thanks glau*stuffØridiculousprods*com) - * Quicktime: support "co64" atom - * SWF: lower memory use when compressed SWF files processed - (thanks doughammondØblueyonder*co*uk) - * Bugfix: FLV height and width was calculated incorrectly - (thanks moysevichØgmail*com) - * Bugfix: FLV GETID3_FLV_TAG_META parsed incorrectly - (thanks moysevichØgmail*com) - * Bugfix: Quicktime: 'tkhd' matrix_v and matrix_d were switched - (thanks rjjmoroØhotmail*com) - * Bugfix: Quicktime: frame_rate was often incorrect for MP4 video - * Bugfix: getid3_lib::CastAsInt returned -2147483648 when passed - 2147483648 (0x80000000) - - -1.7.8b2: [2007-10-15] James Heinrich, Allan Hansen - * Video bitrate now calculated even if not explicitly stated in - file metadata, but if overall and audio bitrates are known - * Bugfix: 'comments_html' missing last letter in id3v2 tags. - * Bugfix: module objects (e.g. getid3_riff) that are instantiated - in other modules are explicitly disposed once no longer needed. - * Bugfix: some AVI files were not returning audio information - because "strh" chunk was not being read in - * Bugfix: asf [audio][][dataformat] should be set - to "wma" but wasn't - * Bugfix: [mpeg][audio][bitrate_mode] should always be one of - ("cbr", "vbr", "abr") but wasn't for some values in - LAMEvbrMethodLookup() - * Bugfix: MP3 audio in AVI files could show "cbr" instead of - correct audio bitrate_mode, and audio bitrate could be slightly - incorrect if multiple files were scanned in a loop (scanning - single files produced correct values). - * Bugfix: remove [audio/video][bitrate] key if falsely set to zero - * Bugfix: PlaytimeString returned non-matching value for negative - playtimes (which shouldn't happen either, but now they're at - least shown correctly, if they happen due to other bugs) - * Bugfix: Several ASF header values are invalid if the broadcast - flag is set, getID3() now calculates these values in other - ways if the broadcast flag is set (thanks fletchØpobox*com) - * Bugfix: lyrics3-flags-lyrics field was always false, and there - never was a lyrics3-flags-timestamp field present even though - the lyrics3-raw-IND field consisted of "10" (lyrics present, - timestamp not present). (thanks i*f*schulzØweb*de) - * Bugfix: TAR.GZ files produce PHP errors when - option_gzip_parse_contents == true in module.archive.gzip.php - (thanks alan*harderØsun*com) - - -1.7.8b1: [2007-01-08] Allan Hansen - » Major update to readme.txt - » PHP 4.2.0 required - » Tagwriter requires metaflac 1.1.1+ in order to write FLAC tags. - » Removed broken and non-fixable tagwriting module for real format. - ! Developers please help fix the above module: - http://www.getid3.org/phpBB2/viewtopic.php?t=677 - » Avoided security issues with demo.browse.php, demo.write.php and - demo.mysql.php. These demos are now disabled by default and has - to be enabled in the source. - * Bugfix: id3v2 genre broken since 1.7.7. - » Added DTS module (module.audio.dts.php) - ¤ ASF/WMV files now return largest video stream dimensions in - [video][resolution_x] and [video][resolution_y] - * Bugfix: Minor issues with midi module (avoid PHP_NOTICE). - * Bugfix: Minor issues with lyrics3 (avoid PHP_NOTICE). - * Bugfix: PHP_NOTICE issues in MultiByteCharString2HTML() - * Bugfix: PHP_NOTICE issue in BigEndian2Float() - * Bugfix: fread() zero bytes issue in real module. - * Bugfix: ASF module returned mime type video/x-ms-wma instead of - video/x-ms-wmv for certain FourCCs. - * Bugfix: PHP_NOTICE issues with broken ID3v2 tag/garbage. - * Bugfix: PNG module broken in regards to gIFg and gIFx chunks. - » Removed detection of short filenames 8dot3 under windows, as - it only worked for English versions of windows and has other - problems. - * Bugfix: Some CBR MP3 files detected as VBR with plenty of warnings. - * Bugfix: PHP_NOTICE issues in MP3 module. - * Bugfix: Quicktime returned incorrect frame rate. - * Bugfix: DivByZero on zero length FLV files. - * Bugfix: PHP_NOTICE one some FLV files. - * Bugfix: ID3v2 UTF-8/16 encoded frames terminated by \x00 - * Bugfix: ID3v2 LINK frames iconv error. - * Bugfix: ID3v2 padding length calculated incorrectly. - * Bugfix: ID3v2.3 extended headers non-conformance - » SVG file detection. - » Added SVG user module (user_modules/module.graphic.svg.php). - Thanks to Roan Horning. - » PAR2 file detection (no parsing) - * Bugfix: Wave files being detected as MP3. - * Bugfix: ASF padding offset bug. - * Bugfix: Shorten module not working for wav files with fmt - chunks <> 16 bytes. - ¤ RIFF: Zero sized chunk invokes warning instead of error. - ¤ FLAC: Removed some ['raw'] keys. - ¤ MPC: Mime type returned: audio/x-musepack - -1.7.7: [2006-06-25] Allan Hansen - * Bugfix: AAC static bitrate cache wrong result when parsing - several files. - * Bugfix: Do not return NULL video bitrate for ASF v3. - * Bugfix: getid3_lib::GetImageSize() broken => JPG module broken. - * Bugfix: Encoder options should now be returned with correct - "--alt-preset n" / "--alt-preset cbr n" when scanning more files. - * Bugfix: Shorten module not escapeshellarg() filenames (UNIX only). - * Bugfix: Filenames not escapeshellarg() for md5_data and - sha1_data (UNIX only). - * Bugfix: UNIX: head and tail called with -cNNN instead of "-c NNN". - » Added detection support for PDF and MS Office documents - (*.doc, *.xls, *.pps, etc) (thanks zeromassmediaØgmail*com) - ¤ Bugfix: ID3v2 "TDRC" frame now used as "year" in comments if TYER - unavailable (TYER is deprecated in ID3v2.4) - (thanks matthiasØpanczyk*org) - ¤ Removed GETID3_OS_DIRSLASH, replaced with DIRECTORY_SEPARATOR - * Bugfix: added LAME preset guessing for presets 410,420,440,490 - (thanks adminØlogbud*com) - * Bugfix: Added escapeshellarg() call in getid3_lib::hash_data - (thanks towbØgmx*net) - » TAR module no longer reads entire file into memory - » FLV module no longer reads entire file into memory - * Bugfix: added LAME preset guessing for presets 410,420,440,490 - (thanks adminØlogbud*com) - * Bugfix: Added escapeshellarg() call in getid3_lib::hash_data - (thanks towbØgmx*net) - * Bugfix: Error message when padding in FLAC files were used up. - * Bugfix: Shorten module not working under windows. - ¤ Bugfix: gmmktime() instead of mktime(). - ¤ Using gmmktime() instead of mktime() in ISO, ZIP, PNG and RIFF - modules to avoid E_STRICT notices with PHP5.1+. - * Bugfix: ['comments_html'] and ['comments'] contains different - value when having multiple tags (one of them ID3v1) and the - long field names. - -1.7.6: [2006-03-12] James Heinrich - * Rewrote getid3_lib::GetDataImageSize() to use GetImageSize() - instead of using code by filØrezox*com - * Bugfix: incorrect dimensions from disabled Quicktime tracks - (thanks m-1Øgmx*net) - * Bugfix: ['codec'] key warning in module.audio-video.asf.php - (thanks niel*archerØblueyonder*co*uk) - * Bugfix: undefined array in write.php - (thanks drewishØkatherinehouse*com) - * Bugfix: DeleteAPEtag() incorrectly failing when no tag present - (thanks drewishØkatherinehouse*com) - * Bugfix: ID3v2 writing frames with URL fields failing when URL - is not in ISO-8859-1 (thanks drewishØkatherinehouse*com) - * Bugfix: PHP notices on bad ID3v2 frames - (thanks cw264701Øohiou*edu) - * Bugfix: audio & video bitrates sometimes wrong in ASF files - (thanks kris_kauperØexcite*com) - -1.7.5: [2005-12-29] James Heinrich - » Added FLV (FLash Video) support -- new file: - module.audio-video.flv.php - (thanks Seth Kaufman for code) - » Real tags can now be written (previous Real tag writing - code was not supposed to be in public releases, as it - was not complete) - » GETID3_HELPERAPPSDIR now autodetected under Windows - ¤ ASF lyrics now returned under [comments][lyrics] - * Bugfix: removed "--lowpass xxxxx" info from guessed - LAME presets when source frequency <= 32kHz - * Bugfix: ID3v2 extended header errors - * Bugfix: missing ob_end_clean() in write.id3v2.php - (thanks rasherØgmail*com) - -1.7.4: [2005-05-04] James Heinrich - ¤ Added ['quicktime']['hinting'] key (boolean) - (thanks jonØwebignition*net) - * Bugfix: major UTF-8 to UTF-16/ISO-8859-1 conversion - bug (empty string returned) when using iconv_fallback - (thanks chrisØfmgp*com) - * Bugfix: Missing 'lossless' key in RIFF-WAV - (thanks bobbfwedØcomcast*net) - -1.7.3: [2005-04-22] James Heinrich - » Added TAR support -- new file: module.archive.tar.php - (thanks Mike Mozolin for code!) - » Added GZIP support -- new file: module.archive.gzip.php - (thanks Mike Mozolin for code!) - * Bugfix: demo.browse.php now displays embedded images - internally instead of passing local filename as IMG - SRC (should allow demo.browse.php to correctly show - embedded images over a network) - (thanks patpowermanØhotmail*com) - * Bugfix: minor UTF-8 display issues in demo.browse.php - * Bugfix: demo.browse.php now works even if the evil - setting magic_quotes_gpc is turned on - (thanks patpowermanØhotmail*com) - * Bugfix: incorrect MIDI playtime for some files - (thanks joelØoneporpoise*com) - * Bugfix: 'url_source' typo in module.tag.id3v2.php - (thanks richardlynchØusers*sourceforge*net) - * Bugfix: Quicktime 'mvhd' matrix values were wrong - (thanks webØbobbymac*net) - ¤ ID3v2 now returns xx/yy for ['track'] (if - available), with xx in ['tracknum'] and yy in - ['totaltracks']. Previously ['tracknum'] was not - available and ['track'] had only xx. - Bugfixes and improvements to /demo/demo.mysql.php: - - remix/version parsed from tags and stored in - database, can be used when renaming files - - track number can be used for renaming files - - -1.7.2: [2004-10-18] Allan Hansen - » Added support for WavPack v4.0+ - (thanks ahØartemis*dk) - » Removed code for parsing EXE files - (thanks ahØartemis*dk) - Removed file: module.misc.exe.php - * Bugfix: Large ID3v2 tags inside ASF not parsed - properly under PHP5. - * Bugfix: Certain Wavpack3 files failed under PHP5 due - to new undocumented tmpfile() limit (same problem as - above). - * Bugfix: New iTunes crashes PHP - temp fix - no tags - on those files. - * Bugfix: ['nsv']['NSVs']['framerate_index'] might be - wrong (thanks ahØartemis*dk) - * Bugfix: transparent color was wrong from truecolor - PNG (thanks ahØartemis*dk) - * Bugfix: Changed MPC SV7 header size from 30 to 28, - this will change hash values for MPC files - (thanks ahØartemis*dk) - * Bugfix: Changed MPC SV4-6 header size from 28 to 8, - this will change hash values for MPC files - (thanks ahØartemis*dk) - ¤ Trim/unset wavpack encoder_options to match 2.0.0b2 - output. - ¤ Commented-out unknown/unused values in NSV and ISO - modules (thanks ahØartemis*dk) - - -1.7.1b1: [July-26-2004] James Heinrich - » Added support for Apple Lossless Audio Codec - » Added support for RealAudio Lossless - » Added support for TTA v3 - » Added support for TIFF - New file: /getid3/module.graphic.tiff.php - » Modified iconv_fallback to work with UTF-8, UTF-16, UTF-16LE, - UTF-16BE and ISO-8859-1 even if iconv() and/or XML support is - not available. This means that iconv() is no longer required - for most users of getID3() - (thanks Jeremia, khleeØbitpass*com) - » Added support for Monkey's Audio v3.98+ (thanks ahØartemis*dk) - » Included new demo showing most-basic getID3() usage - New file: /demos/demo.basic.php - * Bugfix: LAME3.94+ presets cached incorrectly if multiple files - are scanned in one batch and first file is LAME3.93 or earlier - (thanks enoyandØyahoo*com) - * Bugfix: Added warning if compressed ID3v2 frame decompression - fails. (thanks Mike Billings) - * Bugfix: Assorted small fixes to ensure compatability with PHP5 - * Bugfix: ID3v1 genre "Blues" could not be written - (thanks Jeremia) - * Bugfix: ['bitrate_mode'] typo in module.audio-video.real.php - (thanks asukakenjiØusers*sourceforge*net) - * Bugfix: ['zip']['files'] is now populated with filenames even - if End Of Central Directory couldn't be parsed - * Bugfix: ['audio']['lossless'] was incorrect for FLAC - (thanks WaldoMonster) - * Bugfix: MD5 File was incorrect in directory browse mode for - /demo/getid3.browse.php - * Bugfix: PHP v5 compatability changes (float array keys, fread() - calls with zero data length) - (thanks getid3Øjsc*pp*ru) - * Bugfix: was dying if on compressed ID3v2 frames if - gzuncompress() function was unavailable - * Bugfix: ['vqf']['COMM'] was always empty - * Bugfix: MIDI playtime was missing for single-track MIDI files - * Bugfix: removed � characters from ['comments_html'] - (thanks p*quaedackersØplanet*nl) - * Bugfix: improved MIDI playtime accuracy - (thanks joelØoneporpoise*com) - * Bugfix: BMP subtypes 4 and 5 were not being identified - * Bugfix: frame_rate in AVI was incorrectly truncated to integer - * Bugfix: FLAC cuesheet track index was incorrect - (thanks tetsuo*yokozukaØoperamail*com) - ¤ ['quicktime']['display_scale'] now contains the playback scale - multiplier for QuickTime movies - a movie set to playback at - double-size will have "2" here. Other values are "1" and "0.5" - ¤ Added LAME preset guessing for --preset medium with v3.90.3 - (thanks phwipØfish*co*uk) - ¤ Added $encoding_id3v1 to allow for ID3v1 encodings other than - the standard ISO-8859-1 - ¤ Default AVI video bitrate_mode is now 'vbr' - (thanks eltoderØpisem*net) - Force getID3() to abort if Shorten files have ID3 or APE tags - (thanks ahØartemis*dk) - Editable textbox for parent directory in demo.browse.php - (thanks eltoderØpisem*net) - - -1.7.0-hotfix [2004-03-17] Allan Hansen - (hotfix version released by Allan Hansen) - * Bugfix: PHP 4.1.x compatiblity - fgets($fp) => fgets($fp, 1024) - * Bugfix: Added default charset to TextEncodingNameLookup() in - module.tag.id3v2.php - Ø Removed option_no_iconv - iconv() support is only a requirement for WMA/WMW/ASF, and for - destination encodings other than ISO-8859-1 and UTF-8, iconv is - not needed otherwise. New 'iconv_req' in GetFileFormatArray() - only set for WMA/WMV/ASF. analyze() now refuses to analyse - WMA/ASF file if iconv is not present. - iconv_fallback() only dies on internal errors not missing iconv() - - -1.7.0: [January-19-2004] James Heinrich - » Added support for RIFF/CDXA files (MPEG video in RIFF container - format (thanks chrisØdigitekdesign*com) - » Added support for TTA v2 (thanks ahØartemis*dk) - ¤ ID3v2 unsynchronisation scheme disabled by default because most - tag-reading programs cannot read unsynchronised tags. Can be - overridden by setting id3v2_use_unsynchronisation to true. - (thanks mikeØdelusion*org) - ¤ extention.*.php renamed to extension.*.php - (thanks tp62Øcornell*edu) - ¤ /demo/demo.check.php renamed to /demo/demo.browse.php - ¤ Added id3v2_paddedlength configuration parameter to WriteTags() - and renamed tag_language to id3v2_tag_language - ¤ MPEG audio layers are now represented as 1, 2 or 3 instead of - 'I', 'II', or 'III' - ¤ Added [audio][wformattag] and [video][fourcc] for WAV and AVI - ¤ Added [audio][streams] which contains one entry for each audio - stream present in the file (usually only one). The data is a - copy of what is usually found in [audio]. If there are multiple - audio streams then [audio] will contain a sum of the bitrates - of all audio streams, and the data format of the first stream - (if streams are of different data types) - ¤ Added BruteForce mode to mp3 scanning. Disabled by default as - it is extremely slow and only files that are broken enough to - not really play will gain any benefit from this. - ¤ Suppress '--resample xxxxx' appended to encoder options for mp3 - with low-quality presets for default sampling frequencies - ¤ Enhanced LAME preset guessing for pre-3.93 with a better lookup - table, --resample/--lowpass guessing (thanks phwipØfish*co*uk) - ¤ RIFF files with non-MP3 contents no longer have - [audio][encoder_options] set - ¤ Added [audio][encoder_options] to audio formats where possible - (including LiteWave, LPAC, OptimFROG, TTA) - ¤ Moved [quantization] and [max_prediction_order] from - [lpac][flags] to just [lpac] - ¤ WavPack flags are now parsed into [wavpack][flags] - * Bugfix: APEtags with ReplayGain information stored with comma- - seperated decimal values (ie "0,95" instead of "0.95") were - giving wrong peak and gain values - * Bugfix: Filesize > 2GB not always detected correctly - * Bugfix: Some ID3v2 frames had data key unset incorrectly - (thanks chrisØdigitekdesign*com) - * Bugfix: Warnings on empty-strings-only comments - * Bugfix: ID3v2 tag writing may have had incorrect padding length - if padded length less than current ID3v2 tag length and - merge_existing_data is false (thanks mikeØdelusion*org) - * Bugfix: hash_data() for SHA1 was broken under Windows - * Bugfix: BigEndian2Float()/LittleEndian2Float() were broken - * Bugfix: LAME header calculated track peaks were incorrect for - LAME3.94a15 and earlier - * Bugfix: AVIs with VBR MP3 audio data reported incorrect bitrate - and bitrate_mode - * Bugfix: AVIs sometimes had incorrect or missing video and total - bitrates - * Bugifx: AVIs sometimes had incorrect ['avdataend'] and - therefore also incorrect data hashes (md5_data, sha1_data) - * Bugfix: ID3v1 genreid no longer returned for Unknown genre - * Bugfix: ID3v1 SCMPX genres were broken - Modified LAME header parsing to correctly process peak track - value for LAME3.94a16+ (thanks Gabriel) - md5_file() and sha1_file() now work under Windows in PHP < 4.2.0 - and 4.3.0 respectively with helper apps - Default md5_data() tempfile location is now system temp directory - instead of same directory as file (thanks towbØtiscali*de) - Improved list of RIFF ['INFO'] comment key translations - More helpful error message when GETID3_HELPERAPPSDIR has spaces - /demo/demo.browse.php now autogets both MD5 and SHA1 hashes for - files < 50MB - Replaced PHP_OS comparisons with GETID3_OS_ISWINDOWS define - (thanks necroticØusers*sourceforge*net) - - -1.7.0b5: [December-29-2003] James Heinrich - » Windows only: Various binary files are now required for some - file formats, especially for tag writing, as well as md5sum - (and other) calculations. These binaries are now stored in the - directory defined as GETID3_HELPERAPPSDIR in getid3.php - (default is /helperapps/ parallel to /getid3/). - Note: This directory must not have any spaces in the pathname. - All neccesary files are available as a seperate download. - See /helperapps/readme.txt for more information - New file: /helperapps/readme.txt - » Unified tag-writing interface for all tag formats - New file: /getid3/write.php - /getid3/write.apetag.php - /getid3/write.id3v1.php - /getid3/write.id3v2.php - /getid3/write.lyrics3.php - /getid3/write.metaflac.php - /getid3/write.vorbiscomment.php - » Added support for Shorten - requires shorten binary (head.exe - is also required under Windows). - New file: /getid3/module.audio.shorten.php - » Added support for RKAU - New file: /getid3/module.audio.rkau.php - » Added (minimal) support for SZIP - New file: /getid3/module.archive.szip.php - » Added MySQL caching extention (thanks ahØartemis*dk) - New file: /getid3/extention.cache.mysql.php - » Added DBM caching extention (thanks ahØartemis*dk) - New file: /getid3/extention.cache.dbm.php - » Added sha1_data hash option (thanks ahØartemis*dk) - » Added option to allow getID3() to skip ID3v2 without parsing it - for faster scanning when ID3v2 data is not required. If you - want to enable this feature delete /getid3/module.tag.id3v2.php - (thanks ahØartemis*dk) - ¤ 8-bit WAV data now calculates MD5 checksums as normal, not - converting to signed data as before, so stored md5_data_source - in FLAC files will no longer match md5_data for the equivalent - decoded 8-bit WAV. A warning will be generated for 8-bit FLAC - files - ¤ Added option_no_iconv option to allow getID3() to work - partially without iconv() support enabled in PHP - (thanks ahØartemis*dk) - ¤ All '*_ascii' keys removed for ASF/WMA/WMV files - ¤ All 'ascii*' keys removed for ID3v2 tags - ¤ Ogg filetypes now return MIME of "application/ogg" instead of - the previous "application/x-ogg" - (thanks blakewattersØusers*sourceforge*net) - ¤ Force contents of ['id3v2']['comments'] to UTF-8 format from - whatever encoding each frame may have (text encoding can vary - from frame to frame in ID3v2) - ¤ MP3Gain information from APE tags suppressed from ['tags'] and - parsed into ['replay_gain'] - ¤ ReplayGain information (all formats) changed from "Radio" and - "Audiophile" to "Track" and "Album" respectively - ¤ ['volume'] and ['max_noclip_gain'] are now available in both - ['replay_gain']['track'] and ['replay_gain']['album'] for all - formats that calculate ReplayGain. - ¤ ['video']['total_frames'] is available for AVIs - ¤ All parsed ID3v2 frame data is now in ['id3v2'][XXXX][#] - (previously some frame types would have numeric array keys if - multiple instances of that frame type were allowed and other - frame types would not) - ¤ ASF/WMA "WM/Picture" images are now parsed in the same manner - as ID3v2 with the image (ex JPEG) data returned in [data] - rather than [value] - * Bugfix: Optional tag processing options were being ignored (ie - ID3v1 still processed even if option_tag_id3v1 == false) - (thanks ahØartemis*dk) - * Bugfix: fixed MultiByteCharString2HTML() for UTF-8 - * Bugfix: Quicktime files not always reporting video frame_rate - * Bugfix: False ID3v1 synch patterns in APE or Lyrics3 tags are - now detected and incorrect ID3v1 data not returned - (thanks sebastian_maresØusers*sourceforge*net for the idea) - * Bugfix: WMA9 Lossless now reported as lossless - * Bugfix: two typos in ID3v1 genre list - * Bugfix: MPEG-2/2.5 ABR/VBR MP3 files had doubled playtime - * Bugfix: MPEG-2/2.5 LayerII (ie MP2: 24/22.05/16kHz) files were - not detected due to incorrect frame length calculation - * Bugfix: MPEG LayerI files were not detected due to incorrect - frame length calculation (must be multiple of slot length) - Added alternative md5_data via system call - twice as fast. Needs - "getID3()-WindowsSupport" to work under Windows. - (thanks ahØartemis*dk) - ID3v2.4 compressed frames are now supported - php_uname() calls changed to use PHP_OS constant - Added SCMPX extensions to ID3v1 genres (0xF0-0xFE) - Obfuscated contributor email address in changelog and sourcecode - Added memory-saving EmbeddedLookup() function for lookup tables - in RIFF and ID3v2 modules (thanks ahØartemis*dk) - Major memory patches to many modules by using - $var = &$INFO_ARRAY_AT_SOME_INDEX - in place of large multi-dimensional array declarations. - Memory saved: RIFF: ~200kB; ID3v2: ~475kB; ASF: ~50kB etc. - (thanks ahØartemis*dk) - - -1.7.0b4: [November-19-2003] James Heinrich - » Support added for MPC files with old SV4-SV6 structure - » RealVideo now properly supported with resolution, framerate, etc - (thanks jcsston) - » RealAudio files with old-style file format (v2-v4) are now - fully supported - » Support added for DolbyDigital WAV files (thanks ahØartemis*dk) - ¤ ['RIFF'] is now ['riff'] to conform to make all root key names - lowercase - ¤ ['OFR'] is now ['ofr'] to conform to make all root key names - lowercase - ¤ ['tags_html'] is now available as a copy of ['tags'] but - with all text replaced with an HTML version of all characters - above chr(127), translated according to whatever the encoding - of the source tag is, in the HTML form Ӓ - ¤ CopyTagsToComments() is now available in getid3_lib - ¤ QuicktimeVR files now return a ['video']['dataformat'] of - 'quicktimevr' instead of 'quicktime' (thanks gtsØtsu*biz) - ¤ Quicktime video files with DivX, Xvid, 3ivx or MPEG4 video - streams now return those names as ['video']['dataformat'] - ¤ MPEG video files are now identified with ['video']['codec'] set - to either 'MPEG-1' or 'MPEG-2' (rather than just 'MPEG'). If you - see a file wrongly identified, please report it! - (thanks fccHandler) - ¤ All bitrate values in ['mpeg']['audio'] is now reported in bps - rather than kbps (ie 128000 instead of 128) for consistancy - ¤ AVIs with MP2 audio now report ['audio']['dataformat'] as 'mp2' - rather than 'wav' (thanks metalbrainØnetian*com) - ¤ Added ['md5_data_source'] for OptimFROG - ¤ AC3 in RIFF-WAV now identified with ['audio']['dataformat'] - returning 'ac3' - ¤ WavPack ['extra_bc'] now returned as integer - ¤ WavPack ['extras'] now returned as 3-element array of integers - ¤ MP3 ['audio']['encoder options'] now returns 'VBR' or 'CBR' only - (no bitrate) if no LAME preset is used, or 'VBR q??' where ?? is - a number 0-100 for Fraunhofer-encoded VBR MP3s - * Bugfix: VBR MP3s could have incorrect bitrate reported - * Bugfix: Quicktime files with MP4 audio were not returning - ['video']['dataformat'] (thanks robØmassive-interactive*nl) - * Bugfix: strpad vs str_pad typo in module.riff.php - (thanks nicojunØusers*sourceforge*net) - * Bugfix: ReplayGain information was often wrong for MPC files - * Bugfix: MD5 and other post-TAIL chunks were not being processed - in module.audio.optimfrog.php - * Bugfix: Undefined variable in table_var_dump() in demo/check.php - * Bugfix: QuickTime files now only return information in [audio] - or [video] if those exist in the file - * Bugfix: WavPack no longer tries to read entire compressed data - chunk - * Bugfix: Properly handle VBR MP3s with "Info" (rather than - "Xing") header frame. foobar2000 adds this to MP3 files when - "Fix MP3 Header" function is used (thanks ahØartemis*dk) - * Bugfix: Fraunhofer VBRI headers for MP3s were assuming 2-byte - entries for TOC rather than using stride, and were ignoring the - scaling value. (thanks sebastianØmaresweb*net) - Several QuickTime atoms have been added to an exclusion list - because they have been observed, but I have no idea what they - are supposed to do so I can't add real support for them, but - they should not generate warnings (robØmassive-interactive*nl) - Old MPC encoder (before v1.06) was return as v0.00, now returned - as 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05' - (thanks ahØartemis*dk) - Added check for magic_quotes_runtime and code to disable it if - neccesary (thanks stefan*kischkelØt-online*de) - Added 3ivx fourCCs to module.audio-video.quicktime.php - MP3 and AC3 streams are now parsed when contained inside RIFF-WAV - or RIFF-AVI container formats - Better detection of named presets in LAME 3.93/3.94 - - -1.7.0b3: [October-17-2003] James Heinrich - » AC-3 (aka Dolby Digital) is now supported. - New file: /getid3/module.audio.ac3.php - * Bugfix: ID3v2-writing function Unsynchronise() was broken, which - made ID3v2 tag containing binary data (typically pictures) get - corrupted. (thanks t*coombesØbendigo*vic*gov*au, - i*kuehlbornØndh*net, mikeØdelusion*org, mikeØftl*com) - * Bugfix: Zip comments now returned as array instead of string, - as they're supposed to be. - * Bugfix: Quicktime/MP4 files may have reported extremely low - bitrates (thanks spunkØdasspunk*com) - Improved double-ID3v1 check to prevent false detection when string - "TAG" is present in APE or Lyrics3 - Fixed /demo/simple.php - Fixed /demo/joinmp3.php - Fixed /demo/mimeonly.php - Fixed /demo/write.php - - -1.7.0b2: [October-15-2003] James Heinrich - » TTA Lossless Audio Compressor format now supported. - (http://tta.iszf.irk.ru) - New file: /getid3/module.graphic.tta.php - » PhotoCD (PCD) format now supported. Image data for the three - lowest resolutions (192x128, 384x256, 768x512) can be optionally - extracted. - New file: /getid3/module.graphic.pcd.php - ¤ RIFF-MP3 files now should return the same ['md5_data'] as the - identical MP3 file outside the RIFF container - ¤ Name of LAME preset used (if available, needs LAME v3.90+) - returned in ['mpeg']['audio']['LAME']['preset_used'] and also as - part of ['audio']['encoder_options'] - ¤ VQF module now sets ['audio']['encoder_options'] to i.e. CBR96 - ¤ MP3 module now sets ['audio']['encoder_options'] on CBR files - and all LAME-encoded files - ¤ MPC module now sets ['audio']['encoder_options'] - ¤ Monkey module now sets ['audio']['encoder_options'] - ¤ AAC module now sets ['audio']['encoder_options'] to profile name - ¤ ASF module now sets ['audio']['encoder_options'] - ¤ Ogg module adds ['audio']['encoder_options'] -b 128 on - Ogg Vorbis 1.0+ ABR files - ¤ Ogg module adds ['audio']['encoder_options'] -q N on - Ogg Vorbis 1.0+ VBR files 44k/48k sample rate/stereo files only. - ¤ Ogg module ['audio']['encoder_options'] "Nominal birate: 80k" to - other Ogg Vorbis files. - ¤ ID3v2 track number now returned as string (with leading zeros, - if present in data) rather than integer (thanks Plamen) - ¤ ASF module returns ['asf']['comments']['encoding_time_unix'] if - available (from WM/EncodingTime) - ¤ Fixed /demo/mysql.php and added some new features: - - encoder options - - ID3v2 "Encoded By" - - non-empty comments - - total entries in database summary (totals & averages) - - database version update - * Bugfix: 'UNICODE' iconv() charset changed to 'UTF-16LE' or - 'UTF-16BE' as appropriate - * Bugfix: iconv_fallback() function created in case iconv() fails - * Bugfix: fixed MD5 calls in demo/check.php - * Bugfix: reenabled detection of APE + Lyrics3 tags in same file - * Bugfix: ASF module now returns ID3v1 genre as string instead of - number - patch from Eugene Toder. - * Bugfix: ASF module now reads non-standard field names, - i.e. "date" as well as WM/Year - patch from Eugene Toder. - * Bugfix: ASF module now returns genre as-is if it is not a - standard ID3v1 genre (thanks wonderboy) - * Bugfix: Eliminated false-synch problem in MP3 module - * Bugfix: Fixed missing root ['bitrate'] for most formats - * Bugfix: ['audio']['compression_ration'] missing for MPC - (thanks WaldoMonster) - * Bugfix: NSV module died in 1.7.0b1 - * Bugfix: ASF module died in 1.7.0b1 when WM/Picture preset - * Bugfix: ASF tracknumber incorrect when specified by WM/Track - rather than WM/TrackNumber (thanks jgriffiniiiØhotmail*com) - * Bugfix: MPEG audio+video playtime should now be pretty accurate - (ie within 0.1% variation at most) - (thanks mgrimmØhealthtvchannel*org) - * Bugfix: ID3v2 not being copied to ['tags'] in some cases - * Bugfix: LAME CBR files with Info tag were being incorrectly - flagged as VBR (thanks Jojo) - * Bugfix: LAME tag not being detected for LAME 3.90 (original) - Changed regex pattern match for MP3 to include 3rd byte for more - reliable/accurate pattern matching - Added duplicate-ID3v1 tag checking (two ID3v1 tags, one after the - other) that has been known to occur with iTunes - (thanks towbØtiscali*de) - Added instructions for enabling iconv() support under Windows - Removed some unneccesary debugging code - Suppressed duplicate PHP warnings for missing include files - Included some missing dependencies in various files - /demo/audioinfo.class.php now copies ['audio']['encoder_options'] - - -1.7.0b1: [2003-09-28] Allan Hansen - This beta version was not made by James Heinrich. It was made by - Allan Hansen - please send bug reports on this - beta directly to me. - - James Heinrich will release 1.7.0 final, but it may take some time - to work out the bugs from the major rewrite. - - This version could be called getID3lite. It makes a lot of checks - optional and makes it easy to remove support for undesired formats - - It also is more library-like. Older versions of getID3() declared - an incredible amount of global scope functions and defined several - constants. 1.7.0beta1 still declares constants, but they are all - prepended by GETID3_. It declares no global scope functions - they - are all wrapped into classes. - - » Made getID3() depend on iconv library: compile PHP --with-iconv - » Created new directory structure - Moved all demos to demos/ - Moved all getID3() files to getid3/ - Renamed most files to module.something - Changed header in all module.something to explain what they do - Simply remove all modules you don't need - Wrapped all modules into classes - * Bugfix: Implemented misc patches from Eugene Toder - * Bugfix: Implemented misc patches from "six" - ¤ Added root key 'encoding' - ¤ Added prefix GETID3_ to all defined constants. - ¤ Wrapped getid3.php into getid3 class - ¤ Wrapped getid3.functions.php into getid3_lib class - Removed unused functions - Moved several functions away from getid3.functions.php and - into the files where they are actually used. - Renamed getid3.functions.php to getid3.lib.php - Moved getid3.rgad.php functions into getid3_lib - Moved getid3.getimagesize.php funcitons ingo getid3_lib - ¤ Moved getid3.ogginfo.php into ogg module - ¤ Combined GetTagOnly() and GetAllFileInfo() in method analyze - ¤ Removed redundant and unuseful root keys - 'file_modified_time' == filemtime($filename) - 'md5_file' == md5_file($filename) - 'exist' == file_exists($filename) - ¤ Changed root key ['tags'] from array of string to array of array - of comments. - Simplified code for detecting base path. - Removed ob_ from InitializeFilepointerArray(). That was really a - ugly HACK to get output from fopen. If user want the reason, - he should open the file himself! - Checking for APE tags before lyrics3 - makes Lyrics3 not depend - on APE tag. It seems to work on my test file. - Changed ['error'] and ['warning'] in multiple files to append to - array instead of appending to string. That simplified code in - getid3.php too. - Simplified clean-up procedure: simply remove all empty root keys - Setting tags in individual modules instead of main getid3.php - Made Bonk and ASF modules non-dependent on id3 modules - id3 - optional. - Rewrote HandleAllTags() - simplified and convert comments to - desired encoding. - Replaced all calls to RoughTranslateUnicodeToASCII() in ASF module - with a TrimConvert() method. This uses iconv() for conversion. - It also converts from UNICODE instead of UTF-16BE, as the spec - says it should. - Replaced all calls to RoughTranslateUnicodeToASCII() in id3v2 - module with iconv(). id3v2 module also reads - $ThisFileInfo['encoding'] and converts all comments to this - format. All other formats just add their comments in their - native charset, but every comment field in id3v2 can have a - different encoding, so this is needed. - Did same thing as above with ISO module. However - it does not - work. I need to find out how to specify big-endian unicode != - UNICODING encoding name given to iconv(). - Built-in assume mp3 format in getid3.php - Temporarily nuked root key ['comments'] and CopyCommentsToRoot() - Updated demo/audioinfo.class.php - Updated demo/check.php - some thing don't work! - Other demos are out of order! - - -1.6.5: [October-06-2003] James Heinrich - » Added support for LiteWave (thanks supportØclearjump*com) - Ø Split out speedup info from ['OFR']['OFR']['compression'] into - ['OFR']['OFR']['speedup'] - Ø If EXIF functions for JPEG not available, now warning not error - Ø ID3v2 track number now returned as string (with leading zeros, - if present in data) rather than integer (thanks Plamen) - * Bugfix: now correctly parses cbSize element of WAVEFORMATEX - structure (thanks supportØclearjump*com) - * Bugfix: ASF module now reads non-standard field names, - i.e. "date" as well as WM/Year - patch from Eugene Toder. - * Bugfix: ASF module now returns genre as-is if it is not a - standard ID3v1 genre (thanks wonderboy) - * Bugfix: ['audio']['compression_ration'] missing for MPC - (thanks WaldoMonster) - Prevent infinite loop in MP3 histogram if framelength == 0 - Added wFormatTag values 0x00FF and 0x2001 - 0x2005 - (thanks steveØheadbands*com) - Added "twos" and "sowt" FourCCs for Mac AIFC - - -1.6.4: [June-30-2003] James Heinrich - » Added support for free-format MP3s - (thanks Sebastian Mares for the idea) - » Compressed (Flash 6+) SWF files are now handled properly - (thanks alan*cheungØalumni*ust*hk) - » Added DeleteLyrics3() to getid3.lyrics3.php - » Added FixID3v1Padding() to getid3.putid3.php - » Added new simple MP3-splicing sample file - (thanks tommybobØmailandnews*com for the idea) - New file: getid3.demo.joinmp3.php - » Moved all contents of getid3.putid3.php into either - getid3.id3v1.php or getid3.id3v2.php or getid3.functions.php as - appropriate - Removed file: getid3.putid3.php - ¤ ['error'] and ['warning'] keys now return as arrays, not strings - ¤ New root key for all files: ['file_modified_time'] (UNIX time) - ¤ getid3.demo.scandir.php renamed to getid3.demo.mysql.php - ¤ New demo file returns the MIME type only for a single file - (thanks adminØe-tones*co*uk for the idea) - New file: getid3.demo.mimeonly.php - ¤ Added check for valid ID3v1 padding (strings should be padded - with null characters but some taggers incorrectly use spaces). - A warning will be generated if padding is invalid. New boolean - key ['id3v1']['padding_valid'] indicates padding validity. - ¤ CleanUpGetAllMP3info() removes more useless root keys for - unknown-format files - ¤ Extended LAME information in ['mpeg']['audio']['LAME'] is now - only returned for LAME v3.90+ - ¤ LAME-encoded MP3s now return - ['mpeg']['audio']['LAME']['long_version'] as well as - ['mpeg']['audio']['LAME']['short_version'] - these are identical - in LAME v3.90+ but older versions will report longer more - detailed version information if available - ¤ New Lyrics3 values: ['lyrics3']['raw']['offset_start'] and - ['lyrics3']['raw']['offset_end'] - ¤ New optional parameter on getAPEtagFilepointer() to scan from a - defined offset rather than end-of-file to allow scanning of APE - tags before Lyrics3 tags - ¤ ['tag_offset_start'] and ['tag_offset_end'] are now present in - ['ape'], ['lyrics3'], ['id3v1'] and ['id3v2'] - ¤ Numerous changes to the returned structure and content for La - files, including parsing the seektable (if applicable) and - parsing RIFF data occuring after the end of the compressed audio - data (notably RIFF comments) - (thanks mikeØbevin*de) - ¤ getSWFHeaderFilepointer() now has optional 3rd parameter - $ReturnAllTagData (default == false) which if set to true will - return data on all tags in ['swf']['tags'] - ¤ ['swf']['bgcolor'] now returns the 6-character string - representing the background color in HTML hex color format - (thanks ubergeekØubergeek*tv) - ¤ ['swf']['header']['frame_delay'] is no longer returned - ¤ getQuicktimeHeaderFilepointer() now has two additional optional - parameters: $ReturnAtomData (default == true) and - $ParseAllPossibleAtoms (default == false). Setting - $ReturnAtomData to false will reduce the size of the returned - data array by unsetting ['quicktime']['moov'] before returning. - Leaving $ParseAllPossibleAtoms as false now suppresses parsing - of several atom types that contain very large tables of data - that are not typically useful. Atom type suppressed are: - stts, stss, stsc, stsz, and stco - (thanks ubergeekØubergeek*tv) - ¤ ['fileformat'] no longer set to 'id3' if ID3v1 or ID3v2 tag - detected but no other data format recognized - * Bugfix: La files now return the correct values for - ['avdataoffset'] and ['avdataend'] and therefore the correct - values for ['md5_data'] - note that ['md5_data'] values will not - match values from previous versions of getID3() - the previous - versions were incorrect - (thanks mikeØbevin*de) - * Bugfix: A temporary file was being created in the web server's - root directory (not DocumentRoot) each time ['md5_data'] was - calculated, and not removed due to lack of permissions. Temp - file is now created (as it was supposed to be) in the directory - of the file being examined, or the system temp directory, and - properly removed when done. - * Bugfix: Several incorrect values were being returned inside - ['mpeg']['audio']['LAME'] (thanks bouvigneØmp3-tech*org) - * Bugfix: SWF frame rates values were usually incorrect. - (thanks alan.cheungØalumni*ust*hk and ubergeekØubergeek*tv) - * Bugfix: ID3v2.2 files always flagged 4 bytes of invalid padding - (thanks marcaØmac*com) - * Bugfix: Lyrics3 without ID3v1 was not working properly - * Bugfix: Lyrics3, APE & ID3v1 can all now exist in the same file. - A warning is issued if APE comes after Lyrics3 (because Lyrics3- - aware taggers probably are not APE-aware and therefore won't be - able to find the Lyrics3 tag) (thanks mp3gainØhotmail*com) - * Bugfix: WriteAPEtag() now writes the APE tag before any Lyrics3 - tags (if present) and removes any incorrect ones that are after - existing Lyrics3 tags (thanks mp3gainØhotmail*com) - * Bugfix: RIFF-WAVE file with incorrect NumberOfSamples values in - the 'fact' chunk no longer cause incorrect playtime calculation - (thanks stprasadØindusnetworks*com) - * Bugfix: getid3.demo.simple.php had undefined variables if the - file needed to be deep-scanned with assumeFormat - * Bugfix: fixed previously-incorrect ['avdataend'] values for APE - and Lyrics3 tags in some cases, which in some cases means that - ['md5_data'] is different than previously (now correct) - Much-improved detection of AAC-ADTS, which also means MP3 - format detection should now be nearly twice as fast - Truncated AVIs and WAVs are now reported - Number of new features and bugfixes in getid3.demo.mysql.php - Quicktime 'meta' atoms now parsed, so Quicktime MP4 files can now - return artist, title, album, etc (thanks spunkØdasspunk*com) - Consolidated all comments processing functions (processing the - ['comments'] and ['tags'] keys) into HandleAllTags() which now - also checks to ensure that APE tags are really better than ID3v2 - before using them in ['comments'] - Known issue with Meracl ID3 Tag Writer v1.3.4 truncating last byte - of MP3 file when appending new ID3v1 tag now specifically noted - (rather than generic Probably Truncated File message) - getid3.demo.mysql.php now stores last-modified time for each file - getid3.demo.mysql.php is now case-sensitive for filenames - getid3.demo.mysql.php can generate M3U playlists of any of the - groups of files it can select (duplicate filenames, tag types, - etc.) - getid3.demo.mysql.php can now find mismatched tag contents and - filenames - getid3.demo.check.php now shows total number of errors & warnings - GetFileFormatArray() now matches actual patterns for MP3 files - based on the first two bytes of the file, rather than just the - first one - Simplified DeleteAPEtag() and made it work properly with Lyrics3 - - -1.6.3: [May-17-2003] James Heinrich - » Added support for Bonk (thanks ahØartemis*dk) - New file: getid3.bonk.php - » Added support for AVR (thanks ahØartemis*dk) - New file: getid3.avr.php - ¤ Contents of getid3.id3.php moved to getid3.id3v1.php - Removed file: getid3.id3.php - ¤ Contents of getid3.frames.php moved to getid3.id3v2.php - Removed file: getid3.frames.php - ¤ Returned data structure documentation improved and updated and - now stored in getid3.structure.txt rather than getid3.readme.txt - New file: getid3.structure.txt - ¤ Now including the GNU General Public License in the distribution - as getid3.license.txt - New file: getid3.license.txt - ¤ Added new, optional, parameter to WriteAPEtag() (and also - GenerateAPEtag()) which must be set to TRUE if the values you - are passing are already UTF8-encoded, otherwise all data is - encoded to UTF8 by default. For all ASCII/ANSI data this value - should be left at the defaul value of FALSE. - ¤ Added third, optional, parameter to getID3v2Filepointer() - - $StartingOffset (default == 0) which can parse an ID3v2 tag - in a file at a position other than the start-of-file. - ¤ ['video']['pixel_aspect_ratio'] now returned when known - ¤ AVI files with WMA audio now return ['audio']['dataformat'] - of 'wma' rather than 'wav' - ¤ ASF-WMA files now return the artist value from WM/AlbumArtist - in ['comments']['artist'] (thanks msibbaldØsaebauld*com) - ¤ ASF-WMA files now return the 'author' value from - ['asf']['content_description'] in ['comments']['artist'] - instead of ['comments']['author'] - ¤ ASF-WMA files now return the 'description' value from - ['asf']['content_description'] in ['comments']['comment'] - instead of ['comments']['description'] - * Bugfix: APE tag writing with multiple values for a tag (more - than one ARTIST for example) was not being correctly written - (thanks ahØartemis*dk) - * Bugfix: CreateDeepArray() was returning an empty-string key as - the top-level returned value - ['iso']['files'] now directly - contains the file listing without an empty array in between. - * Bugfix: ID3v2 genreid was not being returned in some cases. - * Bugfix: APEv1 tags would generate error messages - * Bugfix: APE tags would sometimes show phantom second entry for - each item (title, artist, etc) with no data - * Bugfix: APE tag writing was not UTF8-encoding the data - - non-ASCII characters (above chr(127)) were being incorrectly - stored (thanks ahØartemis*dk) - * Bugfix: getid3.demo.scandir.php had undefined function error - * Bugfix: getid3.demo.scandir.php would not display list of files - with no tags - Added link to getid3.demo.check.php from list of specific-tags - files in getid3.demo.scandir.php - - -1.6.2: [May-04-2003] James Heinrich - » New official mirror site for getID3() - http://www.getid3.org - » Added basic support for SWF (Flash) (thanks n8n8Øyahoo*com) - New file: getid3.swf.php - » Added experimental support for parsing the audio portion of - MPEG-video files. I don't have any actual documentation for - this, so this part is experimental and not guaranteed accurate, - but it seems to be working OK as far as I have been able to test - it. Bug reports (or even better - documentation!) are welcome at - info@getid3.org - » Added new simple directory-scanning sample file - New file: getid3.demo.simple.php - » getid3.demo.write.php now writes APE tags as well. - ¤ Renamed getid3.write.php to getid3.demo.write.php - ¤ Renamed audioinfo.class.php to getid3.demo.audioinfo.class.php - ¤ getid3.php now automatically includes the getid3.functions.php - function library file, no need to include it seperately. - ¤ getLyrics3Filepointer() has been changed to be consistant with - all the other similar function structures - the parameters have - changed. The old function has been renamed to getLyrics3Data() - ¤ Added DeleteAPEtag() function to getid3.ape.php - ¤ HandleID3v1Tag() now only handles ID3v1. Lyrics3 processing is - now done by HandleLyrics3Tag() - ¤ If BitrateHistogram is enabled in getOnlyMPEGaudioInfo() it now - also returns ['mpeg']['audio']['version_distribution'] showing - the number of frames of each MPEG version (1, 2 or 2.5) - all - frames *should* be of the same MPEG version - ¤ getID3v1Filepointer() always returns TRUE now, even if it didn't - find a valid ID3v1 tag - ¤ getOnlyMPEGaudioInfo() now looks for MPEG sync in the first 128k - bytes rather than the first 64k bytes - ¤ Added dummy function GetAllMP3info() to generate warning not to - use that deprecated function. - ¤ ['video']['codec'] is now 'MPEG' for all MPEG video files (this - will change to 'MPEG-1' or 'MPEG-2' as soon as I figure out how - to determine that) (thanks jigalØspill*nl) - ¤ ['mpeg']['audio']['LAME']['mp3_gain'] renamed to - ['mpeg']['audio']['LAME']['mp3_gain_db'] (gain in dB) - ¤ Added ['mpeg']['audio']['LAME']['mp3_gain_factor'] (gain as a - multiplication factor) - ¤ Added support for Preset and Surround Info bytes from LAME VBR - tag (http://gabriel.mp3-tech.org/mp3infotag.html) - * Bugfix: APE tag writing would put the string 'Array' for all - values rather than the actual data (thanks ahØartemis*dk) - * Bugfix: Warning now generated for VBR MPEG-video files because - getID3() cannot determine average bitrate. If you know of - documentation that would tell me how to do this, please email - info@getid3.org - * Bugfix: Replay Gain values from Vorbis comments are now - returned in ['replay_gain'] (and not in ['comments']) - (thanks ahØartemis*dk) - * Bugfix: Replay Gain values from APE comments are now correctly - returned in ['replay_gain'] (thanks ahØartemis*dk) - * Bugfix: getid3.demo.check.php is now case-insensitive when - assuming a format for a corrupted file if standard detection - does not identify the file type. - * Bugfix: RIFF comments were overwriting/suppressing ID3 comments - for RIFF-MP3 files (thanks wmØwofuer*com) - * Bugfix: RIFF-MP3 files with 'RMP3' chunks instead of 'WAVE' were - not being correctly identified. - * Bugfix: ID3v2 padding shorter than the length of an ID3v2 frame - header was not correctly detected - * Bugfix: getid3.demo.check.php now does in-depth scanning for MP2 - and MP1 files the same as for MP3 files based on file extension - if a MPEG-audio structure isn't found immediately at the start - of the file - * Bugfix: removed condition where RIFF-WAV was being scanned for - MPEG-audio signature when it shouldn't be present (non-MP3 WAV) - * Bugfix: ASF files were not always showing correct audio datatype - * Bugfix: array_merge_clobber() and array_merge_noclobber() were - not being conditionally defined in getid3.functions.php - (thanks rich.martinØreden-anders*com) - * Bugfix: stream_numbers was not being correctly returned in - bitrate_mutual_exclusion_object chunks of ASF files - * Bugfix: Added support for 24kHz and 12kHz audio in ASF files - * Bugfix: Removed possible undefined offset error in MP3s where - cannot find synch before end of file - * Bugfix: Removed potential out-of-memory crash situation when - parsing Real files with chunks larger than the available memory - (thanks jigalØspill*nl) - * Bugfix: ID3v1 was incorrectly taking precedence over ID3v2 in - the ['comments'] array (thanks lionelflØwanadoo*fr) - * Bugfix: No longer calculates overall bitrate and playtime for - VBR MPEG video files based on the audio bitrate. - * Bugfix: AssumeFormat was not working properly - Added summary footer line to getid3.demo.check.php - Added '.mpeg' to the list of assume-format-from-filenames list in - getid3.demo.check.php - MPEG-video files now more reliably detected - A number of additional features have been added to - getid3.demo.scandir.php - Added many RIFF-AVI audio types and fourcc video types to the - lookup functions in getid3.riff.php - Now identifes files with Lyrics3 v1 tags that are of incorrect - length (v1 Lyrics3 is supposed to be 5100 bytes long, but - [unknown program] writes variable-length tags (which is illegal - for Lyrics3 v1)). getID3() now correctly parses these tags and - issues a warning. - Split GetFileFormat() to GetFileFormat() and GetFileFormatArray() - HTML colors in getid3.demo.check.php are now defined as constant - variables at the top of the file (if you want to change them) - Added support for OptimFROG v4.50x (non-alpha) (new header fields) - (thanks floringhidoØyahoo*com) - Added support for Lossless Audio v0.4 (thanks mikeØbevin*de) - - -1.6.1: [March-03-2003] James Heinrich - » Added support for writing APE v2. - WriteAPEtag() in getid3.ape.php - NOTE: APE v1 writing support will *not* be added to future - versions of getID3() - (thanks ahØartemis*dk and adamØphysco*com for the idea) - » Added support for AIFF (Audio Interchange File Format) including - AIFF, AIFC and 8SVX (thanks ahØartemis*dk for the idea) - Removed file: getid3.aiff.php - » Added support for OptimFROG (v4.50a and v4.2x) - (thanks ahØartemis*dk for the idea) - New file: getid3.optimfrog.php - » Added support for WavPack (thanks ahØartemis*dk for the idea) - » Added support for LPAC (thanks ahØartemis*dk for the idea) - » Added support for NeXT/Sun .au format - New file: getid3.au.php - » Added support for Creative SoundBlaster VOC format - New file: getid3.voc.php - » Added support for the BWF (Broadcast Wave File) RIFF chunks - "bext" and "MEXT" (thanks Ryan and njhØsurgeradio*co*uk) - » Added support for the CART (Broadcast Wave File) RIFF chunks - (thanks Ryan) - » Added getid3.demo.scandir.php - a sample recursive scanning demo - that scans every file in a given directory, and all sub- - directories, and stores the resulting data in MySQL database, - and then displays a list of duplicate files based on md5_data - ¤ ['md5_data_source'] now contains the MD5 value for the original - uncompressed data for formats that store that information - (currently only FLAC v0.5+). ['md5_data'] (if chosen to be - calculated) will contain the calculated MD5 value for the - compressed file. To check if 2 files are identical in every way, - including all comments: compare ['md5_file']. To check if two - files were compressed from the same source file: compare - ['md5_data_source']. To check if the compressed audio/video data - of two files is identical, even if comments or even the - container file format is different (MP3 in RIFF container, - FLAC in Ogg container, etc): compare ['md5_data']. - ¤ ['md5_data'] for 8-bit WAV files is now calculated based on a - converted version of the data from unsigned to signed (MSB - inverted) to match the MD5 value calculated by FLAC - ¤ New optional parameter added to GetAllFileInfo() - - $MD5dataIfMD5SourceKnown (default: false). If false the md5_data - value will NOT be calculated for files (such as FLAC) that have - ['md5_data_source'] set, even if $MD5data == true. - (thanks ahØartemis*dk) - ¤ getid3.check.php renamed to getid3.demo.check.php - ¤ Added GetTagOnly() function to getid3.php - similar to - GetAllFileInfo() except only takes a filename as a parameter and - only returns ID3v2, APE, Lyrics3 and ID3v1 tag information - no - attempt is made to parse the data contents of the file at all. - (thanks Phil for the idea) - ¤ Added ['audio']['lossless'] and ['video']['lossless'] for all - formats (when known). Both are boolean values - true means the - data is lossless-compressed, false means the data is lossy- - compressed. - ¤ Added ['audio']['compression_ratio'] and/or - ['video']['compression_ratio'] for all formats. Returns a number - (usually) less than 1, where 1 represents no compression and 0.5 - represents a compressed file half the size of the original file - ¤ Added ['video']['bits_per_sample'] to all video formats (when - known) - ¤ Added ['video']['frame_rate'] to all video formats (when known) - ¤ ['fileformat'] set to 'mp1' or 'mp2' instead of 'mp3' when - ['audio']['dataformat'] is one of those (thanks ahØartemis*dk) - ¤ Added 4th parameter to md5_data(), $invertsign, which will invert - the MSB of each byte before MD5'ing. This is needed for 8-bit - WAV files because FLAC calculates the stored MD5 value on - signed data rather than the original byte values. ['md5_data'] - of an 8-bit WAV will now match the ['md5_data_source'] value - (thanks lichvarmØphoenix*inf*upol*cz) - ¤ ['ape']['items']['data'] and ['ape']['items']['data_ascii'] now - contains an array of values, if the tag contains UTF-8 text (as - opposed to binary data) - ¤ ['mpeg']['audio']['bitratemode'] renamed to - ['mpeg']['audio']['bitrate_mode'] - * Bugfix: Removed potential bug that could replace all MP3 file - contents with only the new ID3v2 tag in getid3.putid3.php - * Bugfix: md5_data values calculated for RIFF (WAV, AVI) files - were incorrect (thanks ahØartemis*dk) - * Bugfix: MP3 data in an MP4 wrapper fileformat could not identify - bitrate (thanks ahØartemis*dk) - * Bugfix: ['audio'] and/or ['video'] keys would sometimes get - removed even if not empty - * Bugfix: Prevented creation of null entries in - ['RIFF']['WAVE']['INFO'] if a comment entry was not present - * Bugfix: Potential infinite-loop condition in getid3.ogg.php - (thanks afshin.behniaØsbcglobal*net) - * Bugfix: Ogg files with illegal ID3v1 (and/or APE or Lyrics3) - tags were not finding the last Ogg page - (thanks afshin.behniaØsbcglobal*net) - * Bugfix: replay-gain values not properly set from LAME tag - * Bugfix: RIFF-MP3 had incorrect md5_data - * Bugfix: the LAME DLL CBR problem of not re-writing the LAME - frame at the beginning of the data is now detected for MP3s - with ID3v2 tags as well - * Bugfix: APE tags with multiple values (ie multiple entries in - the "artist" tag) are now shown properly in ['ape']['items'] - * Bugfix: fixed condition where APE tag with no ID3v1 tag could be - mistaken for APE tag with ID3v1 (and incorrectly parsed) - * Bugfix: added warning if ID3v2 frame has zero-length data - (thanks cmassetØclubinternet*fr) - * Bugfix: getid3.frames.php looking for non-existant key in USER - frames - Improved detection of RIFF-MP3 data. [unknown program] encodes - RIFF-WAV data with a chunk name of 'RMP3' instead of the - standard 'RIFF' - Encoder now returned in both ['comments'] and ['audio']['encoder'] - for RIFF-WAV files with an INFO.ISFT chunk - Generate a warning for FLAC files encoded with v0.3 or v0.4 - because audio_signature is not calculated during encoding - (thanks ahØartemis*dk) - Modified getid3.check.php to display md5_data_source as well as - md5_file and md5_data if display-MD5 mode is selected - Modified getid3.check.php to assume-format based on file extension - in browse mode if fileformat is found to be 'id3' (formerly only - if the fileformat was null) - Changed scaling of BitrateColor() from representing 1-256kbps to - representing 1-768kbps for better display of high-bitrate files, - specifically lossless-compressed CD-audio (FLAC, LA, etc) - - -1.6.0: [January-30-2003] James Heinrich - » Added support for OggFLAC (FLAC data stored in an Ogg container) - (thanks ahØartemis*dk for the idea) - » Added support for Speex (the data stored in an Ogg container) - » Comments are now available in the root 2-dimensional array - ['comments'] - each entry in this array will contain one or more - strings. For example, if there are two artists then - ['comments']['artist'][0] will contain the first one and - ['comments']['artist'][1] the other. All keys are forced - lowercase. Comments will be stored in the ['comments'] array in - this order of precedence: - 1) Native format tags (ASF, VQF, NSV, RIFF, Quicktime, Vorbis) - 2) APE tags - 3) ID3v2 - 4) Lyrics3 - 5) ID3v1 - Lower-priority tags will not overwrite or append existing values - of higher-priority tags (for example, 'artist' in ID3v1 will be - ignored if already specified in APE), but missing values will be - filled in (for example, if 'album' is specified in ID3v2 but not - in APE, it will be included in the ['comments'] array). - Note: Root keys (['title'], ['artist'], etc) are NOT available - in this or future versions of getID3(). - (thanks ahØartemis*dk) - » MD5 hashes are now available for all formats for both the entire - file (['md5_file']) and the portion of the file containing only - the audio/video data, stripped of all prepended/appended tags - like ID3v2, ID3v1, APE, etc - ['md5_data'] - (thanks ahØartemis*dk for alternate md5_file() function that - runs on UNIX system running PHP < 4.2.0) - NOTE: Ogg files require the use of vorbiscomment to obtain the - md5_data value. vorbiscomment must be downloaded from - http://www.vorbis.com/download.psp and placed in the getID3() - directory. All Ogg formats (Vorbis, OggFLAC, Speex) are affected - by this problem, but only OggVorbis files can be processed with - vorbiscomment. OggFLAC and Speex files will be processed by - getID3(), but this may result in an incorrect value for md5_data - in the event that VorbisComments are larger than 1 page (4-8kB). - NOTE: md5_data for Ogg will not work if PHP is running in Safe - Mode - » There is now a wrapper class available, written by Allan Hansen, - which should simplify extracting most common basic information - (such as format, bitrate, comments). - New file: audioinfo.class.php - » OggWrite() in getid3.ogginfo.php has been replaced with a new - version that uses vorbiscomment to write the comments, because - of a reported bug that can corrupt OggVorbis files such they - cannot be played. - NOTE: Ogg comment writing now requires the use of vorbiscomment - which must be downloaded from http://www.vorbis.com/download.psp - and placed in the getID3() directory. - NOTE: Ogg comment writing will not work if PHP is running in - Safe Mode - ¤ New root key ['tags'] is now always returned for all formats. - It is an array that may contain any of: - * Native format tags: 'vqf', 'riff', 'vorbiscomment', 'asf', - 'nsv', 'real', 'midi', 'zip', 'quicktime' - * Appended data tags: 'ape', 'lyrics3', 'id3v2', 'id3v1' - ¤ New root key ['audio'] is an array containing any or all of: - codec, channels, channelmode, bitrate, bits_per_sample, - dataformat, bitrate_mode, sample_rate, encoder - Note: This replaces several root keys, including: - bitrate_audio, bits_per_sample, frequency, channels - ¤ New root key ['video'] is an array containing any or all of: - bitrate_mode, bitrate, codec, resolution_x, resolution_y, - resolution_y, frame_rate, encoder - Note: This replaces several root keys, including: - bitrate_video, resolution_x, resolution_y, frame_rate - ¤ ['id3']['id3v1'] has moved to ['id3v1'] - ¤ ['id3']['id3v2'] has moved to ['id3v2'] - ¤ ['audiodataoffset'] and ['audiodataend'] have been renamed to - ['avdataoffset'] and ['avdataend'] respectively - ¤ GetAllMP3info() has been changed to GetAllFileInfo() with a - different parameter list ($allowedFormats is no longer a - parameter). Check your code where you're calling - GetAllMP3Info() - you will need to change both the function - name and the parameter list if you pass more than 2 parameters - ¤ All formats now return ['audio']['dataformat'] and/or - ['video']['dataformat'] where appropriate - this goes along with - ['fileformat'] - ['fileformat'] will return the actual structure - of the file, whereas ['dataformat'] will return the format of - the data inside that structure. For example, an Ogg file can - contain Vobis data (normal), or it can contain FLAC data in the - Ogg container format. In that case, ['fileformat'] would be - 'ogg', but ['dataformat'] would be 'flac'. - Note: this means that WAV and AVI files now return a - ['fileformat'] of 'riff' rather than 'wav' or 'avi'. - ¤ ['filesize'] is no longer returned for files larger than 2GB - because PHP does not support large file access. Attempting to - parse a file larger than 2GB will result in a message stored in - ['error'] and ['filesize'] not set. - ¤ APEtag, ID3v1, and ID3v2 are now supported on ALL multimedia - files - even if illegal by format. Ogg will return warning if - ID3/APE tags are present. (thanks ahØartemis*dk) - ¤ All files: non-critical errors are now returned in the root key - ['warning'] rather than ['error'] (only critical errors that - prevent getID3() from correctly parsing the file are returned in - ['error'] (thanks ahØartemis*dk) - ¤ Renamed all references to $MP3fileInfo to $ThisFileInfo - ¤ Joliet now supported for ISO-9660. - ['iso']['supplementary_volume_descriptor'] is now returned, if - available, and ['iso']['files'] will contain ASCII equivalents - of the Unicode directory structure & filenames stored. - ¤ Moved Monkey's Audio code from getid3.ape.php to seperate file. - New file: getid3.monkey.php - ¤ Added new keys for ISO-9660: ['name_ascii'] for directories, - ['file_identifier_ascii'] for files - ¤ Added root key ['track'] for CD-audio files - ¤ Ogg/Vorbis-comment files now have comments returned inside - ['ogg']['comments_common'] as an array of strings, rather than - simple strings in ['ogg'] - ¤ Quicktime files now have comments returned inside - ['quicktime']['comments'] as an array of strings, rather than - simple strings in ['quicktime'] - ¤ ['mime_type'] is a new root key returned for all supported - formats (thanks ahØartemis*dk) - ¤ ['fileformat'] now returns 'mp1' instead of 'mp3' for MPEG-1 - layer-I audio files (thanks ahØartemis*dk) - ¤ ['mpeg']['audio']['bitratemode'] now returns lowercase - ¤ MPEG-4 audio files which consist of MP3 data wrapped in a - Quicktime fileformat will now return the usual data in - ['mpeg']['audio'] - ¤ Type-1 DV AVIs are now supported - ¤ DV AVIs will return 1 or 2 in ['RIFF']['video'][x]['dv_type'] - ¤ Changed ['fileformat'] from 'mpg' to 'mpeg' for MPEG video files - ¤ ASF comments are now stored in ['asf']['comments'] instead of - ['asf'] - ¤ RealMedia chunk data is now returned inside ['real']['chunks'] - instead of ['real'] - ¤ ['replay_gain'] now properly populated from APE tags - ¤ Added support for ASF_Old_ASF_Index_Object in ASF files - (thanks ahØartemis*dk) - ¤ AAC-ADTS files now return ['aac']['bitrate_distribution'] - ¤ ParseVorbisComments() has been replaced with - ParseVorbisCommentsFilepointer() (with different parameters) - ¤ All references to any key ['frequency'] are now ['sample_rate'] - ¤ Moved ID3v2 comments from ['id3v2'] into common root - ['comments'] structure, and now returns more values than before - * Bugfix: ['iso']['files'] and ['zip']['files'] could potentially - contain duplicate entries (in a numeric-indexed array) for files - if the directory structure specifies files multiple times. - Entries are now guaranteed unique, with the last entry for the - file overwriting any former ones. - * Bugfix: RIFF parsing had numerous issues, including: - - large AVIs would take a very very long time to parse - - chunks with odd (not even) sizes would cause the parser fail - - video and/or audio codecs not always identified - The ParseRIFF() function has been completely rewritten and fixes - all known issues with RIFF parsing. Users are, however, - encouraged to double-check output of any parsed (AVI/WAV/CDDA) - files. - * Bugfix: Modified getid3.riff.php to return correct total - bitrates for AVIs with multiple audio streams - * Bugfix: GetFileFormat() was not creating array structure - correctly (thanks ahØartemis*dk) - * Bugfix: LAME tag for MP3s can only specify up to 255kbps, so any - files with actual CBR bitrate of >=256 were reported incorrectly - * Bugfix: Lyrics3 synched lyrics were not being correctly returned - * Bugfix: CreateDeepArray() was broken for non-nested cases, which - meant ZIP and ISO ['files'] structures were broken - * Bugfix: Incorrect pattern matching for ZIP files meant no zip - files were being detected as such - * Bugfix: AAC-ADIF was returning an incorrect number of channels - (too few) in some cases (thanks ahØartemis*dk) - * Bugfix: Vorbis comments were returning an incorrect value for - ['dataoffset'] in some cases - * Bugfix: MPEG video ['marker_bit'] and ['vbv_buffer_size'] were - incorrect - * Bugfix: ['playtime_string'] could potentially have a value of - x minutes and 60 seconds (ie 3:60 instead of 4:00) - Added support for FLAC cuesheets (FLAC 1.1.0+) - (thanks ahØartemis*dk) - Improved parsing speed in MP3, MP2 and AAC (thanks ahØartemis*dk) - Extra error-checking added to try and identify corrupt files for - most audio formats (thanks ahØartemis*dk) - More accurate playtime calculation for RealMedia - (thanks ahØartemis*dk) - Changed all relevant files to use ['audiodataoffset'] and - ['audiodataend'] rather than ['filesize'] where appropriate - (thanks ahØartemis*dk) - Added text encoding type 255 as a duplicate of UTF-16BE but with - Big-Endian rather than Little-Endian byte order - Added many RIFF-AVI audio types and fourcc video types to the - lookup functions in getid3.riff.php - Added numerous new known GUIDs to getid3.asf.php - Added PoweredBygetID3() function to easily get a "powered by" - string with the current getID3() version. - Added "Morgan Multimedia Motion JPEG2000" (MJ2C), "DivX v5" (DX50) - and "XviD" (XVID) codecs to list of known codecs in - getid3.riff.php - Changed GETID3_INCLUDEPATH path seperators to forced / - (from \ for Windows) - Modified getid3.check.php to only change \ directory seperators to - / on Windows operating systems - Modified getid3.check.php to handle larger-than-2GB files (which - now do not return a filesize) - Modified getid3.check.php to handle ['dataformat_audio'] and - ['dataformat_video'] - Modified getid3.check.php to show a list of present tags in one - column rather than one column for each of ID3v1, ID3v2, etc - Modified getid3.check.php to show MD5 values. Initially disabled - but can be enabled for a directory with a click. md5_file is - always calculated when displaying detailed info about a single - file; md5_data is calculated if the file is < 50MB - Modified getid3.check.php to show errors and warnings. Details are - visible with a mouseover or a click. - Changed getid3.check.php to use SafeStripSlashes instead of a - manual conditional directory name replacement for special - characters - Added sample recursive scanning sample code to getid3.readme.txt - (thanks lipisinØmail*ru for the idea) - - -1.5.7: [January-10-2003] James Heinrich - » Added support for ISO 9660 (CD-ROM image) format. Most-useful - data is directory structure returned under ['iso']['files'] - Note: Only ISO-9660 supported, not (yet) Joliet extension - (thanks nebula_djØsofthome*net for the idea) - New file: getid3.iso.php - ¤ ZIP files are now parsed by getID3() itself without relying on - built-in PHP functions and/or ZZipLib support. - (thanks Vince for the idea) - ¤ ZIP files now return a simple directory listing with filename - and filesize info only under ['zip']['files']. - Note: empty subdirectories will note appear in here, only files - and non-empty subdirectories. Information for all entries, - including empty subdirectories, is available under - ['zip']['central_directory'] (or under ['zip']['entries'] if the - Central Directory cannot be located (usually due to a trucated - file). - ¤ RIFF-WAV files with MP3 data (or MP3s with RIFF headers, if you - want to think of it that way) now have the MPEG audio portion - scanned and the usual data returned in ['mpeg']['audio'] if the - RIFF audio codec has wFormatTag of "85" (identified by getID3() - as "MPEG Layer 3") - (thanks ahØartemis*dk for the idea) - ¤ EXIF data (if present) is returned for JPEG files under - ['jpg']['exif'] (thanks nebula_djØsofthome*net) - ¤ ['filepath'] now returned for all files with the directory part - of the full filename. - ¤ ['filenamepath'] is now returned for all files (equivalent to - ['filepath'].'/'.['filename']) - * Bugfix: ['id3']['id3v2'][]['dataoffset'] was wrong - * Bugfix: MP3s tagged with iTunes have an invalid comment field - frame name ('COM ' - should be 'COMM') but the data is valid - otherwise; the frame is now renamed to 'COMM' and parsed - normally (with the error noted in ['error']) - (thanks kheller2Ømac*com for the sample file) - * Bugfix: Some ASF/WMA audio files were not being identified as - any format (thanks ahØartemis*dk) - * Bugfix: Warning now generated and ASCII format assumed for - invalid text encoding values in ID3v2 - * Bugfix: Changed ZIP detection pattern from 'PK' to 'PK\x04\x03' - * Bugfix: Ogg/FLAC files with large Vorbis comments were dying in - an infinite loop with lots of error messages due to missing $fd - parameter on ParseVorbisComments() (thanks ahØartemis*dk) - * Bugfix: ['data'] and ['image_mime'] were being returned for all - Ogg comments even if they were not images for versions of PHP - that have image_type_to_mime_type() built in (ie PHP 4.3.0+) - - -1.5.6: [December-31-2002] James Heinrich - » Added support for NSV (Nullsoft Streaming Video) - (www.nullsoft.com/nsv/) - (thanks demonØsoundplanet*com for the idea) - New file: getid3.nsv.php - » Added support for CD-audio track files (track01.cda etc) - ¤ Added standard ['frame_rate'] root value when known (AVI, NSV, - MPEG-video) - ¤ ASF files now report ['fileformat'] of: - 'wmv' when Windows Media Video codec v7/v8/v9 is used; - 'wma' when any 'Windows Media Audio' named audio codec is used - and no video stream is present; - 'asf' in all other cases (audio-only, video-only, or both) - ¤ Removed support for ZIP functions (will be rewritten to not - require ZZIPlib support in future versions) - ¤ Added function SafeStripSlashes() as a drop-in replacement for - stripslashes(), but that only strips slashes if magic_quotes_gpc - is set - ¤ Removed support for remote file scanning (HTTP / FTP) - ¤ Added ['aac']['frames'] (number of AAC frames in file) - ¤ Added ['mpeg']['audio']['frame_count'] when a bitrate histogram - is created - ¤ Average bitrate for VBR MP3/MP2 is calculated from actual counts - of frames of various bitrates (rather than relying on the header - values or filesize) when a bitrate histogram is created - ¤ RecursiveFrameScanning() split out into seperate function - (getid3.mp3.php) - ¤ Removed old function getMP3header() from getid3.mp3.php - ¤ Changed default MPEG_VALID_CHECK_FRAMES (number of mp3 frames - scanned to ensure a valid audio sequence has been located) from - 10 to 25. This means scanning will be slightly slower, but more - reliable/accurate - * Bugfix: ID3v2.2 - valid frame names not correctly detected - (thanks maeckerØweb*de for the sample file) - * Bugfix: ID3v2.2 - valid padding not correctly detected - (thanks maeckerØweb*de for the sample file) - * Bugfix: MIDI files with flat key signatures were not being - correctly reported (thanks alexleeisØshaw*ca for sample file) - * Bugfix: now returns message in ['error'] if file does not exist - * Bugfix: ['RIFF']['video'][x]['codec'] wasn't always being - correctly populated - * Bugfix: ['bitrate'] was incorrect for multi-stream RealMedia - * Bugfix: ['playtime_seconds'] was sometimes null or incorrect - for multi-stream RealMedia - * Bugfix: ChannelTypeID was incorrect in RVA2 ID3v2.4 frames - * Bugfix: Fixed potential divide-by-zero error for corrupt FLAC - files (thanks ahØartemis*dk) - * Bugfix: AAC-ADTS was not returning ['bitrate_mode'] unless - $ReturnExtendedInfo was TRUE (thanks ahØartemis*dk) - * Bugfix: LAME-encoded CBR MP3s now properly identified as CBR - with correct bitrate (thanks ahØartemis*dk) - * Bugfix: VBR MP2 (or headerless MP3) is now identified as VBR - rather than CBR. Note: to obtain VBR bitrate for headerless - files, the entire file is scanned and a histogram distribution - of bitrates is created, and the average bitrate calculated from - that. (thanks ahØartemis*dk for sample file) - Added support for DSIZ chunks in VQF, and checks to make sure size - of audio data matches DSIZ value, if present - (thanks ahØartemis*dk for sample file) - Rewrote GetAllMP3info() - removed some unneccesary code, changed - format-detection routine from ParseAsThisFormat() to - GetFileFormat() to allow for more flexible format parsing - (needed for ISO CD-ROM images, helpful for Quicktime and others) - Changed references in all files from string-cast indexes: ["$i"] - to non-cast indexes: [$i] where appropriate - Put a sans-serif 9pt style on all text in getid3.check.php - getAACADTSheaderFilepointer() now return TRUE if synch is lost - after the first frame has been successfully parsed (previously - it would return FALSE if synch was lost at any time, meaning the - file is most likely MP3, which was incorrect) - (thanks ahØartemis*dk for sample file) - Speed improvement code changes to getid3.mp3.php (up to 24% faster - in some cases) (thanks ahØartemis*dk for the code) - Changed all include_once() to require_once() - - -1.5.5: [November-25-2002] James Heinrich - » Added support for La (Lossless Audio - www.lossless-audio.com) - (thanks ahØartemis*dk for the idea) - New file: getid3.la.php - ¤ Moved lookup functions from getid3.lookup.php to the files where - they are used. - New file: getid3.id3.php - New file: getid3.rgad.php - Removed file: getid3.lookup.php - ¤ getID3v1Filepointer() returns FALSE if ID3v1 tag not found - ¤ Added new paramter "ReturnExtendedInfo" to the function - getAACADTSheaderFilepointer() in getid3.aac.php which now - defaults to FALSE - if TRUE then the data for every frame is - returned (containing aac_frame_length, adts_buffer_fullness and - num_raw_data_blocks, which aren't usually very useful). Speed - improvement with FALSE is about 35%. - ¤ Now returns fopen() errors in ['error'], for example if a remote - file is not accessible. - ¤ Changed default number of MP3 audio frames to scan to determine - if a valid stream has been found from 5 to 10, now also defined - as a constant at the top of getid3.mp3.php This will result in - slightly slower MP3 parsing, but greater reliability in - detecting false/invalid/corrupted VBR headers. - ¤ fopen() errors now displayed in getid3.putid3.php - (thanks miguel.dieckmannØhamburg*de) - ¤ Added 4th parameter to decodeMPEGaudioHeader() $ScanAsCBR which - will force an MP3 audio frame sequence to be force-scanned in - CBR mode. You should never need to call this directly, it's only - used internally to scan for MP3 files that have an illegal VBR - header with CBR data. (thanks fletchØpobox*com) - * Bugfix: ASF_Marker_Object in getid3.asf.php was always returning - an error in non-existant "reserved_1" and failing - * Bugfix: VBR bitrate calculations in getid3.mp3.php only occur if - ['mpeg']['audio']['VBR_frames'] is defined. - (thanks fletchØpobox*com) - * Bugfix: getid3.putid3.php no longer deletes original MP3 if - ID3v2 tag writing fails (thanks miguel*dieckmannØhamburg*de) - * Bugfix: incorrect order of if-statement error messages in - getid3.putid3.php (thanks miguel*dieckmannØhamburg*de) - getid3.asf.php now notes the error and continues parsing rather - than failing when it encounters an error parsing a chunk - Now actually scan 1000 frames for AAC ADTS as reported in the - v1.5.4 changelog, rather than 100. (thanks ahØartemis*dk) - Improved scanning speed in getAACADTSheaderFilepointer() by ~30% - (thanks ahØartemis*dk for the fix) - Added FileSizeNiceDisplay() function to getid3.functions.php for - formatting filesize output in kB, MB, GB, etc. - - -1.5.4: [October-07-2002] James Heinrich - » Added support for Quicktime. - New file: getid3.quicktime.php - » Added support for AAC files, both ADTS and ADIF header formats. - ADIF format is a pain because it's very similar to standard MP3 - header format, and it's hard to distinguish between the two. I - have tried to make the detection accurate, but I have a limited - number of AAC test files to play with so if you have an AAC file - that gets detected as MP3/MP2 (or vice-versa), please send me - the details via email at getid3Øsilisoftware*com - ADTS format is very slow to parse because to get the bitrate of - VBR files the whole file has to be stepped through frame by - frame (getID3() scans up to the first 1000 frames and assumes - that to be close enough). - Note: I would suggest commenting out support for AAC (see top of - GetAllMP3info() function in getid3.php) unless you need it. - (thanks jfaulØgmx*de for the idea and sample Delphi source code) - New file: getid3.aac.php - » Added bitrate distribution analysis option for MP3 VBR files. A - new boolean parameter for getOnlyMPEGaudioInfo() enabled this - feature which steps through the MP3 file frame by frame and - counts how many frames of each bitrate exist. This information - is returned in ['mpeg']['audio']['bitrate_distribution'] - Caution: this feature is very inefficient for large files and - takes a very long time and does lots of disk I/O. Use with care. - ¤ Changed layout of allowedFormats in GetAllMP3info() function in - getid3.php to allow easy removal of support for any of the - supported format. As stated above, I recommend commenting out - AAC unless needed. - ¤ Added ['flac']['compressed_audio_bytes'], - ['flac']['uncompressed_audio_bytes'], and - ['flac']['compression_ratio'] - ¤ Replaced FXPT2DOT30toFloat() function with FixedPoint2_30() - * Bugfix: getid3.mpc.php was slightly miscalculating the number of - samples, therefore also bitrate and playtime - (thanks ahØartemis*dk for the fix) - * Bugfix: MonkeyCompressionLevelNameLookup() didn't know about - 'insane' compression (thanks ahØartemis*dk for the fix) - * Bugfix: MonkeySamplesPerFrame() was incorrect for MAC v3.95+ - (thanks ahØartemis*dk for the fix) - * Bugfix: getid3.check.php wasn't processing the assumeFormat - directive when (register_globals == off) - * Bugfix: detecting of synch pattern for MP3 files with invalid - data at the beginning wasn't always correct, also meant possible - incorrect bitrate/duration/etc info for such corrupt files. - getid3.functions.php now includes a replacement utf8_decode() - function for those PHP installations that are not configured - with the --with-xml option. (thanks stephaneØtekartists*com) - - -1.5.3: [September-30-2002] James Heinrich - » Added support for VQF. (thanks mtØmansonthomas*com for the idea) - New file: getid3.vqf.php - » Added support for FLAC. Comments, if present, are returned under - ['ogg'] because they follow the Ogg Vorbis structure standard. - New file: getid3.flac.php - ¤ OS/2-format bitmaps are now correctly interpreted. The format of - the bitmap is now returned in ['bmp']['type_os'] and - ['bmp']['type_version']. OS/2 bitmaps can be v1 or v2, Windows - can be v1, v4 or v5 - - -1.5.2: [September-25-2002] James Heinrich - » Support for RealMedia (audio & video) added - Note: only tested on G2 and v5 audio and video files - if anyone - has older and/or newer sample files, please test it and/or send - me the sample files. - (thanks stephaneØtekartists*com for idea) - New file: getid3.real.php - » Support for BMP added. Palette and pixel data can optionally be - extracted as well - this is slow and generally unneccesary, but - the option is there if you need it. Also includes PlotBMP() - which will take the extracted pixel data and output it as a true - color PNG. This function requires GD v2.0+ - Note: Untested on 16-bit and 32-bit BMPs because I couldn't find - any sample files - if you know of a program that can create such - files, please email getid3Øsilisoftware*com - Note: Support for RGB (uncompressed), RLE8 and RLE4 is included - and tested. BITFIELDS support is also included for 16- & 32-bit - formats, but it's untested, so if anybody has any test files - please send them to getid3Øsilisoftware*com - Note: Support currently only for Windows-format BMPs, and trying - to parse an OS/2-format bitmap leads to unpredictable/invalid - results. - New file: getid3.bmp.php - » PNG now fully parsed, including all information chunks - New file: getid3.png.php - ¤ Support for GIF/JPG/PNG moved to seperate files and expanded, - including standard ['resolution_x'] and ['resolution_y'] as well - as more thorough parsing of header information - New file: getid3.gif.php - New file: getid3.jpg.php - table_var_dump() simplified and now outputs {-style character - entities for characters outside the normal alphanumeric range - CleanOggCommentName() changed to a regular expression - (thanks chris-getid3Øbolt*cx for rewriting the function) - - -1.5.1: [September-20-2002] James Heinrich - » Added support for MPEGplus/Musepack SV7. ['fileformat'] is 'SV7' - for version 7 files (versions 4, 5 ,6 and 8 are not supported - yet, but will be of ['fileformat'] SV4, SV5, SV6 and SV8) when - they are supported (thanks Christian Fritz for the idea) - New file: getid3.mpc.php - ¤ ['bitrate_audio'], ['bitrate_video'], ['bitrate_mode'], - ['channels'], ['resolution_x'], and ['resolution_y'] keys added - for all appropriate formats - ¤ Ogg files with a COVERART comment now save and display the - attached image the same way as is done with ID3v2 APICs - ¤ ['ogg']['comments'][n]['data'] and - ['ogg']['comments'][n]['dataoffset'] is now returned for all - comments. ['ogg']['comments'][n]['data'] is only useful if - the field is supposed to contain binary data. It is a - base64_decode()'d version of ['value']. - ['ogg']['comments'][n]['dataoffset'] is the byte offset in the - file at which the 'COMMENTNAME=value string' starts, not the - start of just 'value' - ¤ ['ogg']['comments'][n]['image_mime'] is now returned if - ['ogg']['comments'][n]['data'] contains valid image data. - ¤ More than 3 Ogg pages may now be read in, if the comment data - is longer than 1 page (usually about 4kB) - ¤ ['fileformat'] is now 'mp2' rather than 'mp3' if it's MPEG-1, - Layer-II audio - ¤ ASF bitrates now calculated even if stream_bitrate_properties - object not present - ¤ ['asf']['stream_properties_object'] is now a numeric-key array - with one entry for each stream - the key being the stream number - ¤ ['replay_gain'] is returned for all audio formats that support - it (MP3-LAME, ID3v2, Ogg) (thanks Christian Fritz for the idea) - ¤ ['mpeg']['audio']['LAME']['RGAD']['radio_replay_gain'] is now - ['mpeg']['audio']['LAME']['RGAD']['radio'] (same for audiophile) - ¤ ASF/WMA files now use WM/Track to get track number from if - WM/TrackNumber is not available (thanks stephaneØtekartists*com) - ¤ ASF/WMV files now returns ['year'] and ['asf']['year'] - ¤ ASV/WMV files now use ['content_description']['description'] for - the ['comment'] field (thanks stephaneØtekartists*com) - ¤ ['track'] is now always returned as an integer - * Bugfix: Ogg comments that are larger than one data page (usually - about 4kB) are now correctly parsed (thanks Christian Fritz) - * Bugfix: Ogg comment data is now UTF8-decoded - * Bugfix: Ogg comment writing now UTF8-encodes the data - * Bugfix: playtime for ASF files was off by (usually - between 3 and 12 seconds) - * Bugfix: ['asf']['stream_properties_objects']['flags'] data was - possibly incorrect - * Bugfix: ASF Padding Object was overwriting - Stream Bitrate Properties Object data (now returned correctly in - ['asf']['padding_object'] - * Bugfix: ASF Marker Object Reserved_2 field was incorrect - * Bugfix: ASF Bitrate Mutual Exclusion Object had incorrect stream - numbers - Warning displayed if incorrectly-formatted Ogg comment is present - (known to be an issue with CDex v1.40, but fixed by v1.50b7) - (thanks Christian Fritz) - Ogg comment writing now checks for valid comment names - Added bitrate column in getid3.check.php, and added some formatting - (font, colour) - Performance tweaks using bitwise math instead of binary string - operations - - -1.5.0: [September-18-2002] James Heinrich - » Ogg comment writing support added. getid3.write.php has been - updated to allow for writing comment tags to both MP3 and Ogg. - Big thanks to Chris Bolt for writing the - OggWrite() function and offering it for inclusion in getID3() - New file: getid3.ogginfo.php - » Support for Monkey's Audio and APE tag added. - (thanks Christian Fritz for the idea) - New file: getid3.ape.php - ['fileformat'] now returns 'mac' for Monkey's Audio files, or - 'ape' for files with an APE tag (Monkey's Audio or other format) - » getid3.thumbnail.php has been removed from the distribution and - the table_var_dump() function now outputs APICs as seperate - files in the same directory as the analyzed file. This should - make the image-displaying more reliable as well as reduce - complexity. The naming convention for the images is - filename.ext.[byte offset of APIC data].[jpg|gif|png] - If anybody still has any problems with corrupted images please - let me know at getid3Øsilisoftware*com - » Support for extended Xing/LAME tag - (see http://users.belgacom.net/gc247244/extra/tag.html) - Data is returned in ['mpeg']['audio']['LAME'] - ¤ ['ogg']['tracknumber'] has been renamed to ['ogg']['track'] and - ['track'] is now returned in the root of the array - ¤ ['ogg']['pageheader'][n]['flag'] has been renamed to - ['ogg']['pageheader'][n]['flags'] and the unprocessed flag byte - is available in ['ogg']['pageheader'][n]['flags_raw'] - ¤ ['frequency'] is now returned for WAVE files in the root of the - array (thanks danielØelectroteque*org) - ¤ ASF files now return codec, bitrate, resolution, etc information - under ['asf']['video_media'] or ['asf']['audio_media'] - * Bugfix: RVA2 and EQU2 writing in getid3.putid3.php were - incorrectly writing Volume Adjustment field - * Bugfix: EQU2 in getid3.frames.php was reading Volume Adjustment - as unsigned integer instead of signed integer - * Bugfix: handling of remote files over HTTP & FTP was broken - (thanks Vince) - * Bugfix: incorrect handling of some ASF packets - ASF/Windows Media format now more fully parsed, including Index - Objects - Added several new fourCC video codecs - - -1.4.3: [September-15-2002] James Heinrich - » Now parses ASF / WMV / WMA files - ¤ New file: getid3.asf.php - * Bugfix: RoughTranslateUnicodeToASCII() would return nothing - if didn't find a terminator it was expecting - Added FILETIMEtoUNIXtime() function (for converting 64-bit - Microsoft FILETIME timestamps, used in ASF files and elsewhere, - to UNIX Epoch timestamps) - Added GUIDtoBytestring() and BytestringToGUID() functions - - -1.4.2: [September-12-2002] James Heinrich - » getID3() now requires PHP v4.1.0 or higher because it now is - designed to work with register_globals = off and the new auto- - globals ($_GET, $_SERVER, etc). - * Bugfix: VBR MP3 files with Fraunhofer-style VBR header were not - being correctly detected in most cases - (thanks dkushnerØoddcast*com and mikeØftl*com for sample files) - * Bugfix: IsValidTextEncoding() was broken - * Bugfix: Add stripslashes($EditorFilename) to getid3.write.php - (writing was broken for files with ' or " in the filename) - (thanks mikeØftl*com and kthejoker) - * Bugfix: If there is garbage data between a valid VBR header - frame and a sequence of valid MPEG-audio frames the VBR data is - no longer discarded. (thanks to mikeØftl*com for sample - Fraunhofer-style VBR file produced with MusicMatch v7.2) - ¤ Changed variable system to work with (register_globals = off) - ¤ Moved relevant code into seperate PlaytimeString() function - ¤ Added nl2br() to table_var_dump() for cleaner output - ¤ Now returns the following keys from Fraunhofer-VBR files: - ['VBR_seek_offsets'], ['VBR_seek_offsets_stride'], - ['VBR_offsets_relative'] and ['VBR_offsets_absolute'] - ¤ Added ID3v1matchesID3v2() function and implemented in - getid3.check.php (thanks to "Guest" in the forums for the idea) - Changed amount of data read in getid3.getimagesize.php from 10kB - to entire file. (thanks mikeØftl*com) - Wrapped function_exists() checks around function definitions in - getid3.functions.php - Fixed a lot of E_WARNING and E_NOTICE situations, especially in - ID3-writing code (getid3.putid3.php, etc) - Added checks to make sure all needed data is available for writing - ID3v2 tags - - -1.4.1b5: [May-30-2002] James Heinrich - * Bugfix: Unsynchronise() was broken, now fixed - (thanks mikeØftl*com) - * Bugfix: GenerateID3v2Tag() now correctly uses non-synchsafe - integers for frame size descriptors in ID3v2.3 and ID3v2.2 - (thanks mikeØftl*com) - ¤ Added ['artist'], ['title'], etc keys to root of returned - array to provide a common place to access any returned info - from any file type. Currently gets info from ID3v1, ID3v2, - Ogg, and RIFF/WAVE. Possible returned keys are: - title, artist, album, year, genre, comment, track - ¤ Modified LookupGenre() function to search for either genre based - on numeric ID, or now reverse lookup as well - ¤ Added ['artist'], ['title'], etc keys to ['RIFF'] information - if info tags are present - Added functionality to attach a picture to the ID3v2 tag in - getid3.write.php - Sorted genres into alphabetical order (special 3 at end of list) - in getid3.write.php - Changed the comment-edit field in getid3.write.php to a multi-line -