/*
Copyright (C) 2005 Mark D. Anderson mda@discerning.com.
Released under the Academic Free License 2.1 http://www.opensource.org/licenses/afl-2.1.php

**** 
NOTE: Matthias Platzer has developed an MIT-licensed javascript library (in part using
techniques from here), which you may prefer to use over this one, as his covers
more detection and is more documented than this one:
  http://www.knallgrau.at/code/plugin_js/
****

Some relevant articles:
    http://www.apple.com/quicktime/authoring/qtjavascript.html
    http://www.oreillynet.com/pub/a/javascript/2001/07/20/plugin_detection.html
    http://webmonkey.wired.com/webmonkey/reference/javascript_code_library/wm_pluginbot/?tw=reference&category=language_extensions
    http://b2knet.com
    http://cvs.sourceforge.net/viewcvs.py/awstats/awstats/wwwroot/js/awstats_misc_tracker.js?view=markup
    http://www.webreference.com/tools/browser/sniffer2.js
    http://www.microsoft.com/technet/prodtechnol/netshow/maintain/detplayr.mspx
    http://msdn.microsoft.com/workshop/author/clientcaps/overview.asp
    http://msdn.microsoft.com/workshop/author/behaviors/reference/behaviors/clientcaps.asp

The navigator.plugins and navigator.mimeTypes collections are filled
in on non-IE browsers, and on IE/mac.
On IE/windows, they exist but are empty.

The navigator.plugins object is indexed by number, or by name strings like "QuickTime Plug-in 6.5.2".
The individual items have members: name, filename, description, and length (number of mimetypes).

The navigator.mimeTypes object is indexed by number, or by type strings like "audio/aiff"
or "application/x-shockwave-flash".
The individual items have members: type, suffixes, description, and enabledPlugin (the owning
object in the naviagor.plugins collection).

IE/Windows has no way to query everything that is available; you can only
probe for any particular ActiveX control by progid (using new ActiveXControl(progid)) 
or by classid (using clientCaps).

Several articles claim that it is necessary to use VBScript to probe for the
existence of an ActiveX control, such as:

    On Error Resume Next
    Set theObject = CreateObject("QuickTimeCheckObject.QuickTimeCheck.1")
    On Error goto 0
    If IsObject(theObject) Then
       If theObject.IsQuickTimeAvailable(0) Then 'Just check for file
          haveqt = true
       End If
    End If

or:

    Function TestActiveX(objectID) 
       on error resume next 
       TestActiveX = IsObject(CreateObject(objectID)) 
    End Function 

But this is not true. It can be done in JavaScript.
It is just that in IE 4.0, there was no try/catch mechanism for handling errors.
We don't care about IE4, so we use JavaScript exclusively.

IE 5.0 and above has the clientCaps behavior:
   http://msdn.microsoft.com/workshop/author/behaviors/reference/behaviors/clientcaps.asp
This can be used to probe for the existence of a component based on its classid.
There is a list of detectable components at: 
  http://msdn.microsoft.com/workshop/author/behaviors/reference/methods/detectable.asp
The documentation for getComponentVersion says that it cannot
be used to detect versions of third-party components. That isn't true.
It works with some third-party components, such as Flash, but not
with others, such as Real and QuickTime.

TODO:
  Some other controls to consider adding in the future:
    CRViewer.CRViewer
    AxMetaStream.MetaStreamCtl.1
    Autodesk.MGMap.1
    Wfica.WficaCtl.1
    IPIX.ActiveXCtrl.5
    RFXInstMgr.RFXInstMgr.1 WGInstMgr.WGInstMgr.1 richfx.com

  Java stuff

  cookie detection: navigator.cookieEnabled;

  screen stuff: screen.widthscreen.height screen.colorDepth

  Experiment with ActiveX support in Gecko on Windows:
    GeckoActiveXObject
    http://www.iol.ie/~locka/mozilla/plugin.htm
    http://www.skyzyx.com/scripts/geckoax.php
    http://www.skyzyx.com/scripts/axobj.php
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmrm10/htm/predeliveringlicensesfornetscape71.asp

  Fix it on Opera - repeats plugins elements, and has no enabledPlugin value

  Use activex_pdf.GetVersions() for PDF.PdfCtrl.5
  Use activex_real.GetIsPlus() for real
*/

