From 0df1f91e53210faba80ee669bc1800f5d4742fd0 Mon Sep 17 00:00:00 2001 From: Michael Dale Date: Sat, 17 Oct 2009 10:35:46 +0000 Subject: [PATCH] * stubs for api iFrame Proxy system. Early summary in /js2/mwEmbed/apiProxy/ ** testing page for proxy setup (hard coded to localhost) ** added basic support to skin.php for enabling of apiproxy (off by default) * added flickrSearch as another remote repository * other refactoring, cleanup of remoteSearchDriver (add-media-wizard) * removed query.json.1-3.js * removed jquery.secureEvalJSON.js * replaced with official http://www.JSON.org/json2.js script which is only included if JSON is undefined --- includes/AutoLoader.php | 1 + includes/DefaultSettings.php | 33 +- includes/Skin.php | 17 +- js2/apiProxyPage.js | 27 + js2/editPage.js | 2 +- js2/mwEmbed/jquery/plugins/jquery.json-1.3.js | 156 ------ .../jquery/plugins/jquery.secureEvalJSON.js | 156 ------ .../libAddMedia/mvBaseUploadInterface.js | 17 +- js2/mwEmbed/libAddMedia/remoteSearchDriver.js | 306 ++++++----- .../searchLibs/archiveOrgSearch.js | 22 +- .../searchLibs/baseRemoteSearch.js | 42 +- .../libAddMedia/searchLibs/flickrSearch.js | 139 ++++- .../libAddMedia/searchLibs/mediaWikiSearch.js | 53 +- .../libAddMedia/searchLibs/metavidSearch.js | 6 +- .../libAddMedia/seqRemoteSearchDriver.js | 181 ------- js2/mwEmbed/libEmbedVideo/nativeEmbed.js | 13 +- .../libMwApi/NestedCallbackIframe.html | 16 + js2/mwEmbed/libMwApi/json2.js | 479 ++++++++++++++++++ js2/mwEmbed/libMwApi/mw.proxy.js | 210 ++++++++ js2/mwEmbed/libMwApi/mwProxy.js | 17 + js2/mwEmbed/mv_embed.js | 192 ++++--- js2/mwEmbed/php/languages/mwEmbed.i18n.php | 1 + js2/mwEmbed/tests/testApiProxy.html | 76 +++ js2/remoteMwEmbed.js | 9 + 24 files changed, 1376 insertions(+), 795 deletions(-) create mode 100644 js2/apiProxyPage.js delete mode 100644 js2/mwEmbed/jquery/plugins/jquery.json-1.3.js delete mode 100644 js2/mwEmbed/jquery/plugins/jquery.secureEvalJSON.js delete mode 100644 js2/mwEmbed/libAddMedia/seqRemoteSearchDriver.js create mode 100644 js2/mwEmbed/libMwApi/NestedCallbackIframe.html create mode 100644 js2/mwEmbed/libMwApi/json2.js create mode 100644 js2/mwEmbed/libMwApi/mw.proxy.js create mode 100644 js2/mwEmbed/libMwApi/mwProxy.js create mode 100644 js2/mwEmbed/tests/testApiProxy.html diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 94e4dced61..8ca1a4d696 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -619,6 +619,7 @@ $wgJSAutoloadLocalClasses = array( 'uploadPage' => 'js2/uploadPage.js', 'editPage' => 'js2/editPage.js', 'ajaxCategories' => 'js2/ajaxcategories.js', + 'apiProxyPage' => 'js2/apiProxyPage.js' ); class AutoLoader { diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index c135b374b9..80878160b4 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -213,9 +213,9 @@ $wgImgAuthPublicTest = true; ///< defaults to true - if public read is turned on * thumbScriptUrl The URL for thumb.php (optional, not recommended) * transformVia404 Whether to skip media file transformation on parse and rely on a 404 * handler instead. - * initialCapital Equivalent to $wgCapitalLinks (or $wgCapitalLinkOverrides[NS_FILE], - * determines whether filenames implicitly start with a capital letter. - * The current implementation may give incorrect description page links + * initialCapital Equivalent to $wgCapitalLinks (or $wgCapitalLinkOverrides[NS_FILE], + * determines whether filenames implicitly start with a capital letter. + * The current implementation may give incorrect description page links * when the local $wgCapitalLinks and initialCapital are mismatched. * pathDisclosureProtection * May be 'paranoid' to remove all parameters from error messages, 'none' to @@ -462,7 +462,7 @@ $wgMaxUploadSize = 1024*1024*100; # 100MB /** - * Enable Firefogg support. Adds support for in-browser transcoding to Ogg + * Enable Firefogg support. Adds support for in-browser transcoding to Ogg * Theora, chunked uploads for large image files and client side hash checks. * * Ignored unless $wgEnableJS2system is true. @@ -2438,11 +2438,11 @@ $wgCapitalLinks = true; /** * @since 1.16 - This can now be set per-namespace. Some special namespaces (such - * as Special, see Namespace::$alwaysCapitalizedNamespaces for the full list) must be - * true by default (and setting them has no effect), due to various things that - * require them to be so. Also, since Talk namespaces need to directly mirror their - * associated content namespaces, the values for those are ignored in favor of the - * subject namespace's setting. Setting for NS_MEDIA is taken automatically from + * as Special, see Namespace::$alwaysCapitalizedNamespaces for the full list) must be + * true by default (and setting them has no effect), due to various things that + * require them to be so. Also, since Talk namespaces need to directly mirror their + * associated content namespaces, the values for those are ignored in favor of the + * subject namespace's setting. Setting for NS_MEDIA is taken automatically from * NS_FILE. * EX: $wgCapitalLinkOverrides[ NS_FILE ] = false; */ @@ -2787,7 +2787,7 @@ $wgEnableScriptLoader = false; * * note this will only check core scripts that are directly included on the page. * (not scripts loaded after the initial page display since after initial page - * display scripts inherit the unique request id) + * display scripts inherit the unique request id) * * and or you can update $wgStyleVersion */ @@ -2799,6 +2799,11 @@ $wgScriptModifiedCheck = true; */ $wgEnableJS2system = false; +/* + * enable api iframe proxy + */ +$wgEnableIframeApiProxy = false; + /* * boolean; if we should minify the output. (note if you send ?debug=true in * the page request it will automatically not group and not minify) @@ -2806,7 +2811,7 @@ $wgEnableJS2system = false; $wgEnableScriptMinify = true; /* - * boolean; if we should enable javascript localization (it loads loadGM json + * boolean; if we should enable javascript localization (it loads loadGM json * call with mediaWiki msgs) */ $wgEnableScriptLocalization = true; @@ -2817,7 +2822,7 @@ $wgEnableScriptLocalization = true; $wgMwEmbedDirectory = "js2/mwEmbed/"; /* - * Turn on debugging for the javascript script-loader & forces fresh copies + * Turn on debugging for the javascript script-loader & forces fresh copies * of javascript */ $wgDebugJavaScript = false; @@ -3729,7 +3734,7 @@ $wgAjaxWatch = true; $wgAjaxUploadDestCheck = true; /** - * Enable the AJAX upload interface (needed for large http uploads & to display + * Enable the AJAX upload interface (needed for large http uploads & to display * progress on uploads for browsers that support it) */ $wgAjaxUploadInterface = true; @@ -4010,7 +4015,7 @@ $wgExceptionHooks = array(); * Page property link table invalidation lists. When a page property * changes, this may require other link tables to be updated (eg * adding __HIDDENCAT__ means the hiddencat tracking category will - * have been added, so the categorylinks table needs to be rebuilt). + * have been added, so the categorylinks table needs to be rebuilt). * This array can be added to by extensions. */ $wgPagePropLinkInvalidations = array( diff --git a/includes/Skin.php b/includes/Skin.php index a0c3311ef1..81dd0bf0d0 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -363,6 +363,7 @@ class Skin extends Linker { global $wgVersion, $wgEnableAPI, $wgEnableWriteAPI; global $wgRestrictionTypes, $wgLivePreview; global $wgMWSuggestTemplate, $wgDBname, $wgEnableMWSuggest; + global $wgSitename, $wgEnableIframeApiProxy, $wgEnableJS2system; $ns = $wgTitle->getNamespace(); $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $wgTitle->getNsText(); @@ -413,6 +414,7 @@ class Skin extends Linker { 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null, 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(), 'wgNamespaceIds' => $wgContLang->getNamespaceIds(), + 'wgSiteName' => $wgSitename, ); if ( $wgContLang->hasVariants() ) { $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); @@ -442,6 +444,17 @@ class Skin extends Linker { $vars['wgLivepreviewMessageError'] = wfMsg( 'livepreview-error' ); } + //add api proxy var and script link if on the special proxy page: + if( $wgEnableJS2system && + $wgTitle->getNamespace() == NS_MEDIAWIKI && + $wgTitle->getDBKey() == 'ApiProxy' ) + { + $vars['wgEnableIframeApiProxy'] = $wgEnableIframeApiProxy; + //also add the apiProxy Page script if we are on that page + if( $wgEnableIframeApiProxy ) + $wgOut->addScriptClass( 'apiProxyPage' ); + } + if ( $wgOut->isArticleRelated() && $wgUseAjax && $wgAjaxWatch && $wgUser->isLoggedIn() ) { $msgs = (object)array(); foreach ( array( 'watch', 'unwatch', 'watching', 'unwatching' ) as $msgName ) { @@ -864,7 +877,7 @@ END; $catlinks = $this->getCategoryLinks(); $classes = 'catlinks'; - + // Check what we're showing global $wgOut, $wgUser; $allCats = $wgOut->getCategoryLinks(); @@ -960,7 +973,7 @@ END; else $ret .= str_repeat( "', $curIdent ) . ''; diff --git a/js2/apiProxyPage.js b/js2/apiProxyPage.js new file mode 100644 index 0000000000..46ff0fe317 --- /dev/null +++ b/js2/apiProxyPage.js @@ -0,0 +1,27 @@ +/* +* mwProxy js2 page system. +* +* invokes the apiProxy system +*/ + +/* + * since this is proxy server set a pre-append debug flag to know which debug msgs are coming from where + */ + +if( !mwApiProxyConfig ) + var mwApiProxyConfig = {}; + +//The default mwApiProxyConfig config +//(presently hard coded but should read from user and site config) +var mwApiProxyDefaultConfig = { + 'master_whitelist' : [ 'en.wikipedia.org', 'localhost', '127.1.1.100' ], + 'master_blacklist' : [] +}; +//user_white_list should also be checked and configured at runtime. + +js2AddOnloadHook( function() { + //build our configuration from the default and mwApiProxyConfig vars + mwApiProxyConfig = $j.extend(true, mwApiProxyDefaultConfig, mwApiProxyConfig); + + $j.apiProxy( 'server', mwApiProxyConfig ); +}); diff --git a/js2/editPage.js b/js2/editPage.js index 63958ff1d2..1c0d1e0cb7 100644 --- a/js2/editPage.js +++ b/js2/editPage.js @@ -24,7 +24,7 @@ var defaultAddMediaConfig = { js2AddOnloadHook( function() { var amwConf = $j.extend( true, defaultAddMediaConfig, mwAddMediaConfig ); // kind of tricky, it would be nice to use run on ready "loader" call here - if( typeof $j.wikiEditor != 'undefined' ) { + if( typeof $j.wikiEditor != 'undefined' ) { $j( 'textarea#wpTextbox1' ).bind( 'wikiEditor-toolbar-buildSection-main', function( e, section ) { if ( typeof section.groups.insert.tools.file !== 'undefined' ) { diff --git a/js2/mwEmbed/jquery/plugins/jquery.json-1.3.js b/js2/mwEmbed/jquery/plugins/jquery.json-1.3.js deleted file mode 100644 index 225ca82a10..0000000000 --- a/js2/mwEmbed/jquery/plugins/jquery.json-1.3.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * jQuery JSON Plugin - * version: 1.0 (2008-04-17) - * - * This document is licensed as free software under the terms of the - * MIT License: http://www.opensource.org/licenses/mit-license.php - * - * Brantley Harris technically wrote this plugin, but it is based somewhat - * on the JSON.org website's http://www.json.org/json2.js, which proclaims: - * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that - * I uphold. I really just cleaned it up. - * - * It is also based heavily on MochiKit's serializeJSON, which is - * copywrited 2005 by Bob Ippolito. - */ - -(function($) { - function toIntegersAtLease(n) - // Format integers to have at least two digits. - { - return n < 10 ? '0' + n : n; - } - - Date.prototype.toJSON = function(date) - // Yes, it polutes the Date namespace, but we'll allow it here, as - // it's damned usefull. - { - return this.getUTCFullYear() + '-' + - toIntegersAtLease(this.getUTCMonth()) + '-' + - toIntegersAtLease(this.getUTCDate()); - }; - - var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g; - var meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }; - - $.quoteString = function(string) - // Places quotes around a string, inteligently. - // If the string contains no control characters, no quote characters, and no - // backslash characters, then we can safely slap some quotes around it. - // Otherwise we must also replace the offending characters with safe escape - // sequences. - { - if (escapeable.test(string)) - { - return '"' + string.replace(escapeable, function (a) - { - var c = meta[a]; - if (typeof c === 'string') { - return c; - } - c = a.charCodeAt(); - return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); - }) + '"'; - } - return '"' + string + '"'; - }; - - $.toJSON = function(o, compact) - { - var type = typeof(o); - - if (type == "undefined") - return "undefined"; - else if (type == "number" || type == "boolean") - return o + ""; - else if (o === null) - return "null"; - - // Is it a string? - if (type == "string") - { - return $.quoteString(o); - } - - // Does it have a .toJSON function? - if (type == "object" && typeof o.toJSON == "function") - return o.toJSON(compact); - - // Is it an array? - if (type != "function" && typeof(o.length) == "number") - { - var ret = []; - for (var i = 0; i < o.length; i++) { - ret.push( $.toJSON(o[i], compact) ); - } - if (compact) - return "[" + ret.join(",") + "]"; - else - return "[" + ret.join(", ") + "]"; - } - - // If it's a function, we have to warn somebody! - if (type == "function") { - throw new TypeError("Unable to convert object of type 'function' to json."); - } - - // It's probably an object, then. - var ret = []; - for (var k in o) { - var name; - type = typeof(k); - - if (type == "number") - name = '"' + k + '"'; - else if (type == "string") - name = $.quoteString(k); - else - continue; //skip non-string or number keys - - var val = $.toJSON(o[k], compact); - if (typeof(val) != "string") { - // skip non-serializable values - continue; - } - - if (compact) - ret.push(name + ":" + val); - else - ret.push(name + ": " + val); - } - return "{" + ret.join(", ") + "}"; - }; - - $.compactJSON = function(o) - { - return $.toJSON(o, true); - }; - - $.evalJSON = function(src) - // Evals JSON that we know to be safe. - { - return eval("(" + src + ")"); - }; - - $.secureEvalJSON = function(src) - // Evals JSON in a way that is *more* secure. - { - var filtered = src; - filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@'); - filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); - filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); - - if (/^[\],:{}\s]*$/.test(filtered)) - return eval("(" + src + ")"); - else - throw new SyntaxError("Error parsing JSON, source is not valid."); - }; -})(jQuery); diff --git a/js2/mwEmbed/jquery/plugins/jquery.secureEvalJSON.js b/js2/mwEmbed/jquery/plugins/jquery.secureEvalJSON.js deleted file mode 100644 index 225ca82a10..0000000000 --- a/js2/mwEmbed/jquery/plugins/jquery.secureEvalJSON.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * jQuery JSON Plugin - * version: 1.0 (2008-04-17) - * - * This document is licensed as free software under the terms of the - * MIT License: http://www.opensource.org/licenses/mit-license.php - * - * Brantley Harris technically wrote this plugin, but it is based somewhat - * on the JSON.org website's http://www.json.org/json2.js, which proclaims: - * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that - * I uphold. I really just cleaned it up. - * - * It is also based heavily on MochiKit's serializeJSON, which is - * copywrited 2005 by Bob Ippolito. - */ - -(function($) { - function toIntegersAtLease(n) - // Format integers to have at least two digits. - { - return n < 10 ? '0' + n : n; - } - - Date.prototype.toJSON = function(date) - // Yes, it polutes the Date namespace, but we'll allow it here, as - // it's damned usefull. - { - return this.getUTCFullYear() + '-' + - toIntegersAtLease(this.getUTCMonth()) + '-' + - toIntegersAtLease(this.getUTCDate()); - }; - - var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g; - var meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }; - - $.quoteString = function(string) - // Places quotes around a string, inteligently. - // If the string contains no control characters, no quote characters, and no - // backslash characters, then we can safely slap some quotes around it. - // Otherwise we must also replace the offending characters with safe escape - // sequences. - { - if (escapeable.test(string)) - { - return '"' + string.replace(escapeable, function (a) - { - var c = meta[a]; - if (typeof c === 'string') { - return c; - } - c = a.charCodeAt(); - return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); - }) + '"'; - } - return '"' + string + '"'; - }; - - $.toJSON = function(o, compact) - { - var type = typeof(o); - - if (type == "undefined") - return "undefined"; - else if (type == "number" || type == "boolean") - return o + ""; - else if (o === null) - return "null"; - - // Is it a string? - if (type == "string") - { - return $.quoteString(o); - } - - // Does it have a .toJSON function? - if (type == "object" && typeof o.toJSON == "function") - return o.toJSON(compact); - - // Is it an array? - if (type != "function" && typeof(o.length) == "number") - { - var ret = []; - for (var i = 0; i < o.length; i++) { - ret.push( $.toJSON(o[i], compact) ); - } - if (compact) - return "[" + ret.join(",") + "]"; - else - return "[" + ret.join(", ") + "]"; - } - - // If it's a function, we have to warn somebody! - if (type == "function") { - throw new TypeError("Unable to convert object of type 'function' to json."); - } - - // It's probably an object, then. - var ret = []; - for (var k in o) { - var name; - type = typeof(k); - - if (type == "number") - name = '"' + k + '"'; - else if (type == "string") - name = $.quoteString(k); - else - continue; //skip non-string or number keys - - var val = $.toJSON(o[k], compact); - if (typeof(val) != "string") { - // skip non-serializable values - continue; - } - - if (compact) - ret.push(name + ":" + val); - else - ret.push(name + ": " + val); - } - return "{" + ret.join(", ") + "}"; - }; - - $.compactJSON = function(o) - { - return $.toJSON(o, true); - }; - - $.evalJSON = function(src) - // Evals JSON that we know to be safe. - { - return eval("(" + src + ")"); - }; - - $.secureEvalJSON = function(src) - // Evals JSON in a way that is *more* secure. - { - var filtered = src; - filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@'); - filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); - filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); - - if (/^[\],:{}\s]*$/.test(filtered)) - return eval("(" + src + ")"); - else - throw new SyntaxError("Error parsing JSON, source is not valid."); - }; -})(jQuery); diff --git a/js2/mwEmbed/libAddMedia/mvBaseUploadInterface.js b/js2/mwEmbed/libAddMedia/mvBaseUploadInterface.js index 7c2bb540a1..3417e3de67 100644 --- a/js2/mwEmbed/libAddMedia/mvBaseUploadInterface.js +++ b/js2/mwEmbed/libAddMedia/mvBaseUploadInterface.js @@ -359,16 +359,11 @@ mvBaseUploadInterface.prototype = { //@@check if we are done if( data.upload['apiUploadResult'] ){ //update status to 100% - _this.updateProgress( 1 ); - if(typeof JSON == 'undefined'){ - //we need to load the jQuery json parser: (older browsers don't have JSON.parse - mvJsLoader.doLoad([ - '$j.secureEvalJSON' - ],function(){ - var apiResult = $j.secureEvalJSON( data.upload['apiUploadResult'] ); - _this.processApiResult( apiResult ); - }); - }else{ + _this.updateProgress( 1 ); + //see if we need to load JSON substitue: + mvJsLoader.doLoad( [ + 'JSON' + ],function(){ var apiResult = {}; try{ apiResult = JSON.parse ( data.upload['apiUploadResult'] ) ; @@ -377,7 +372,7 @@ mvBaseUploadInterface.prototype = { js_log('errro: could not parse apiUploadResult ') } _this.processApiResult( apiResult ); - } + }); return ; } diff --git a/js2/mwEmbed/libAddMedia/remoteSearchDriver.js b/js2/mwEmbed/libAddMedia/remoteSearchDriver.js index c1927e1204..7e1f588ca6 100644 --- a/js2/mwEmbed/libAddMedia/remoteSearchDriver.js +++ b/js2/mwEmbed/libAddMedia/remoteSearchDriver.js @@ -35,8 +35,8 @@ loadGM({ "mwe-results_from" : "Results from $2<\/a>", "mwe-missing_desc_see_source" : "This asset is missing a description. Please see the [$1 orginal source] and help describe it.", "rsd_config_error" : "Add media wizard configuration error: $1", - "mwe-your_recent_uploads" : "Your recent uploads", - "mwe-upload_a_file" : "Upload a new file", + "mwe-your_recent_uploads" : "Your recent uploads to $1", + "mwe-upload_a_file" : "Upload a new file to $1", "mwe-resource_page_desc" : "Resource page description:", "mwe-edit_resource_desc" : "Edit wiki text resource description:", "mwe-local_resource_title" : "Local resource title:", @@ -47,7 +47,7 @@ loadGM({ "mwe-importing_asset" : "Importing asset", "mwe-preview_insert_resource" : "Preview insert of resource: $1", "mwe-checking-resource": "Checking for resource", - "mwe-resource-needs-import": "Resource $1 needs to be imported", + "mwe-resource-needs-import": "Resource $1 needs to be imported to $2", "mwe-ftype-svg" : "SVG vector file", "mwe-ftype-jpg" : "JPEG image file", "mwe-ftype-png" : "PNG image file", @@ -81,7 +81,9 @@ var default_remote_search_options = { 'cFileNS':'File', //What is the canonical namespace prefix for images //@@todo (should get that from the api or in-page vars) - 'upload_api_target': 'http://localhost/wiki_trunk/api.php', // can be local or the url of the upload api. + 'upload_api_target': 'local', // can be local or the url or remote + 'upload_api_name' : null, + 'upload_api_proxy_frame': null, //a page that will request mw.proxy.server 'enabled_cps':'all', //can be keyword 'all' or an array of enabled content provider keys @@ -173,9 +175,6 @@ remoteSearchDriver.prototype = { 'local_domains': ['wikimedia','wikipedia','wikibooks'], //specific to wiki commons config: 'search_title':false, //disable title search - //set up default range limit - 'offset' : 0, - 'limit' : 30, 'tab_img':true }, 'archive_org':{ @@ -191,11 +190,24 @@ remoteSearchDriver.prototype = { 'resource_prefix': 'AO_', 'tab_img':true }, - 'metavid':{ + 'flickr':{ 'enabled':1, 'checked':1, - 'title' :'Metavid.org', - 'homepage':'http://metavid.org', + 'title' : 'flickr.com', + 'desc' : 'flickr.com, a online photo sharing site', + 'homepage':'http://www.flickr.com/about/', + + 'api_url':'http://www.flickr.com/services/rest/', + 'lib' : 'flickr', + 'local' : false, + 'resource_prefix': '', + 'tab_img':true + }, + 'metavid':{ + 'enabled' : 1, + 'checked' : 1, + 'title' : 'Metavid.org', + 'homepage':'http://metavid.org/wiki/Metavid_Overview', 'desc' : 'Metavid hosts thousands of hours of US house and senate floor proceedings', 'api_url':'http://metavid.org/w/index.php?title=Special:MvExportSearch', 'lib' : 'metavid', @@ -216,7 +228,7 @@ remoteSearchDriver.prototype = { 'upload':{ 'enabled':1, 'checked':1, - 'title' :'Upload' + 'title' :'Upload', } }, //define the licenses @@ -309,6 +321,10 @@ remoteSearchDriver.prototype = { * @param licence_url the url of the license */ getLicenceFromUrl: function( license_url ){ + //check for some pre-defined url types: + if( license_url == 'http://www.usa.gov/copyright.shtml') + return this.getLicenceFromKey('pd' , license_url); + //js_log("getLicenceFromUrl::" + license_url); //first do a direct lookup check: for(var j =0; j < this.licenses.cc.licenses.length; j++){ @@ -370,6 +386,7 @@ remoteSearchDriver.prototype = { cUpLoader : null, cEdit : null, + proxySetupDone : null, dmodalCss : {}, init: function( options ){ @@ -394,7 +411,11 @@ remoteSearchDriver.prototype = { } } } - + //set the upload target name if unset + if( _this.upload_api_target == 'local' && ! _this.upload_api_name && wgSiteName) + _this.upload_api_name = wgSiteName; + + //set up the target invocation: if( $j( this.target_invocation ).length==0 ){ js_log("RemoteSearchDriver:: no target invocation provided (will have to run your own doInitDisplay() )"); @@ -478,14 +499,13 @@ remoteSearchDriver.prototype = { init_modal:function(){ js_log("init_modal"); var _this = this; + _this.target_container = '#rsd_modal_target'; //add the parent target_container if not provided or missing if(!_this.target_container || $j(_this.target_container).length==0){ - $j('body').append('
'); - _this.target_container = '#rsd_modal_target'; + $j('body').append('
'); //js_log('appended: #rsd_modal_target' + $j(_this.target_container).attr('id')); //js_log('added target id:' + $j(_this.target_container).attr('id')); //get layout - //layout = _this.getMaxModalLayout(); js_log( 'width: ' + $j(window).width() + ' height: ' + $j(window).height()); var cConf = {}; cConf['cancel'] = function(){ @@ -508,9 +528,8 @@ remoteSearchDriver.prototype = { close: function() { //if we are 'editing' a item close that //@@todo maybe prompt the user? - _this.cancelClipEditCB(); - //$j(this).dialog('close'); - $j(this).parents('.ui-dialog').fadeOut('slow'); + _this.cancelClipEditCB(); + $j(this).parents('.ui-dialog').fadeOut('slow'); } }); doResize(); @@ -528,25 +547,7 @@ remoteSearchDriver.prototype = { 'left':'0px', 'right':'0px', 'bottom':'0px' - }); - /* - - - js_log('done setup of target_container: ' + - $j(_this.target_container +'~ .ui-dialog-buttonpane').length); - */ - - } - }, - getMaxModalLayout:function(border){ - if(!border) - border = 50; - //js_log('setting h:' + (parseInt( $j(document).height() ) - parseInt(border*2)) + ' from:' + $j(document).height() ); - return { - 'h': parseInt( $j(document).height() ) - parseInt(border*4), - 'w': parseInt( $j(document).width() ) - parseInt(border*2), - 'r': border, - 't': border + }); } }, //sets up the initial html interface @@ -623,82 +624,85 @@ remoteSearchDriver.prototype = { _this.upload_api_target = _this.local_wiki_api_url; } } + //make sure we have a url for the upload target: if( parseUri( _this.upload_api_target ).host == _this.upload_api_target ){ $j('#tab-upload').html( gM('rsd_config_error', 'bad_api_url') ); return false; } - //output the form - //set the form action based on domain: - if( parseUri( document.URL ).host == parseUri( _this.upload_api_target ).host ){ - mvJsLoader.doLoad(['$j.fn.simpleUploadForm'],function(){ - - //get extends info about the file - var cp = _this.content_providers['this_wiki']; - //check for "this_wiki" enabled - /*if(!cp.enabled){ - $j('#tab-upload').html('error this_wiki not enabled (can\'t get uploaded file info)'); - return false; - }*/ - - //load this_wiki search system to grab the rObj - _this.loadSearchLib(cp, function(){ - //do basic layout form on left upload "bin" on right - $j('#tab-upload').html('' + - '' + - '' + - '' + - '' + - '
' + - '