// whether this browser has a real navigator.plugins object (vs. empty one on IE)
function has_plugins() {return (navigator.plugins && (navigator.plugins.length > 0));}

// whether this browser supports ActiveX (vs. useless object on IE/Mac)
function has_activex() {return ((typeof 'ActiveXObject' != 'undefined') && (navigator.userAgent.indexOf('Win') != -1));} 

/****************************************************************
 Functions to use if has_plugins()
 ****************************************************************/

// if there is a working nagigator.plugins object, this uses document.write
// to produce an html table of plugin information, and returns 1.
// otherwise it does nothing and returns 0.
function show_plugins() {
    var plugins = navigator.plugins;
    if (!plugins || (plugins.length == 0)) {
	//document.write("No plugins<br>"); 
	return 0;
    }
    document.write("There are " + plugins.length + " plugins installed in your browser:");  
    document.write("<table border='1'><tr><th>Name<th>File<th>Description<th>Mime Types");
    for(var i=0;i<plugins.length;++i) {
	var plugin = plugins[i];
	document.write("<tr><td>" + plugin.name + "<td>" + plugin.filename + "<td>" + plugin.description + "<td>" + plugin.length);
    }
    document.write("</table>");
    return 1;
}

// if there is a working navigator.plugins object, this uses document.write
// to produce an html table of plugin information, and returns 1.
// otherwise it does nothing and returns 0.
// if plugin_name is specified, it only shows the ones for the specified plugin.
function show_mimetypes(plugin_name) {
    if (!navigator.mimeTypes || (navigator.mimeTypes.length == 0)) {
	//document.write("No mimeTypes<br>");
	return 0;
    }
    document.write("Your browser accepts " + navigator.mimeTypes.length + " mime types" +
     (plugin_name ? " via plugin '" + plugin_name + "'" : '') + ":");
    document.write("<table border='1'><tr><th>Type<th>Description<th>Suffixes" + (plugin_name ? '' : '<th>Plugin'));
    for (var i=0;i<navigator.mimeTypes.length;++i) {
	var mt = navigator.mimeTypes[i];
        var pname = (mt.enabledPlugin ? mt.enabledPlugin.name : 'none');
        if (plugin_name && (plugin_name != pname)) continue; 
	document.write("<tr><td>" + mt.type + 
		       // In some cases, the mimetype description might be "<applet> tags" and we don't want an applet loaded.
		       "<td>" + mt.description.replace(/<applet/i, '&lt;applet') + 
		       // allow browser to wrap the list of suffixes by space separating
		       "<td>" + mt.suffixes.replace(/,/g, " ") + 
		       (plugin_name ? '' : "<td>" + pname) +
		       "");
    }
    document.write("</table>");
    return 1;
}

// navigator.plugins only.
// look through all plugins for ones with a name or description containing the provided substring.
// returns first match, or null.
function find_plugin(match_string) {
    for(var i=0;i<navigator.plugins.length;++i) {
	var plugin = navigator.plugins[i];
	if (plugin.name.indexOf(match_string) != -1 ||
	    plugin.description.indexOf(match_string) != -1)
	    return plugin;
    }
    return null;
}

// seems not to work on Opera 7.5
function find_plugin_by_mime(mimetype) {
    var mt = navigator.mimeTypes[mimetype];
    return mt.enabledPlugin;
}

// return any string of digits and periods in name or description,
// or the string 'unknown'.
function plugin_version(plugin) {
    var matches = /[\d\.]+/.exec(plugin.name);
    if (matches) return matches[0];
    matches = /[\d\.]+/.exec(plugin.description);
    return matches ? matches[0] : 'unknown';
}

/****************************************************************
 Functions to use if has_activex()
 ****************************************************************/
// IE/Windows-only
// Utility used for ActiveX controls, which increment their progid for each version.
// Starts at max and decrements down to 1.
// Note this doesn't work great for Macromedia Director, which has version suffixes like '.8.5.1', '.8.5', '.8'
function activex_version(base, max) {
   for(var i=max; i>0; i--) {
     if (has_activex_object(base + i)) return i;
   }
   return 0;
}

function has_activex_object(progid) {
    try {new ActiveXObject(progid); return 1;}
    catch(e) {}
    return 0;
}

// must match element id/name in html , to avoid a call to create_caps_el()
var CAPS_ELEMENT_ID = 'clientcaps';

function create_caps_el() {
    var capsel = document.createElement("DIV");
    capsel.id = 'clientcaps';
    capsel.addBehavior("#default#clientCaps");
    document.body.appendChild(capsel);
    if (!document.getElementById('clientCaps')) alert('not created');
    return capsel;
}

// look up version of a component by classid.
// works for Microsoft stuff, and some third-party stuff.
// won't work for quicktime.
function ie_component_version(classid) {
    if (!classid) return "no classid specified";
    var clientcaps = document.getElementById(CAPS_ELEMENT_ID);
    if (!clientcaps) {
	clientcaps = create_caps_el();
	// return "no element with id '" + CAPS_ELEMENT_ID + "'";
    }
    if (typeof clientcaps.getComponentVersion == 'undefined') return "no clientCaps behavior";
    var ver = clientcaps.getComponentVersion(classid, 'componentid');
    return ver;
}
 

/****************************************************************
 Support for particular plugins or controls (QuickTime, etc.)
 ****************************************************************/
function unpack_real_version(n) {
    return "" + (n >> 28) + "." +
	((n & 0xFF00000) >> 20) + "." + 
	((n & 0xFF000) >> 12) + "." + 
	( n & 0xFFF);
}

// QuickTimeVersion reports decimal numbers like '106070016'. 
// see http://developer.apple.com/technotes/tn/tn1197.html
// convert to hex, then separate first 3 digits by period.
// remainder (typically "80") means it is a release version.
function convert_quicktime_version(ver) {
    if (!ver) return '';
    var nver = ver.toString(16);
    nver = nver.substring(0,1) + '.' + nver.substring(1,2) + '.' + nver.substring(2,3);
    // alert("made qt version=" + nver + " from " + ver);
    return nver;
}

/****************************************************************
 High-level utilities callable for either has_plugins() or has_activex()
 ****************************************************************/
function get_quicktime_version() {
    var ver = '';
    if (has_activex()) {
	try {
	    var qt = new ActiveXObject("QuickTimeCheckObject.QuickTimeCheck.1");
	    ver = convert_quicktime_version(qt.QuickTimeVersion);
	}
	catch(e) {}
    }
    if (has_plugins()) {
	// Opera doesn't index by mime type.
	// IE/Mac does index by mime type....
	// However, for some reason in IE/Mac Preferences you'll see that "video/quicktime" is handled
	// by "QuickTime Player" while "video/mpeg" is handled by "QuickTime plugin.plugin",
	// and the enabledPlugin value is empty for "QuickTime Player".
	// In any event, any particulare mime type might be hijacked by any player.
	// We use 'image/x-quicktime' instead of 'video/quicktime' as it is unlikely to be hijacked,
	// and works ok by default in IE/Mac
        var plugin = navigator.userAgent.indexOf('Opera') != -1 ? find_plugin('QuickTime') : find_plugin_by_mime('image/x-quicktime');
	if (plugin) {
	    ver = plugin_version(plugin);
	}
	else {	
	    // alert("no plugin for mime");
	}
    }
    return ver;
}

function show_quicktime_version() {
    var ver = get_quicktime_version();
    if (ver) 
	document.write('<span class="player-version">QuickTime ' + ver + ' installed</span>');
    else
	document.write('<span class="errors"><a target="_blank" href="http://www.apple.com/quicktime/">QuickTime</a> not installed</span>');
}