' + gM('mwe-upload_a_file') + '

' + - '
' + - mv_get_loading_img() + - '
' + - '
' + - '

' + gM('mwe-your_recent_uploads') + '

' + - '
' + - mv_get_loading_img() + - '
'+ - '
'); - - - //fill in the user page: - if(typeof wgUserName != 'undefined' && wgUserName){ - //load the upload bin with anything the current user has uploaded - cp.sObj.getUserRecentUploads( wgUserName, function(){ - _this.drawOutputResults(); - }); - }else{ - $j('#upload_bin_cnt').empty(); - } - - //deal with the api form upload form directly: - $j('#upload_form').simpleUploadForm({ - "api_target" : _this.upload_api_target, - "ondone_cb" : function( resultData ){ - var wTitle = resultData['filename']; - //add a loading div - _this.addResourceEditLoader(); - //@@note: we have most of what we need in resultData imageinfo - cp.sObj.addByTitle( wTitle, function( rObj ){ - //redraw (with added result if new) - _this.drawOutputResults(); - //pull up resource editor: - _this.resourceEdit( rObj, $j('#res_upload__' + rObj.id).get(0) ); - }); - //return false to close progress window: - return false; - } - }) - }); - }); - }else{ - //setup the proxy - js_log('do proxy:: ' + parseUri( _this.upload_api_target ).host); - $j('#tab-upload').html('proxy upload not yet ready'); - } + //check if we need to setup the proxy:: + if( parseUri( document.URL ).host != parseUri( _this.upload_api_target ).host ){ + //setup proxy + $j('#tab-upload').html( 'do proxy setup'); + }else{ + _this.getUploadForm(); + } },1); }, + getUploadForm:function(){ + var _this = this; + mvJsLoader.doLoad(['$j.fn.simpleUploadForm'],function(){ + //get extends info about the file + var cp = _this.content_providers['this_wiki']; + + //check for "this_wiki" enabled + /*if(!cp.enabled){ + $j('#tab-upload').html('error this_wiki not enabled (can\'t get uploaded file info)'); + return false; + }*/ + + //load this_wiki search system to grab the rObj + _this.loadSearchLib(cp, function(){ + //do basic layout form on left upload "bin" on right + $j('#tab-upload').html('' + + '' + + '' + + '' + + '' + + '
' + + '