function report_plugin(generic_name, match_string, progid_base, progid_max, classid, url) {
    document.write("<tr><td>" + (url ? '<a href="' + url + '">' + generic_name + '</a>' : generic_name) + "<td>");
    if (has_plugins()) {
        var plugin = find_plugin(match_string);
	if (plugin)
	    document.write(plugin_version(plugin) + '<td>' + plugin.name + ': ' + plugin.description);
	else
	    document.write("not installed");
	return;
    }
    if (has_activex()) {
	var ver;
	var progid_suffix = '';
	if (classid) {
            ver = ie_component_version(classid);
            // if (!ver && generic_name == 'Real') alert("no classid version for " + generic_name + " with classid " + classid);
	}

	if (ver) {
	    // ie component versions are comma separated
	    ver = ver.replace(/,/g,'.');
	}
	else if (progid_max) {
	    ver = activex_version(progid_base, progid_max);
	    if (ver) progid_suffix = ver;
	}
	else if (progid_base.indexOf("MediaPlayer") != -1) {
	    if (has_activex_object(progid_base)) {
		// MediaPlayer.MediaPlayer.1 is available starting with WMP 5.2. 
		// WMPlayer.OCX is available starting with WMP 7?
		try {
		    var wm7 = new ActiveXObject("WMPlayer.OCX"); // '.7'
		    // alert("found WMPlayer");
		    ver = wm7.versionInfo;
		}
		catch(e) {ver = "6.4 or older";}
	    }
	}
	else if (progid_base.indexOf("Real") != -1) {
            // http://service.real.com/help/library/guides/ScriptingGuide/HTML/realscript.htm
	    // http://service.real.com/help/library/guides/realone/ScriptingGuide/HTML/htmfiles/embedmet.htm
	    try {
    //alert("about to create rp");
		var rp = new ActiveXObject("rmocx.RealPlayer G2 Control");
//	alert("got rp");
		ver = rp.GetVersionInfo();
//	alert("got rp version " + ver);
                ver = unpack_real_version(ver);
//	alert("unpacked " + ver);
	    }
	    catch(e) {
		var cids = [//'R1P ActiveXObject', // is there such a thing?
			    //'IERJCtl.IERJCtl', // IERJCtl.IERJCtl = Real Juke 
			    'rmocx.RealPlayer G2 Control', //RealG2, v6
			    'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)',  // v5
			    'RealVideo.RealVideo(tm) ActiveX Control (32-bit)' // v4
		];
		for(var i=0;i<cids.length;i++) {
		    var cid = cids[i];
		    if (has_activex_object(cid)) {ver = 'unknown'; progid_base = cid; break;}  
		}
	    }
	}
	else if (progid_base.indexOf("QuickTime") != -1) {
	    try {
		var qt = new ActiveXObject(progid_base);
		ver = convert_quicktime_version(qt.QuickTimeVersion);
	    }
	    catch(e) {}
	}
	else {
	    ver = has_activex_object(progid_base) ? 'unknown' : '';
	}
	if (!ver)
	    document.write("not installed");
	else
	    document.write(ver + '<td>' + progid_base + progid_suffix);
    }
   
}

// high-level function that works on IE and non-IE to report on well-known plugins.
function report_plugins() {
    document.write("<table border='1'><tr><th>Generic Name<th>Highest Version<th>Plugin/ActiveX Control Names");

    report_plugin("Adobe Acrobat",                  "Acrobat",       "PDF.PdfCtrl.", 7,                           '{CA8A9780-280D-11CF-A24D-444553540000}', 'http://www.adobe.com/products/acrobat/readstep2.html', 'application/pdf');
    report_plugin('Adobe SVG',                      'Adobe SVG',     'Adobe.SVGCtl', 4,                           '{78156A80-C6A1-4BBF-8E6A-3CD390EEB4E2}', 'http://www.adobe.com/svg/viewer/install/', 'image/svg+xml');
    report_plugin('Apple QuickTime',                'QuickTime',     'QuickTimeCheckObject.QuickTimeCheck.1', '', '{02BF25D5-8C17-4B23-BC80-D3488ABDDC6B}', 'http://www.apple.com/quicktime/', 'video/quicktime');
    report_plugin('Macromedia Director',            'Director',      'SWCtl.SWCtl.', 9,                           '{166B1BCA-3F9C-11CF-8075-444553540000}', 'http://www.macromedia.com/shockwave/download/', 'application/x-director');
    report_plugin('Macromedia Flash',               'Flash',         'ShockwaveFlash.ShockwaveFlash.', 7,         '{D27CDB6E-AE6D-11CF-96B8-444553540000}', 'http://www.macromedia.com/go/getflashplayer', 'application/x-shockwave-flash');
    // '{6BF52A52-394A-11d3-B153-00C04F79FAA6}' for WMP 7 and above
    // see http://www.w3schools.com/media/media_playerref.asp
    report_plugin("Microsoft Windows Media Player", 'Windows Media', 'MediaPlayer.MediaPlayer.1', '',             '{22D6F312-B0F6-11D0-94AB-0080C74C7E95}', 'http://www.microsoft.com/windows/windowsmedia/', 'application/x-mplayer2');
    report_plugin("Real",                           'Real',          'Real', '',                                  '{CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA}', 'http://www.real.com/', 'audio/x-pn-realaudio-plugin');

    document.write("</table>");
}

/****************************************************************
 UAC-specific
 ****************************************************************/

/*
<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="160" height="136" codebase="http://www.apple.com/qtactivex/qtplugin.cab">
      <param name="src" value="http://teacherline.pbs.org/teacherline/videos/rtt/CharacterizeDonYork36.mov">
      <param name="autoplay" value="true">
      <param name="controller" value="true">
      <param name="cache" value="true">
   <embed src="http://teacherline.pbs.org/teacherline/videos/rtt/CharacterizeDonYork36.mov"
      autoplay="true"
          width="160"
          height="136"
          controller="true"
          cache="true">
   </embed>
</object>

http://david.egbert.name/quicktime/tutorials/embedding_quicktime/
    points out problem letting IE do things

http://stream.uen.org/medsol/digvid/html/1D_qtdetect.html
    need activex and the qt plugin
    interesting detection script
 good examples:
    http://stream.uen.org/medsol/digvid/html/sampler_embed_qtmovies.html

http://developer.apple.com/quicktime/compatibility.html
    more info
apple docs for embed params:
  http://www.apple.com/quicktime/authoring/embed.html

height param is movie height + 16 if a controller is true
    for audio only, set height=16 and width=whatever

<OBJECT ID="WMPlay" classid="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95" codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701"
standby="Loading Microsoft Windows Media Player components..."
type="application/x-oleobject" width="320" height="306">
<PARAM name="FileName" value="FILENAME">
<PARAM NAME="ShowStatusBar" VALUE="True">
<PARAM name="DisplaySize" value="0">
<PARAM name="cache" value="true">
<PARAM name="bgcolor" value="black">
<PARAM name="autoplay" value="true">
<PARAM name="loop" value="false">
<EMBED type="application/x-mplayer2 pluginspage="http://www.microsoft.com/isapi/redir.dll?prd=windows&sbp=mediaplayer&ar=media&sba=plugin"
filename="FILENAME"
displaysize="2"
name="WMPlay"
CACHE=TRUE loop="true" bgcolor="black" AUTOPLAY=TRUE> </EMBED> </OBJECT>
*/


/*
Note that we could also serve a .qtl file (mime application/x-quicktimeplayer).

   <?xml version="1.0"?>
   <?quicktime type="application/x-quicktime-media-link"?>
   <embed src="http://quicktiming.org/tutorials/mime/quicktiming.mov" autoplay="true" />

but still the user has control over whether it is handled by application, plugin, or download
*/