' + gM('mwe-upload_a_file', _this.upload_api_name ) + '

' + + '
' + + mv_get_loading_img() + + '
' + + '
' + + '

' + gM('mwe-your_recent_uploads', _this.upload_api_name) + '

' + + '
' + + mv_get_loading_img() + + '
'+ + '
'); + + + //fill in the user page: + if(typeof wgUserName != 'undefined' && wgUserName){ + //load the upload bin with anything the current user has uploaded + cp.sObj.getUserRecentUploads( wgUserName, function(){ + _this.drawOutputResults(); + }); + }else{ + $j('#upload_bin_cnt').empty(); + } + + //deal with the api form upload form directly: + $j('#upload_form').simpleUploadForm({ + "api_target" : _this.upload_api_target, + "ondone_cb" : function( resultData ){ + var wTitle = resultData['filename']; + //add a loading div + _this.addResourceEditLoader(); + //@@note: we have most of what we need in resultData imageinfo + cp.sObj.addByTitle( wTitle, function( rObj ){ + //redraw (with added result if new) + _this.drawOutputResults(); + //pull up resource editor: + _this.resourceEdit( rObj, $j('#res_upload__' + rObj.id).get(0) ); + }); + //return false to close progress window: + return false; + } + }); + }); //load searchLibs + }); //load simpleUploadForm + }, runSearch: function(){ js_log("f:runSearch::" + this.disp_item); //draw_direct_flag @@ -709,9 +713,9 @@ remoteSearchDriver.prototype = { this.doUploadInteface(); return true; } - //else do runSearch - cp = this.content_providers[this.disp_item]; + //else do runSearch + var cp = this.content_providers[this.disp_item]; //check if we need to update: if( typeof cp.sObj != 'undefined' ){ @@ -737,6 +741,15 @@ remoteSearchDriver.prototype = { checkForCopyURLSupport:function ( callback ){ var _this = this; js_log('checkForCopyURLSupport:: '); + //check if the import url is diffrent from + //check if we need to setup the proxy:: + /*if( parseUri( document.URL ).host != parseUri( _this.upload_api_target ).host ){ + //setup proxy + js_log(" doc.url:" + parseUri( document.URL ).host + ' != ' +parseUri( _this.upload_api_target ).host); + $j('#tab-' + this.disp_item ).html( 'do proxy setup'); + return false; + }*/ + //see if we already have the import mode: if( this.import_url_mode != 'autodetect'){ js_log('import mode: ' + _this.import_url_mode); @@ -860,7 +873,7 @@ remoteSearchDriver.prototype = { var cp = this.content_providers[ cp_id ]; if(typeof cp['sObj'] != 'undefined'){ if( cp.sObj.loading ) - loading_done=false; + loading_done = false; } } if( loading_done ){ @@ -996,10 +1009,10 @@ remoteSearchDriver.prototype = { _this.setResultBarControl(); } - var drawResultCount =0; + var drawResultCount = 0; //output all the results for the current disp_item - if( typeof cp['sObj'] != 'undefined' ){ + if( typeof cp['sObj'] != 'undefined' ){ $j.each(cp.sObj.resultsObj, function(rInx, rItem){ if( _this.result_display_mode == 'box' ){ o+='
'); } - js_log( ' drawResultCount :: ' + drawResultCount + ' append: ' + $j('#rsd_q').val() ); + js_log( 'did drawResultCount :: ' + drawResultCount + ' append: ' + $j('#rsd_q').val() ); //remove any old search res $j('#rsd_no_search_res').remove(); @@ -1206,7 +1219,7 @@ remoteSearchDriver.prototype = { }); }, cancelClipEditCB:function(){ - var _this = this; + var _this = this; js_log('cancelClipEditCB'); var b_target = _this.target_container + '~ .ui-dialog-buttonpane'; $j('#rsd_resource_edit').remove(); @@ -1218,12 +1231,12 @@ remoteSearchDriver.prototype = { //restore the title: $j( _this.target_container ).dialog( 'option', 'title', gM('mwe-add_media_wizard')); js_log("should update: " + b_target + ' with: cancel'); - //restore the buttons: + //restore the buttons: $j(b_target).html( $j.btnHtml( 'Cancel' , 'mv_cancel_rsd', 'close')) .children('.mv_cancel_rsd') .btnBind() .click(function(){ - $j( _this.target_container).dialog('close'); + $j( _this.target_container).dialog('close'); }) }, @@ -1253,16 +1266,17 @@ remoteSearchDriver.prototype = { js_log('remoteSearchDriver::doMediaEdit: ' + mediaType); var mvClipInit = { - 'rObj':rObj, //the resource object - 'parent_ct' : 'rsd_modal_target', - 'clip_disp_ct' : 'clip_edit_disp', - 'control_ct' : 'clip_edit_ctrl', - 'media_type' : mediaType, - 'p_rsdObj' : _this, - 'controlActionsCb' : _this.getClipEditControlActions( cp ) + 'rObj' : rObj, //the resource object + 'parent_ct' : 'rsd_modal_target', + 'clip_disp_ct' : 'clip_edit_disp', + 'control_ct' : 'clip_edit_ctrl', + 'media_type' : mediaType, + 'p_rsdObj' : _this, + 'controlActionsCb' : _this.getClipEditControlActions( cp ) }; - + //set the base clip edit lib class req set: var clibs = ['mvClipEdit']; + if( mediaType == 'image'){ //display the mvClipEdit obj once we are done loading: mvJsLoader.doLoad( clibs, function(){ @@ -1432,7 +1446,7 @@ remoteSearchDriver.prototype = { $j( _this.target_container ).append('
' + - '