function media_click(src, confirm_size, media_class, title, elid) {
  if (confirm_size) {
     var sure = confirm('Are you sure you want to download or play this file of size ' + confirm_size + ' ?');
     if (!sure) return false;
  }
  var frm = document.getElementById('playform');
  var size = frm.elements['play.size'].value;
  var where = frm.elements['play.where'].value;
  // alert("size=" + size + " where=" + where);
  if (where == 'link') return true;

  var dims = size.split('x'); var width = dims[0]; var height = dims[1]; 
  width = parseInt(width);
  height = parseInt(height) + 16;

  // make up fixed width and height for audio, and if popup, make it embedded instead
  if (media_class == 'audio') {
      width = 400;
      height = 16;
      if (where == 'popup') where = 'embedded';
  }
  var params = {
      src: src,
      // qtsrc only necessary for embed. for object/activex, mime hijacking is not an issue.
      // there is no point to specifying an identical value to src.
      // The idea is you specify a qt-specific file for src, so the browser will pick qt as the plugin.
      // Then when the plugin looks at embed, it uses qtsrc instead.
      // For example:
      //    src="path/get_qt.ptng" type="image/x-macpaint" qtsrc="path/my_movie.mov"
      //       or UNeedQT4.pntg
      //    src="UneedQT4.qti" type="image/x-quicktime" qtsrc="My.mov"
      //       qti requires qt4 or higher.
      // But the browser will still fetch the src, right?
      // So why do all this for non-IE browsers...
      // See:
      // http://www.apple.com/quicktime/authoring/qtwebfaq.html
      // http://www.apple.com/quicktime/authoring/embed2.html#qtsrc
      // FYI, qtsrc is relative to src, if qtsrc is relative.
      // See: http://developer.apple.com/documentation/QuickTime/QTBooks/corrections.htm
      // qtsrc: src,
      scale: 'ASPECT',
      controller: 'true',
      autoplay: 'true',
      // TODO: could specify target quicktimeplayer to launch externally 
      //   myself - plugin, regardless
      //   quicktimeplayer - goes to player, but a user preference in the player for the type may say to use plugin
      //   webbrowser - let browser decide
      // target: 'quicktimeplayer',
      enablejavascript: 'true'
  };

  // in a popup, say 100% so can be resized.
  var winheight = height; var winwidth = width;
  if (where == 'popup') {width = '100%'; height = '100%';}

  /*
    Allegedly
    document.idval works in IE
    document.nameval works in non-IE
    document.embeds is an array of embed objects with numerical index or nameval in non-IE
   */
  var idatts = elid ? ' id="' + elid + '"' : '';
  var html;
  if (has_activex()) {
      // strictly should not have attributes not declared in http://www.w3.org/TR/REC-html40/struct/objects.html#edef-OBJECT
      html = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab"' +
	  ' width="' + width + '" height="' + height + '"' + idatts + '>';
      for (var paramname in params) {
	  html += '<param name="' + paramname + '" value="' + params[paramname] + '">' + "\n";
      }
      html += '</object>';
  }
  else {
      html = '<embed pluginspage="http://www.apple.com/quicktime/download/" width="' + width + '" height="' + height + '"' + idatts;
      for (var paramname in params) {
	  html += ' ' + paramname + '="' + params[paramname] + '"' + "\n";
      }
      html += '></embed>';
  }
  // alert(html);
  if (where == 'embedded') {
      document.getElementById('embedhere').innerHTML = html;
      return false;
  }
  if (where == 'popup') {
      // add a fudge factor to prevent scroll bars
      var win = window.open('', 'uacplay', 'resizable=yes,scrollbars=no,status=0,menubar=0,toolbar=0,location=0,height=' + (winheight + 24)+ ',width=' + (winwidth + 24));
      win.document.write("<html><head><title>" + title + "</title></head><body>" + html + "</body></html>");
      return false;
  }
  return true;
}