' + gM('mwe-resource-needs-import', rObj.title) + '

' + + '

' + gM('mwe-resource-needs-import', [rObj.title, _this.upload_api_name] ) + '

' + '
' + rObj.pSobj.getEmbedHTML( rObj, { 'id': _this.target_container + '_rsd_pv_vid', @@ -1468,7 +1482,8 @@ remoteSearchDriver.prototype = { //add hover: //update video tag (if a video) - rewrite_by_id( _this.target_container + '_rsd_pv_vid'); + if( rObj.mime.indexOf('video/') !== -1 ) + rewrite_by_id( $j(_this.target_container).attr('id') + '_rsd_pv_vid'); //load the preview text: _this.getParsedWikiText( wt, _this.cFileNS +':'+ rObj.target_resource_title, function( o ){ @@ -1487,7 +1502,14 @@ remoteSearchDriver.prototype = { js_log("do import asset:" + _this.import_url_mode); //check import mode: if( _this.import_url_mode == 'api' ){ - _this.doImportAPI( rObj , callback); + if( parseUri( document.URL ).host != parseUri( _this.upload_api_target ).host ){ + _this.setupProxy( function(){ + debugger; + //_this.doImportAPI( rObj , callback); + }); + }else{ + _this.doImportAPI( rObj , callback); + } }else{ js_log("Error: import mode is not form or API (can not copy asset)"); } @@ -1497,7 +1519,33 @@ remoteSearchDriver.prototype = { $j(this).remove(); }); }); - }, + }, + /** + * sets up the proxy for the remote inserts + */ + setupProxy:function(callback){ + var _this = this; + + if( _this.proxySetupDone ){ + if(callback) callback(); + return ; + } + //setup the the proxy via mv_embed $j.apiProxy loader: + if( ! _this.upload_api_proxy_frame ){ + js_log("Error:: remote api but no proxy frame target"); + return false; + }else{ + $j.apiProxy( + 'client', + { + 'server_frame' : _this.upload_api_proxy_frame, + },function(){ + //now that the api is setup call actual import + debugger; + } + ); + } + }, checkForFile:function( fName, callback){ js_log("checkForFile::"); var _this = this; @@ -1559,7 +1607,7 @@ remoteSearchDriver.prototype = { //close the loader now that we are ready to present the progress dialog:: $j.closeLoaderDialog(); - + myUp.doHttpUpload({ 'url' : rObj.src, 'filename' : rObj.target_resource_title, @@ -1755,7 +1803,7 @@ remoteSearchDriver.prototype = { var cp = _this.content_providers['this_wiki']; }else{ var cp = this.content_providers[ this.disp_item ]; - } + } //js_log('getPaging:'+ cp_id + ' len: ' + cp.sObj.num_results); var to_num = ( cp.limit > cp.sObj.num_results )? (cp.offset + cp.sObj.num_results): diff --git a/js2/mwEmbed/libAddMedia/searchLibs/archiveOrgSearch.js b/js2/mwEmbed/libAddMedia/searchLibs/archiveOrgSearch.js index 02bbaa745d..2285d16b23 100644 --- a/js2/mwEmbed/libAddMedia/searchLibs/archiveOrgSearch.js +++ b/js2/mwEmbed/libAddMedia/searchLibs/archiveOrgSearch.js @@ -23,23 +23,21 @@ archiveOrgSearch.prototype = { }, getSearchResults:function(){ //call parent: - this.parent_getSearchResults(); - - var _this = this; - this.loading=true; + this.parent_getSearchResults(); + var _this = this; js_log('f:getSearchResults for:' + $j('#rsd_q').val() ); //build the query var var q = $j('#rsd_q').val(); //@@todo check advanced options: include audio and images media types - //for now force (Ogg video) & a creativecommons license + //for now force (Ogg video) & url based license q+=' format:(Ogg video)'; q+=' licenseurl:(http\\:\\/\\/*)'; var reqObj = { 'q': q, //just search for video atm 'fl':"description,title,identifier,licenseurl,format,license,thumbnail", 'wt':'json', - 'rows':'30', - 'indent':'yes' + 'rows' : this.cp.limit, + 'start' : this.cp.offset } do_api_req( { 'data':reqObj, @@ -77,15 +75,7 @@ archiveOrgSearch.prototype = { 'pSobj' :_this }; - this.resultsObj[ resource_id ] = rObj; - - //likely a audio clip if no poster and type application/ogg - //@@todo we should return audio/ogg for the mime type or some other way to specify its "audio" - - //this.num_results++; - //for(var i in this.resultsObj[page_id]){ - // js_log('added: '+ i +' '+ this.resultsObj[page_id][i]); - //} + this.resultsObj[ resource_id ] = rObj; } } }, diff --git a/js2/mwEmbed/libAddMedia/searchLibs/baseRemoteSearch.js b/js2/mwEmbed/libAddMedia/searchLibs/baseRemoteSearch.js index 687649f26b..9fb234aa4d 100644 --- a/js2/mwEmbed/libAddMedia/searchLibs/baseRemoteSearch.js +++ b/js2/mwEmbed/libAddMedia/searchLibs/baseRemoteSearch.js @@ -35,9 +35,9 @@ baseRemoteSearch.prototype = { //default search result values for paging: offset :0, - limit :20, - more_results :false, - num_results :0, + limit : 30, + more_results : false, + num_results : 0, //init the object: init: function( iObj ){ @@ -53,8 +53,9 @@ baseRemoteSearch.prototype = { //do global getSearchResults bindings this.last_query = $j('#rsd_q').val(); this.last_offset = this.cp.offset; - //@@todo its possible that video rss is the "default" format we could put that logic here: - }, + //set the loading flag: + this.loading=true; + }, /* * Parses and adds video rss based input format * @param $data XML data to parse @@ -146,6 +147,37 @@ baseRemoteSearch.prototype = { _this.num_results++; }); }, + getEmbedHTML: function( rObj , options) { + if(!options) + options = {}; + //set up the output var with the default values: + var outOpt = { 'width': rObj.width, 'height': rObj.height}; + if( options['max_height'] ){ + outOpt.height = (options.max_height > rObj.height) ? rObj.height : options.max_height; + outOpt.width = (rObj.width / rObj.height) *outOpt.height; + } + options.style_attr = 'style="width:' + outOpt.width + 'px;height:' + outOpt.height +'px"'; + options.id_attr = (options['id'])?' id = "' + options['id'] +'" ': ''; + + if( rObj.mime.indexOf('image') != -1 ){ + return this.getImageEmbedHTML( rObj, options ); + }else{ + js_log("ERROR:: no embed code for mime type: " + rObj.mime); + return ' Error missing embed code '; + } + }, + getImageEmbedHTML:function( rObj, options ) { + //if crop is null do base output: + var imgHtml = ''; + if( rObj.crop == null) + return imgHtml + //else do crop output: + return '
' + + '
'+ + imgHtml + + '
'+ + '
'; + }, //by default just return the existing image with callback getImageObj:function( rObj, size, callback){ callback( {'url':rObj.poster} ); diff --git a/js2/mwEmbed/libAddMedia/searchLibs/flickrSearch.js b/js2/mwEmbed/libAddMedia/searchLibs/flickrSearch.js index 1b9c3ab88c..8a137dedf2 100644 --- a/js2/mwEmbed/libAddMedia/searchLibs/flickrSearch.js +++ b/js2/mwEmbed/libAddMedia/searchLibs/flickrSearch.js @@ -1,7 +1,36 @@ -var flickrOrgSearch = function ( iObj){ +/* +* basic flickr search uses flickr jsonp api +* http://www.flickr.com/services/api/ +* +* uses the "example api_key" 519b66e3fd8d8080e27a64fe51101e2c +* should update with a difrent "public" key sometime soon +http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=519b66e3fd8d8080e27a64fe51101e2c +* +* we look for licenses from method=flickr.photos.licenses.getInfo +* per http://commons.wikimedia.org/wiki/Special:Upload?uselang=fromflickr +* we are interested in: + (4) Attribution License + (5) Attribution-ShareAlike License, + (7) No known copyright restrictions, + (8) United States Government Work +*/ + +var flickrSearch = function ( iObj){ return this.init( iObj ); } -flickrOrgSearch.prototype = { +flickrSearch.prototype = { + dtUrl : 'http://www.flickr.com/photos/', + //@@todo probably would be good to read the api-key from configuration + apikey : '2867787a545cc66c0bce6f2e57aca1d1', + //what licence we are interested in + _licence_keys: '4,5,7,8', + _srctypes: ['t','sq','s','m','o'], + licenceMap:{ + '4' : 'http://creativecommons.org/licenses+/by/3.0/', + '5' : 'http://creativecommons.org/licenses/by-sa/3.0/', + '7' : 'http://www.flickr.com/commons/usage/', + '8' : 'http://www.usa.gov/copyright.shtml' + }, init:function( iObj ){ //init base class and inherit: var baseSearch = new baseRemoteSearch( iObj ); @@ -14,9 +43,111 @@ flickrOrgSearch.prototype = { } //inherit the cp settings for }, - getSearchResults:function(){ - //call parent: + getSearchResults:function(){ + var _this = this; + js_log("flickr::getSearchResults"); + //call parent (sets loading sate and other setup stuff) this.parent_getSearchResults(); //setup the flickr request: + var reqObj = { + 'method':'flickr.photos.search', + 'format':'json', + 'license':this._licence_keys, + 'api_key':this.apikey, + 'per_page': this.cp.limit, + 'page' : this.cp.offset, + 'text': $j('#rsd_q').val(), + 'extras' : 'license, date_upload, date_taken, owner_name, icon_server, original_format, last_update, geo, tags, machine_tags, o_dims, views, media, path_alias, url_sq, url_t, url_s, url_m, url_o' + } + do_api_req( { + 'data': reqObj, + 'url':this.cp.api_url, + 'jsonCB':'jsoncallback', + }, function( data ){ + _this.addResults( data); + _this.loading = false; + }); + }, + addResults:function( data ){ + var _this = this; + if(data.photos && data.photos.photo){ + //set result info: + this.num_results = data.photos.total; + for(var resource_id in data.photos.photo){ + var resource = data.photos.photo[resource_id]; + + var rObj = { + 'titleKey' : resource.title + '.jpg', + 'resourceKey': resource.id, + 'link' : _this.dtUrl + resource.pathalias + '/'+ resource.id, + 'title' : resource.title, + 'thumbwidth' : resource.width_t, + 'thumbheight': resource.height_t, + 'desc' : resource.title, + //set the license: (rsd is a pointer to the parent remoteSearchDriver ) + 'license' : this.rsd.getLicenceFromUrl( _this.licenceMap[ resource.license ] ), + 'pSobj' : _this, + //assume image/jpeg for "photos" + 'mime' : 'image/jpeg' + }; + //set all the provided srcs: + rObj['srcSet'] = {}; + for( var i in _this._srctypes){ + var st = _this._srctypes[i]; + //if resource has a url add it to the srcSet: + if( resource['url_' + st] ){ + rObj['srcSet'][st] = { + 'h': resource['height_' + st], + 'w': resource['width_' + st], + 'src': resource['url_' + st] + } + //set src to the largest + rObj['src'] = resource['url_' + st]; + } + } + + _this.resultsObj[ resource_id ] = rObj; + } + } + }, + //return image transform via callback + getImageObj:function( rObj, size, callback){ + if( size.width ){ + var skey = this.getSrcTypeKey(rObj, size.width ) + callback ({ + 'url' : rObj.srcSet[ skey ].src, + 'width' : rObj.srcSet[ skey ].w, + 'height' : rObj.srcSet[ skey ].h + }); + } + }, + getImageTransform:function(rObj, opt){ + if( opt.width ){ + return rObj.srcSet[ this.getSrcTypeKey(rObj, opt.width) ].src; + } + }, + getSrcTypeKey:function(rObj, width ){ + if( width <= 75 ){ + if( rObj.srcSet['sq'] ) + return 'sq'; + }else if( width <= 100 ){ + if( rObj.srcSet['t'] ) + return 't'; + }else if( width <= 240 ){ + if( rObj.srcSet['s'] ) + return 's'; + }else if( width <= 500 ){ + if( rObj.srcSet['m'] ) + return 'm'; + }else{ + if( rObj.srcSet['o'] ) + return 'o'; + } + //original was missing return medium or small + if( rObj.srcSet['m'] ) + return 'm'; + if( rObj.srcSet['s'] ) + return 's'; + } } \ No newline at end of file diff --git a/js2/mwEmbed/libAddMedia/searchLibs/mediaWikiSearch.js b/js2/mwEmbed/libAddMedia/searchLibs/mediaWikiSearch.js index 489dbea308..d7be4f6ded 100644 --- a/js2/mwEmbed/libAddMedia/searchLibs/mediaWikiSearch.js +++ b/js2/mwEmbed/libAddMedia/searchLibs/mediaWikiSearch.js @@ -88,9 +88,9 @@ mediaWikiSearch.prototype = { getSearchResults:function(){ //call parent: this.parent_getSearchResults(); - + //set local ref: var _this = this; - this.loading = true; + js_log('f:getSearchResults for:' + $j('#rsd_q').val() ); //do two queries against the Image / File / MVD namespace: @@ -207,7 +207,7 @@ mediaWikiSearch.prototype = { } } } - + //likely a audio clip if no poster and type application/ogg //@@todo we should return audio/ogg for the mime type or some other way to specify its "audio" if( ! rObj.poster && rObj.mime == 'application/ogg' ){ @@ -307,36 +307,20 @@ mediaWikiSearch.prototype = { if(!cEdit) var cEdit = _this.cEdit; }, - getEmbedHTML: function( rObj , options) { + getEmbedHTML: function( rObj , options) { if(!options) - options = {}; - //set up the output var with the default values: - var outOpt = { 'width': rObj.width, 'height': rObj.height}; - if( options['max_height'] ){ - outOpt.height = (options.max_height > rObj.height) ? rObj.height : options.max_height; - outOpt.width = (rObj.width / rObj.height) *outOpt.height; - } - var style_attr = 'style="width:' + outOpt.width + 'px;height:' + outOpt.height +'px"'; - var id_attr = (options['id'])?' id = "' + options['id'] +'" ': ''; - var cat = rObj; - //return the html type: - if(rObj.mime.indexOf('image')!=-1){ - //if crop is null do base output: - var imgHtml = ''; - if( rObj.crop == null) - return imgHtml - //else do crop output: - return '
' + - '
'+ - imgHtml + - '
'+ - '
'; + options = {}; + this.parent_getEmbedHTML( rObj, options); + //check for image output: + if( rObj.mime.indexOf('image') != -1 ){ + return this.getImageEmbedHTML( rObj, options ); } + //for video and audio output: var ahtml=''; - if(rObj.mime == 'application/ogg' || rObj.mime == 'audio/ogg'){ - ahtml = id_attr + + if( rObj.mime == 'application/ogg' || rObj.mime == 'audio/ogg' ){ + ahtml = options.id_attr + ' src="' + rObj.src + '" ' + - style_attr + + options.style_attr + ' poster="'+ rObj.poster + '" ' if(rObj.mime.indexOf('application/ogg')!=-1){ return ''; @@ -346,13 +330,17 @@ mediaWikiSearch.prototype = { return ''; } } - js_log('ERROR:unsupored mime type: ' + rObj.mime); + js_log( 'ERROR:unsupored mime type: ' + rObj.mime ); }, getInlineDescWiki:function( rObj ){ var desc = this.parent_getInlineDescWiki( rObj ); + + //strip categories for inline Desc: (should strip license tags too but not as easy) + desc = desc.replace(/\[\[Category\:[^\]]*\]\]/, ''); + //just grab the description tag for inline desc: var descMatch = new RegExp(/Description=(\{\{en\|)?([^|]*|)/); - var dparts = desc.match(descMatch); + var dparts = desc.match(descMatch); if( dparts && dparts.length > 1){ desc = (dparts.length == 2) ? dparts[1] : dparts[2].replace('}}',''); @@ -363,9 +351,6 @@ mediaWikiSearch.prototype = { js_log('Error: No Description Tag, Using::' + desc ); return desc; }, - parseWikiTemplate: function( text ){ - //@@todo parse wiki Template return object with properties and values - }, //returns the inline wikitext for insertion (template based crops for now) getEmbedWikiCode: function( rObj ){ //set default layout to right justified diff --git a/js2/mwEmbed/libAddMedia/searchLibs/metavidSearch.js b/js2/mwEmbed/libAddMedia/searchLibs/metavidSearch.js index 43862e92df..221ace969c 100644 --- a/js2/mwEmbed/libAddMedia/searchLibs/metavidSearch.js +++ b/js2/mwEmbed/libAddMedia/searchLibs/metavidSearch.js @@ -26,10 +26,8 @@ metavidSearch.prototype = { getSearchResults:function(){ //call parent: this.parent_getSearchResults(); - - var _this = this; - //start loading: - _this.loading= 1; + //set local ref: + var _this = this; js_log('metavidSearch::getSearchResults()'); //proccess all options var url = this.cp.api_url; diff --git a/js2/mwEmbed/libAddMedia/seqRemoteSearchDriver.js b/js2/mwEmbed/libAddMedia/seqRemoteSearchDriver.js deleted file mode 100644 index 43496f31c7..0000000000 --- a/js2/mwEmbed/libAddMedia/seqRemoteSearchDriver.js +++ /dev/null @@ -1,181 +0,0 @@ -/*the sequence remote search driver - extends the base remote search driver with sequence specific stuff -*/ - -var seqRemoteSearchDriver = function(iObj){ - return this.init( iObj ) -} -seqRemoteSearchDriver.prototype = { - sequence_add_target:false, - init:function( this_seq ){ - var _this = this; - js_log("init:seqRemoteSearchDriver"); - //setup remote search driver with a seq parent: - this.pSeq = this_seq; - var iObj = { - 'target_container' : '#cliplib_ic', - 'local_wiki_api_url': this_seq.getLocalApiUrl(), - 'instance_name' : this_seq.instance_name + '.mySearch', - 'default_query' : this.pSeq.plObj.title - } - if(typeof this_seq.amw_conf != 'undefined') - $j.extend(iObj, this_seq.amw_conf); - //inherit the remoteSearchDriver properties:n - var tmpRSD = new remoteSearchDriver( iObj ); - for(var i in tmpRSD){ - if(this[i]){ - this['parent_'+i] = tmpRSD[i]; - }else{ - this[i] = tmpRSD[i]; - } - } - //extend parent_do_refresh_timeline actions: - if(!this.pSeq.parent_do_refresh_timeline){ - this.pSeq.parent_do_refresh_timeline = this.pSeq.do_refresh_timeline; - this.pSeq.do_refresh_timeline = function(){ - js_log("seqRemoteSearchDriver::" + _this.pSeq.disp_menu_item); - //call the parent - _this.pSeq.parent_do_refresh_timeline(); - //add our local bindings - _this.addResultBindings(); - return true; - } - } - }, - resourceEdit:function(){ - var _this = this; - - }, - addResultBindings:function(){ - //set up seq: - var _this = this; - //setup parent bindings: - this.parent_addResultBindings(); - - //add an aditional click binding - $j('.rsd_res_item').click(function(){ - js_log('SeqRemoteSearch: rsd_res_item: click (remove sequence_add_target)'); - _this.sequence_add_target =false; - }); - - //add an additional drag binding - $j( '.rsd_res_item' ).draggable('destroy').draggable({ - helper:function(){ - return $j( this ).clone().appendTo('body').css({'z-index':9999}).get(0); - }, - revert:'invalid', - start:function(){ - js_log('start drag'); - } - }); - $j(".mv_clip_drag").droppable( 'destroy' ).droppable({ - accept: '.rsd_res_item', - over:function(event, ui){ - //js_log("over : mv_clip_drag: " + $j(this).attr('id') ); - $j(this).css('border-right', 'solid thick red'); - }, - out:function(event, ui){ - $j(this).css('border-right', 'solid thin white'); - }, - drop: function(event, ui) { - $j(this).css('border-right', 'solid thin white'); - js_log("Droped: "+ $j(ui.draggable).attr('id') +' on ' + $j(this).attr('id') ); - _this.sequence_add_target = $j(this).attr('id'); - //load the orginal draged item - var rObj = _this.getResourceFromId( $j(ui.draggable).attr('id') ); - _this.resourceEdit(rObj, ui.draggable); - } - }); - - }, - insertResource:function(rObj){ - var _this = this; - js_log("SEQ insert resource after:" + _this.sequence_add_target + ' of type: ' + rObj.mime); - if(_this.sequence_add_target ){ - var tClip = _this.pSeq.getClipFromSeqID( _this.sequence_add_target ); - var target_order = false; - if(tClip) - var target_order = tClip.order; - } - //@@todo show wating of sorts. - - //get target order: - var cat = rObj; - //check for target insert path - this.checkImportResource( rObj, function(){ - - var clipConfig = { - 'type' : rObj.mime, - 'uri' : _this.cFileNS + ':' + rObj.target_resource_title, - 'title' : rObj.title - }; - //set via local properites if avaliable - clipConfig['src'] = (rObj.local_src) ? rObj.local_src : rObj.src; - clipConfig['poster'] = ( rObj.local_poster ) ? rObj.local_poster : rObj.poster; - - if(rObj.start_time && rObj.end_time){ - clipConfig['dur'] = npt2seconds( rObj.end_time ) - npt2seconds( rObj.start_time ); - }else{ - //provide a default duration if none set - clipConfig['dur'] = 4; - } - - //create the media element (target order+1 (since we insert (after) - _this.pSeq.plObj.tryAddMediaObj( clipConfig, (parseInt(target_order) + 1) ); - //refresh the timeline: - _this.pSeq.do_refresh_timeline(); - js_log("run close all: "); - _this.closeAll(); - }); - }, - getClipEditControlActions:function(){ - var _this = this; - return { - 'insert_seq':function(rObj){ - _this.insertResource( rObj ) - }, - 'cancel':function(rObj){ - _this.cancelClipEditCB( rObj ) - } - }; - }, - resourceEdit:function(rObj, rsdElement){ - var _this = this; - //don't resize to default (full screen behavior) - _this.dmodalCss = {}; - //open up a new target_contaienr: - if($j('#seq_resource_import').length == 0) - $j('body').append('
'); - - $j('#seq_resource_import').dialog('destroy').dialog({ - bgiframe: true, - width:750, - height:480, - modal: true, - buttons: { - "Cancel": function() { - $j(this).dialog("close"); - } - } - }); - _this.target_container = '#seq_resource_import'; - //do parent resource edit (with updated target) - this.parent_resourceEdit(rObj, rsdElement); - }, - closeAll:function(){ - js_log( 'should close: seq_resource_import'); - $j('#seq_resource_import').dialog('close').dialog('destroy').remove(); - this.parent_closeAll(); - }, - getEditToken:function(callback){ - if(this.pSeq.sequenceEditToken){ - callback( this.pSeq.sequenceEditToken ) - }else{ - this.parent_getEditToken(callback); - } - }, - cancelClipEditCB:function(){ - js_log('seqRSD:cancelClipEditCB'); - $j('#seq_resource_import').dialog('close').dialog('destroy').remove(); - } -}; diff --git a/js2/mwEmbed/libEmbedVideo/nativeEmbed.js b/js2/mwEmbed/libEmbedVideo/nativeEmbed.js index 7df81e6e5c..4784ff95d9 100644 --- a/js2/mwEmbed/libEmbedVideo/nativeEmbed.js +++ b/js2/mwEmbed/libEmbedVideo/nativeEmbed.js @@ -37,13 +37,12 @@ var nativeEmbed = { //continue with the other attr: eb+= 'oncanplaythrough="$j(\'#'+this.id+'\').get(0).oncanplaythrough();return false;" ' + - 'onloadedmetadata="$j(\'#'+this.id+'\').get(0).onloadedmetadata();return false;" ' + - 'loadedmetadata="$j(\'#'+this.id+'\').get(0).onloadedmetadata();return false;" ' + - 'onprogress="$j(\'#'+this.id+'\').get(0).onprogress( event );return false;" '+ - 'onended="$j(\'#'+this.id+'\').get(0).onended();return false;" '+ - 'onseeking="$j(\'#'+this.id+'\').get(0).onseeking();" ' + - 'onseeked="$j(\'#'+this.id+'\').get(0).onseeked();" ' + - '>' + + 'onloadedmetadata="$j(\'#'+this.id+'\').get(0).onloadedmetadata();return false;" ' + + 'loadedmetadata="$j(\'#'+this.id+'\').get(0).onloadedmetadata();return false;" ' + + 'onprogress="$j(\'#'+this.id+'\').get(0).onprogress( event );return false;" '+ + 'onended="$j(\'#'+this.id+'\').get(0).onended();return false;" '+ + 'onseeking="$j(\'#'+this.id+'\').get(0).onseeking();" ' + + 'onseeked="$j(\'#'+this.id+'\').get(0).onseeked();" >' + ''; return eb; }, diff --git a/js2/mwEmbed/libMwApi/NestedCallbackIframe.html b/js2/mwEmbed/libMwApi/NestedCallbackIframe.html new file mode 100644 index 0000000000..c8964461b7 --- /dev/null +++ b/js2/mwEmbed/libMwApi/NestedCallbackIframe.html @@ -0,0 +1,16 @@ + + + + +Simple nested iframe callback page + + + +Nested Iframe callback + + \ No newline at end of file diff --git a/js2/mwEmbed/libMwApi/json2.js b/js2/mwEmbed/libMwApi/json2.js new file mode 100644 index 0000000000..c422a6e19c --- /dev/null +++ b/js2/mwEmbed/libMwApi/json2.js @@ -0,0 +1,479 @@ +/* + http://www.JSON.org/json2.js + 2009-09-29 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. +*/ + +/*jslint evil: true, strict: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (!this.JSON) { + this.JSON = {}; +} + +(function () { + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); diff --git a/js2/mwEmbed/libMwApi/mw.proxy.js b/js2/mwEmbed/libMwApi/mw.proxy.js new file mode 100644 index 0000000000..86f673e6c2 --- /dev/null +++ b/js2/mwEmbed/libMwApi/mw.proxy.js @@ -0,0 +1,210 @@ +/* +* +* Api proxy system +* +* Built to support cross domain uploading, and api actions for a approved set of domains. +* mwProxy enables a system for fluid contributions across wikis domains for which your logged in. +* +* The framework support a request approval system for per-user domain approval +* and a central blacklisting of domains controlled by site or system administrators. +* +* Flow outline below: +* +* Domain A (lets say en.wiki) +* invokes add-media-wizard and wants to upload to domain B ( commons.wiki ) +* +* Domain A loads iframe to domain B ? with request param to to insert from Domain A +* Domain B checks list of approved domains for (Domain A) & checks the user is logged in ( and if the same account name ) +* if user is not logged in +* a _link_ to Domain B to new window login page is given +* if user has not approved domain and (Domain A) is not pre-approved +* a new page link is generated with a approve domain request +* if user approves domain it goes into their User:{username}/apiProxyDomains.js config +* If Domain A is approved we then: +* loads a "push" and "pull" iframe back to Domain A + (Domain B can change the #hash values of these children thereby proxy the data) +* Domain A now gets the iframe "loaded" callback a does a initial echo to confirm cross domain proxy +* echo sends "echo" to push and (Domain A) js passes the echo onto the "pull" +* Domain A now sends api requests to the iframe "push" child and gets results from the iframe "pull" +* api actions happen with status updates and everything and we can reuse existing api interface code +* +* if the browser supports it we can pass msgs with the postMessage API +* http://ejohn.org/blog/cross-window-messaging/ +* +* @@todo it would be nice if this supported multiple proxy targets (ie to a bright widgets future) +* +*/ + +loadGM({ + "mwe-setting-up-proxy": "Setting up proxy" +}); + +(function( $ ) { + /** + * Base API Proxy object + * + */ + $.proxy = {}; + + /** + * The clientApiProxy handles a request to external server + * + * This is (Domain A) in the above described setup + */ + $.proxy.client = function( pConf, callback ){ + var _this = this; + //do setup: + if( pConf.server_frame ) + $.proxy.server_frame = pConf.server_frame; + + if( pConf.client_frame_path ) + $.proxy.client_frame_path = pConf.client_frame_path; + + //setup a dialog to manage proxy setup: + $j.addLoaderDialog( gM('mwe-setting-up-proxy') ); + + if( parseUri( $.proxy.server_frame).host == parseUri( document.URL ).host ){ + js_log("Error: why are you trying to proxy yourself? " ); + return false; + } + //add an iframe to domain B with request for proxy just do the setup + $.proxy.doFrameProxy( { 'init' : 'echo' } ); + + //if we have a setup callback + $.proxy.callback = callback; + } + /* setup a iframe request hash */ + $.proxy.doFrameProxy = function( reqObj ){ + var hashPack = { + 'cd': parseUri( document.URL ).host, + 'cfp': $.proxy.client_frame_path, + 'req': reqObj + } + js_log( "Do frame proxy request on src: \n" + $.proxy.server_frame + "\n" + + JSON.stringify( reqObj ) ); + //we can't update src's so we have to remove and add all the time :( + //@@todo we should support frame msg system + $j('#frame_proxy').remove(); + $j('body').append('' ); + } + + /* the do_api_request with callback: */ + $.proxy.doRequest = function( reqObj, callback){ + js_log("doRequest:: " + JSON.stringify( reqObj ) ); + //setup the callback: + $.proxy.callback = callback; + //do the proxy req: + $.proxy.doFrameProxy( reqObj ); + } + /** + * The nested iframe action that passes its msg back up to the top instance + */ + $.proxy.nested = function( hashResult ){ + js_log( 'got $.proxy.nested callback!! :: ' + hashResult ); + //try to parse the hash result: + try{ + var rObj = JSON.parse( unescape( hashResult ) ); + }catch (e) { + js_log("Error could not parse hashResult"); + } + //hide the loader if the initial state = ready msg is fired: + if( rObj && rObj.state == 'ready') + $j.closeLoaderDialog(); + //if all good pass it to the callback: + $.proxy.callback( rObj ); + } + /** + * The serverApiProxy handles the actual proxy + * and child frames pointing to the parent "blank" frames + * + * This is (Domain B) in the above described setup + */ + $.proxy.server = function( pConf, callback){ + /* clear the body of any html */ + $j('body').html( 'proxy setup' ); + + //read the anchor action from the requesting url + var jmsg = unescape( parseUri( document.URL ).anchor ); + try{ + var aObj = JSON.parse( jmsg ); + }catch ( e ){ + js_log("ProxyServer:: could not parse anchor"); + } + if( !aObj.cd ){ + js_log("Error: no client domain provided "); + return false; + } + + js_log("Setup server on: " + parseUri( document.URL ).host + + ' client from: ' + aObj.cd + + ' to nested target: ' + aObj.cfp ); + + // make sure we are logged in + // (its a normal mediaWiki page so all site vars should be defined) + if( !wgUserName ){ + js_log('error Not logged in'); + return false; + } + + var domain = parseUri( document.URL ).host; + var nested_frame_src = 'http://' + aObj.cd + aObj.cfp; + //check the master whitelist + for(var i in pConf.master_whitelist){ + if( domain == pConf.master_whitelist[ i ] ){ + //do the request: + return doNestedProxy( aObj.req ); + } + } + //check master blacklist + for(var i in pConf.master_blacklist ){ + if( domain == pConf.master_blacklist ){ + js_log('domain: ' + domain + ' is blacklisted'); + return false; + } + } + //@@todo grab the users whitelist for our current domain + /*var local_api = wgScriptPath + '/index' + wgScriptExtension + '?title=' + + 'User:' + wgUserName + '/apiProxyDomainList.js' + + '&action=raw&smaxage=0&gen=js'; + $j.get( local_api, function( data ){ + debugger; + });*/ + + //if still not found: + js_log("domain " + domain + " not approved"); + + function doNestedProxy( reqObj ){ + js_log("doNestedProxy to: " + nested_frame_src); + var outputhash = escape( JSON.stringify( reqObj ) ); + //check if its just a "echo" request (no need to hit the api) + if( reqObj.init && reqObj.init == 'echo'){ + doNestedFrame( {'state': 'ready', "echo": true } ); + }else{ + //add some api stuff: + reqObj['format'] = 'json'; + //process the api request + $j.get(wgScriptPath + '/api' + wgScriptExtension, + reqObj, + function( data ){ + js_log("Proxy GOT Res: " + data ); + //put it into the nested frame hash string: + doNestedFrame( JSON.parse( data ) ); + } + ); + } + } + //add the doNestedFrame iframe: + function doNestedFrame( resultObj ){ + $j('#nested_push').remove(); + //setup the nested proxy that points back to top domain: + $j('body').append( '' ); + } + } + +})(window.$mw); diff --git a/js2/mwEmbed/libMwApi/mwProxy.js b/js2/mwEmbed/libMwApi/mwProxy.js new file mode 100644 index 0000000000..8a4792165e --- /dev/null +++ b/js2/mwEmbed/libMwApi/mwProxy.js @@ -0,0 +1,17 @@ + +var mwApiClientProxy = function(iObj){ + return this.init( iObj ); +} +mwApiClientProxy.prototype = { + init:function( iObj ){ + + }, + //Gets the apiResult callback + mwApiProxy:resultCb( apiResult ){ + + } +} + +var mwApiServerProxy = function(iObj){ + return this.init( +} \ No newline at end of file diff --git a/js2/mwEmbed/mv_embed.js b/js2/mwEmbed/mv_embed.js index 86bd200cf8..57aaf06ef5 100644 --- a/js2/mwEmbed/mv_embed.js +++ b/js2/mwEmbed/mv_embed.js @@ -91,7 +91,7 @@ if( !mv_embed_path ) { var mv_embed_path = getMvEmbedPath(); } /** -* wrap the global $mw object here: +* The global $mw object: * * Any global functions/classes that are not jQuery plugins should make * there way into the $mw namespace @@ -382,8 +382,9 @@ if( !mv_embed_path ) { //@@todo we need a formatNum and we need to request some special packaged info to deal with that case. return gM( msg , size ); }; - - + + + /** * MediaWiki wikitext "Parser" * @@ -429,36 +430,15 @@ if( !mv_embed_path ) { this.pOut = ''; }, parse : function(){ - this.pObj = {}; - this.pObj.tmpl = new Array(); - - //refrences for swap key - this.pObj.tmpl_text = new Array(); - this.pObj.tmpl_key = new Array(); - this.pObj.tmpl_ns = '' ; // wikiText with place-holder - - //get templates losly based on Magnus_Manske/tmpl.js code: - var tcnt = 0 ; - var ts = '' ; - var curt = 0 ; - var schar = 0; - - - //build out nested template holders: - var depth = 0; - var tKey = 0; - var ns = ''; - /* - * quickly recursive / parse out templates with top down recurse decent + * quickly recursive / parse out templates: */ // ~ probably a better algorithm out there / should mirror php parser flow ~ // ... but I am having fun with recursion so here it is... - function rdpp ( txt ){ + // or at least mirror: http://www.mediawiki.org/wiki/Extension:Page_Object_Model + function rdpp ( txt , cn){ var node = {}; - //if we should output node text - var ont = true; //inspect each char for(var a=0; a < txt.length; a++){ if( txt[a] == '{' && txt[a+1] == '{' ){ @@ -467,21 +447,20 @@ if( !mv_embed_path ) { if(!node['c']) node['c'] = new Array(); - node['c'].push( rdpp( txt.substr( a ) ) ); - ont=true; + node['c'].push( rdpp( txt.substr( a ), true ) ); }else if( txt[a] == '}' && txt[a+1] == '}'){ if( !node['p'] ){ return node; } node = node['p']; - ont=false; a=a+2; } if(!node['t']) node['t']=''; - + if( txt[a] ) node['t']+=txt[a]; + } return node; } @@ -503,8 +482,8 @@ if( !mv_embed_path ) { tObj["arg"] = tname.split(':').pop(); } - js_log("TNAME::" + tObj["arg"] + ' from:: ' + ts); - + js_log("TNAME::" + tObj["name"] + ' from:: ' + ts); + var pSet = ts.split('\|'); pSet.splice(0,1); if( pSet.length ){ @@ -566,25 +545,57 @@ if( !mv_embed_path ) { return getMagicTxtFromTempNode( node ); } } - //get text node system: - var node = rdpp ( this.wikiText ); - //debugger; - //parse out stuff: - - this.pOut = recurse_magic_swap( node); + //parse out the template node structure: + this.pNode = rdpp ( this.wikiText ); + //strip out the parent from the root + this.pNode['p'] = null; + + //do the recusrive magic swap text: + this.pOut = recurse_magic_swap( this.pNode ); }, + + /* + * parsed template api ~losely based off of ~POM~ + * http://www.mediawiki.org/wiki/Extension:Page_Object_Model + */ + + /** + * templates + * + * gets a requested template from the wikitext (if available) + * + */ + templates: function( tname ){ + this.parse(); + var tmplSet = new Array(); + function getMatchingTmpl( node ){ + if( node['c'] ){ + for(var i in node['c']){ + getMatchingTmpl( node['c'] ); + } + } + if( tname && node.tObj){ + if( node.tObj['name'] == tname ) + tmplSet.push( node.tObj ); + }else if( node.tObj ){ + tmplSet.push( node.tObj ); + } + } + getMatchingTmpl( this.pNode ); + return tmplSet; + }, /** * Returns the transformed wikitext - * - * Build output from swapable index - * (all transforms must be expanded in parse stage and linerarly rebuilt) - * Alternativly we could build output using a placeholder & replace system + * + * Build output from swapable index + * (all transforms must be expanded in parse stage and linerarly rebuilt) + * Alternativly we could build output using a placeholder & replace system * (this lets us be slightly more slopty with ordering and indexes, but probably slower) - * - * Ideal: we build a 'wiki DOM' + * + * Ideal: we build a 'wiki DOM' * When editing you update the data structure directly - * Then in output time you just go DOM->html-ish output without re-parsing anything + * Then in output time you just go DOM->html-ish output without re-parsing anything */ getHTML : function(){ //wikiText updates should invalidate pOut @@ -597,8 +608,9 @@ if( !mv_embed_path ) { //return the parserObj return new parseObj( wikiText, opt) ; } - + })(window.$mw); + //setup legacy global shortcuts: var loadGM = $mw.lang.loadGM; var loadRS = $mw.lang.loadRS; @@ -618,7 +630,8 @@ $mw.lang.loadGM({ "mwe-size-kilobytes" : "$1 K", "mwe-size-bytes" : "$1 B", "mwe-error_load_lib" : "Error: JavaScript $1 was not retrievable or does not define $2", - "mwe-loading-add-media-wiz": "Loading add media wizard" + "mwe-loading-add-media-wiz": "Loading add media wizard", + "mwe-apiproxy-setup" : " Setting up API proxy" }); @@ -666,10 +679,6 @@ function lcCssPath( cssSet ) { * This is used by the script loader to auto-load classes (so we only define * this once for PHP & JavaScript) * - * This is more verbose than the earlier version that compressed paths - * but it's all good, gzipping helps compress path strings - * grouped by directory. - * * Right now the PHP AutoLoader only reads this mv_embed.js file. * In the future we could have multiple lcPath calls that PHP reads * (if our autoloading class list becomes too long) just have to add those @@ -685,13 +694,15 @@ lcPaths({ "$j.ui" : "jquery/jquery.ui/ui/ui.core.js", "$j.fn.ColorPicker" : "libClipEdit/colorpicker/js/colorpicker.js", "$j.Jcrop" : "libClipEdit/Jcrop/js/jquery.Jcrop.js", - "$j.fn.simpleUploadForm": "libAddMedia/simpleUploadForm.js", - + "$j.fn.simpleUploadForm" : "libAddMedia/simpleUploadForm.js", + + "$mw.proxy" : "libMwApi/mw.proxy.js", + "ctrlBuilder" : "skins/ctrlBuilder.js", "kskinConfig" : "skins/kskin/kskin.js", "mvpcfConfig" : "skins/mvpcf/mvpcf.js", - "$j.secureEvalJSON" : "jquery/plugins/jquery.secureEvalJSON.js", + "JSON" : "libMwApi/json2.js", "$j.cookie" : "jquery/plugins/jquery.cookie.js", "$j.contextMenu" : "jquery/plugins/jquery.contextMenu.js", "$j.fn.suggestions" : "jquery/plugins/jquery.suggestions.js", @@ -725,12 +736,13 @@ lcPaths({ "mvAdvFirefogg" : "libAddMedia/mvAdvFirefogg.js", "mvBaseUploadInterface" : "libAddMedia/mvBaseUploadInterface.js", "remoteSearchDriver" : "libAddMedia/remoteSearchDriver.js", - "seqRemoteSearchDriver" : "libAddMedia/seqRemoteSearchDriver.js", + "seqRemoteSearchDriver" : "libSequencer/seqRemoteSearchDriver.js", "baseRemoteSearch" : "libAddMedia/searchLibs/baseRemoteSearch.js", "mediaWikiSearch" : "libAddMedia/searchLibs/mediaWikiSearch.js", "metavidSearch" : "libAddMedia/searchLibs/metavidSearch.js", "archiveOrgSearch" : "libAddMedia/searchLibs/archiveOrgSearch.js", + "flickrSearch" : "libAddMedia/searchLibs/flickrSearch.js", "baseRemoteSearch" : "libAddMedia/searchLibs/baseRemoteSearch.js", "mvClipEdit" : "libClipEdit/mvClipEdit.js", @@ -778,10 +790,6 @@ function mv_set_loading(target, load_id){ * mvJsLoader class handles initialization and js file loads */ -// Shortcut -function mwLoad( loadSet, callback ) { - mvJsLoader.doLoad( loadSet, callback ); -} var mvJsLoader = { libreq : {}, libs : {}, @@ -1066,6 +1074,13 @@ var mvJsLoader = { } } +// Shortcut ( @@todo consolidate shortcuts & re-factor mvJsLoader ) +function mwLoad( loadSet, callback ) { + mvJsLoader.doLoad( loadSet, callback ); +} +//$mw.shortcut +$mw.load = mwLoad; + // Load an external JS file. Similar to jquery .require plugin, // but checks for object availability rather than load state. @@ -1172,15 +1187,30 @@ function mv_remove_modal( speed ) { /* * Store all the mwEmbed jQuery-specific bindings * (set up after jQuery is available). - * This lets you call rewrites in a jQuery way * - * @@ eventually we should refactor mwCode over to jQuery style plugins - * and mv_embed.js will just handle dependency mapping and loading. + * These functions are genneraly are loaders that do the dynamic mapping of + * dependencies for a given compoent + * * */ function mv_jqueryBindings() { js_log( 'mv_jqueryBindings' ); (function( $ ) { + /* + * apiProxy Loader loader: + * + * @param mode is either 'server' or 'client' + */ + $.apiProxy = function( mode, pConf, callback ){ + js_log('do apiProxy setup'); + $.addLoaderDialog( gM('mwe-apiproxy-setup') ); + mvJsLoader.doLoad( [ + '$mw.proxy', + 'JSON' + ], function(){ + $mw.proxy[mode]( pConf, callback ); + }); + } //non selector based add-media-wizard direct invocation with loader $.addMediaWiz = function( iObj, callback ){ js_log(".addMediaWiz call"); @@ -1253,7 +1283,7 @@ function mv_jqueryBindings() { 'mvPlayList', '$j.ui', '$j.contextMenu', - '$j.secureEvalJSON', + 'JSON', 'mvSequencer' ], [ @@ -1421,14 +1451,15 @@ function mv_jqueryBindings() { * * @param msg text text of the loader msg */ - $.addLoaderDialog = function( msg_txt ){ - if( $('#mwe_tmp_loader').length != 0 ) - $('#mwe_tmp_loader').remove(); - + $.addLoaderDialog = function( msg_txt ){ + $.addDialog( msg_txt, msg_txt + '
' + mv_get_loading_img() ); + } + + $.addDialog = function ( title, msg_txt, btn ){ + $('#mwe_tmp_loader').remove(); //append the style free loader ontop: - $('body').append('
' + - msg_txt + '
' + - mv_get_loading_img() + + $('body').append('
' + + msg_txt + '
'); //turn the loader into a real dialog loader: mvJsLoader.doLoadDepMode([ @@ -1444,14 +1475,21 @@ function mv_jqueryBindings() { draggable: false, resizable: false, height: 140, - modal: true + modal: true, + buttons: btn }); }); } - $.closeLoaderDialog = function(){ + $.closeLoaderDialog = function(){ $('#mwe_tmp_loader').dialog('close'); } - + + $.mwProxy = function( apiConf ){ + mvJsLoader.doLoad( ['$mw.apiProxy'], + function(){ + $mw.apiProxy( apiConf ); + }); + } })(jQuery); } /* @@ -1876,7 +1914,11 @@ if ( typeof DOMParser == "undefined" ) { * Utility functions */ function js_log( string ) { - if( window.console ) { + ///add any prepend debug strings if nessesary + if( mwConfig['debug_pre'] ) + string = mwConfig['debug_pre']+ string; + + if( window.console ) { window.console.log( string ); } else { /* diff --git a/js2/mwEmbed/php/languages/mwEmbed.i18n.php b/js2/mwEmbed/php/languages/mwEmbed.i18n.php index ffbcd355ca..5eef7823be 100644 --- a/js2/mwEmbed/php/languages/mwEmbed.i18n.php +++ b/js2/mwEmbed/php/languages/mwEmbed.i18n.php @@ -104,6 +104,7 @@ $messages['en'] = array( 'mwe-size-bytes' => '$1 B', 'mwe-error_load_lib' => 'Error: JavaScript $1 was not retrievable or does not define $2', 'mwe-loading-add-media-wiz' => "Loading add media wizard", + 'mwe-apiproxy-setup' => " Setting up API proxy", /* * js file: /libAddMedia/mvFirefogg.js diff --git a/js2/mwEmbed/tests/testApiProxy.html b/js2/mwEmbed/tests/testApiProxy.html new file mode 100644 index 0000000000..f9bf577fa7 --- /dev/null +++ b/js2/mwEmbed/tests/testApiProxy.html @@ -0,0 +1,76 @@ + + + + +Api Proxy Test + + + + + + +

Simple API proxy testing system

+ +
Setting up Proxy ... ( )
+
+ + + \ No newline at end of file diff --git a/js2/remoteMwEmbed.js b/js2/remoteMwEmbed.js index b4d90b0025..836b9719e6 100644 --- a/js2/remoteMwEmbed.js +++ b/js2/remoteMwEmbed.js @@ -28,6 +28,15 @@ function doPageSpecificRewrite() { importScriptURI( mwEmbedHostPath + '/uploadPage.js' + reqAguments ); } ); } + + // Special api proxy page + if( wgPageName == 'MediaWiki:ApiProxy' ){ + var wgEnableIframeApiProxy = true; + load_mv_embed( function() { + importScriptURI( mwEmbedHostPath + '/ApiProxyPage.js' + reqAguments ); + }); + } + // OggHandler rewrite for view pages: var vidIdList = []; -- 2.20.1