diff --git a/AqueductExtension.php b/AqueductExtension.php new file mode 100644 index 0000000..b3536d8 --- /dev/null +++ b/AqueductExtension.php @@ -0,0 +1,283 @@ + 'Aqueduct Extension', + 'author' => 'The Johns Hopkins University Applied Physics Laboratory', + 'description' => 'A Mediawiki-based platform that allows users to explore, visualize, and annotate external RDF data sources within a wiki interface', + 'version' => 1.2 + ); + + //Set up the special page + $wgExtensionMessagesFiles['AqueductExtension'] = $dir . 'AqueductExtension.i18n.php'; + $wgAutoloadClasses['SpecialAqueductConfiguration'] = $dir . 'SpecialAqueductConfiguration.php'; + $wgSpecialPages['AqueductConfiguration'] = 'SpecialAqueductConfiguration'; + + //Set up the Aqueduct API + $wgAutoloadClasses['ApiAqueduct'] = $dir . 'ApiAqueduct.php'; + $wgAPIModules['aqueduct'] = 'ApiAqueduct'; + + //Set up the Aqueduct Set API + $wgAutoloadClasses['ApiAqueductSet'] = $dir . 'ApiAqueductSet.php'; + $wgAPIModules['aqueductset'] = 'ApiAqueductSet'; + + // Article prepopulation extension + require_once($dir . 'AqueductPagePopulation.php'); + require_once($dir . 'AqueductEditPage.php'); + + // Set up the hook to interupt the edit to check for a new page creation. + $wgHooks['EditPage::showEditForm:initial'][] = 'aqEditWidgetTagHelp'; + $wgHooks['AlternateEdit'][] = 'aqEditWidgetTagJS'; + $wgHooks['AlternateEdit'][] = 'aqPopulateNewPage'; + + //Register a hook to set up the user-configured Mediawiki namespaces + $wgHooks['SetupAfterCache'][] = 'wfAqueductSetupNS'; + + //Load the file with the widget tag logic + require_once($dir . 'WidgetTags.php'); + + //Register the general extension init code (used to set up the widget parser tags) + if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) { + $wgHooks['ParserFirstCallInit'][] = 'wfAqueductExtension'; + } + else { // Otherwise do things the old fashioned way + $wgExtensionFunctions[] = "wfAqueductExtension"; + } + + //Register the magic word init code (used to set up the parser function alias) + $wgHooks['LanguageGetMagic'][] = 'wfAqueductExtensionSetMagicWords'; + + + aqProfile("mw"); + + //Function and class definitions go below + + //Set up the user-defined Aqueduct namespaces (this must be called early in Mediawiki's init) + function wfAqueductSetupNS() + { + global $wgAqueductTblname, $wgCanonicalNamespaceNames, $wgExtraNamespaces, $wgAqueductQueryTblname, $wgAqueductOldNSTblname; + //Don't use DB functions because that file is not loaded here... + aqProfile("aq"); + $newNamespaces = array(); + $db =& wfGetDB( DB_SLAVE ); + + $res = $db->select($wgAqueductTblname, '*'); + while($row = $db->fetchRow($res)) + { + if (intval($row['aq_wiki_namespace_id'])!=0) + { + $newNamespaces[intval($row['aq_wiki_namespace_id'])] = $row['aq_wiki_namespace']; + $newNamespaces[intval($row['aq_wiki_namespace_id'])+1] = $row['aq_wiki_namespace'].'_talk'; + } + } + $db->freeResult($res); + + $res = $db->select($wgAqueductOldNSTblname, '*'); + while($row = $db->fetchRow($res)) + { + if (intval($row['aq_wiki_namespace_id'])!=0) + { + $newNamespaces[intval($row['aq_wiki_namespace_id'])] = $row['aq_wiki_namespace']; + $newNamespaces[intval($row['aq_wiki_namespace_id'])+1] = $row['aq_wiki_namespace'].'_talk'; + } + } + $db->freeResult($res); + + $res = $db->select($wgAqueductQueryTblname, '*'); + while($row = $db->fetchRow($res)) + { + if (intval($row['aq_wiki_namespace_id'])!=0) + { + $newNamespaces[intval($row['aq_wiki_namespace_id'])] = $row['aq_wiki_parent_namespace'] . '_' . $row['aq_wiki_namespace_tag']; + $newNamespaces[intval($row['aq_wiki_namespace_id'])+1] = $row['aq_wiki_parent_namespace'] . '_' . $row['aq_wiki_namespace_tag'] . '_talk'; + } + } + $db->freeResult($res); + + if (!is_array($wgExtraNamespaces)) + { + $wgExtraNamespaces = array(); + } + $wgExtraNamespaces = $wgExtraNamespaces + $newNamespaces; + $wgCanonicalNamespaceNames = $wgCanonicalNamespaceNames + $newNamespaces; + aqProfile("mw"); + return true; + } + + //Extension initialization function (normally used to set up parser tags) + function wfAqueductExtension() + { + global $wgParser,$wgAqueductLayoutMode,$wgDefaultSkin,$wgAqueductJSFiles,$wgRequest,$wgTitle,$wgOut,$wgScriptPath; + aqProfile("aq"); + wfAqueductSetParserHooks($wgParser); + //Only do this once (multiple parser init will be called if we are displaying a compound page) + if (!isset($wgAqueductLayoutMode)) + { + //Figure out if we are using the gridbook skin; if so, go into layout mode + $wgAqueductLayoutMode = + ($wgDefaultSkin == 'gridbook' && + $wgRequest->getText( 'action', 'view' ) == 'view' && + $wgTitle->getNamespace()>-1); + if ($wgAqueductLayoutMode) + { + if (!$wgAqueductJSFiles) + { + $wgAqueductJSFiles = array(); + } + //Force a bunch of JS and CSS files to load even if there are no widgets on the page because we are in layout mode + $wgAqueductJSFiles['jquery-1.3.2.min.js'] = false; + $wgAqueductJSFiles['jquery-ui-1.7.2.custom.min.js'] = false; + $wgAqueductJSFiles['jquery.layout.min-1.2.0.js'] = false; + $wgAqueductJSFiles['gridPlacement.js'] = false; + $wgAqueductJSScripts[''] = false; + $wgOut->addScriptFile($wgScriptPath. '/extensions/AqueductExtension/widget/js/jquery-1.3.2.min.js'); + $wgOut->addScriptFile($wgScriptPath. '/extensions/AqueductExtension/widget/js/jquery-ui-1.7.2.custom.min.js'); + $wgOut->addScriptFile($wgScriptPath. '/extensions/AqueductExtension/widget/js/jquery.layout.min-1.2.0.js'); + $wgOut->addScriptFile($wgScriptPath. '/extensions/AqueductExtension/widget/js/gridPlacement.js'); + $wgOut->addScript('' . "\n"); + } + } + aqProfile("mw"); + return true; + } + + function aqSetHook($parser, $name, $function, $description) + { + global $wgAqueductWidgetTags; + + // Initiate the array + if (!isset($wgAqueductWidgetTags) || !is_array($wgAqueductWidgetTags)) + $wgAqueductWidgetTags = array(); + + // store the description + $wgAqueductWidgetTags["$name"] = "$description"; + + // Add to the parser hooks + // if parser is null, then only initiate the descriptions! + if ($parser !== null) + $parser->setHook( $name, $function ); + } + + function aqSetAdvHook($parser, $name, $function, $description) + { + global $wgAqueductAdvWidgetTags; + + // Initiate the array + if (!isset($wgAqueductAdvWidgetTags) || !is_array($wgAqueductAdvWidgetTags)) + $wgAqueductAdvWidgetTags = array(); + + // store the description + $wgAqueductAdvWidgetTags["$name"] = "$description"; + + // Add to the parser hooks + // if parser is null, then only initiate the descriptions! + if ($parser !== null) + $parser->setHook( $name, $function ); + } + + function wfAqueductSetParserHooks($parser) + { + global $wgEnableLayoutWidgets,$wgEnableCustomScripts; + aqSetHook($parser, "aqProfile", "aqProfileTag", "Display profiling output for a page's Aqueduct operations." ); + aqSetHook($parser, "aqRawWidget", "aqRawWidgetTag", "Place a Raw Format widget. Each row represents an RDF triple." ); + aqSetHook($parser, "aqTableViewWidget", "aqTableViewWidgetTag", "Place a Table Format widget. Each row represents an entity, and each column contains fields about that entity." ); + aqSetHook($parser, "aqNetworkViewWidget", "aqNetworkViewWidgetTag", "Place a Google Earth widget. Displays data with geospatial information as markers." ); + aqSetHook($parser, "aqNetworkViewWidget2D", "aqNetworkViewWidget2DTag", "Place a Google Map widget. Displays data with geospatial information as markers." ); + aqSetAdvHook($parser, "aqIncludeWidgets", "aqIncludeWidgetsTag", "Takes a WikiTitle as input and includes all widgets from that page." ); + aqSetAdvHook($parser, "aqAddData", "aqAddDataTag", "Takes a WikiTitle as input and includes all data from that page into widgets on this page." ); + aqSetAdvHook($parser, "aqAddQuery", "aqAddQueryTag", "Takes a SPARQL query as input to execute." ); + aqSetAdvHook($parser, "aqWikiText", "aqWikiTextTag", "Indicates where wikitext should be displayed in a grid-mode page." ); + + if ($wgEnableCustomScripts ) + { + aqSetAdvHook($parser, "aqAddHeader", "aqHeaderTag", "Adds a Javascript file to the page." ); + aqSetAdvHook($parser, "aqAddScript", "aqScriptTag", "Adds a Javascript script to the page." ); + } + if ($wgEnableLayoutWidgets) + { + aqSetAdvHook($parser, "aqLayout", "aqLayoutTag", "Inserts a layout to be used by a Layout widget." ); + aqSetHook($parser, "aqLayoutWidget", "aqLayoutWidgetTag", "Uses the layout inserted into a page via 'aqLayout' to display RDF." ); + } + + if ($parser !== null) + $parser->setFunctionHook( 'triplemagicword', 'aqAddTripleTag' ); + } + + function wfAqueductExtensionSetMagicWords( &$magicWords, $langCode ) + { + $magicWords['triplemagicword'] = array( 0, 'triple' ); + return true; + } + + function aqProfile($newmode) + { + global $wgAqueductProfile; + if ($wgAqueductProfile === TRUE) + { + global $aqProfileMode, $aqProfileStart, $aqProfileData; + $endtime = microtime(TRUE); + if ($aqProfileMode) + { + if (!$aqProfileData) + { + $aqProfileData = array(); + } + if (array_key_exists($aqProfileMode,$aqProfileData)) + { + $oldtime = $aqProfileData[$aqProfileMode]; + } + else + { + $oldtime = 0.0; + } + $totaltime = $endtime - $aqProfileStart; + $aqProfileData[$aqProfileMode] = $oldtime + $totaltime; + } + $aqProfileMode = $newmode; + $aqProfileStart = microtime(TRUE); + } + } + + function aqProfileTag() + { + global $aqProfileData; + $out = print_r($aqProfileData,TRUE); + $aqProfileData = null; + return $out; + } + +?> diff --git a/AqueductSQLTbl.sql b/AqueductSQLTbl.sql new file mode 100644 index 0000000..e860db3 --- /dev/null +++ b/AqueductSQLTbl.sql @@ -0,0 +1,32 @@ +create table aqueduct +( + aq_wiki_namespace varchar(100) primary key, + aq_wiki_namespace_id int unique, + aq_source_uri varchar(100) unique, + aq_source_type varchar(10), + aq_source_name varchar(100), + aq_source_location varchar(100), + aq_source_cert_path varchar(100), + aq_source_cert_pass varchar(100), + aq_initial_lowercase int, + aq_search_fragments int +); + +create table aqueductqueries +( + aq_wiki_namespace_id int primary key, + aq_wiki_parent_namespace varchar(100) references aqueduct(aq_wiki_namespace), + aq_wiki_namespace_tag varchar(100), + aq_query_type varchar(100), + aq_datasource varchar(100), + aq_query varchar(2000), + aq_algorithm varchar(100), + aq_query_uri_param int +); + + +create table aqueductoldnamespaces +( + aq_wiki_namespace_id int primary key, + aq_wiki_namespace varchar(100) unique +); diff --git a/GridBook.php b/GridBook.php new file mode 100644 index 0000000..d3bd962 --- /dev/null +++ b/GridBook.php @@ -0,0 +1,501 @@ +skinname = 'gridbook'; + $this->stylename = 'gridbook'; + $this->template = 'GridBookTemplate'; + + } + + function setupSkinUserCss( OutputPage $out ) { + global $wgHandheldStyle; + + parent::setupSkinUserCss( $out ); + + // Append to the default screen common & print styles... + $out->addStyle( 'monobook/main.css', 'screen' ); + if( $wgHandheldStyle ) { + // Currently in testing... try 'chick/main.css' + $out->addStyle( $wgHandheldStyle, 'handheld' ); + } + + $out->addStyle( 'monobook/IE50Fixes.css', 'screen', 'lt IE 5.5000' ); + $out->addStyle( 'monobook/IE55Fixes.css', 'screen', 'IE 5.5000' ); + $out->addStyle( 'monobook/IE60Fixes.css', 'screen', 'IE 6' ); + $out->addStyle( 'monobook/IE70Fixes.css', 'screen', 'IE 7' ); + + $out->addStyle( 'monobook/rtl.css', 'screen', '', 'rtl' ); + } +} + +/** + * @todo document + * @ingroup Skins + */ +class GridBookTemplate extends QuickTemplate { + var $skin; + /** + * Template filter callback for MonoBook skin. + * Takes an associative array of data set from a SkinTemplate-based + * class, and a wrapper for MediaWiki's localization database, and + * outputs a formatted page. + * + * @access private + */ + function execute() { + global $wgRequest; + $this->skin = $skin = $this->data['skin']; + $action = $wgRequest->getText( 'action' ); + + // Suppress warnings to prevent notices about missing indexes in $this->data + wfSuppressWarnings(); + +?> +data['xhtmlnamespaces'] as $tag => $ns) { + ?>xmlns:xml:lang="text('lang') ?>" lang="text('lang') ?>" dir="text('dir') ?>"> +
+ + html('headlinks') ?> +The Aqueduct extension for Mediawiki provides several services. The extension allows the administrator to define a mapping between wiki pages and URIs. The administrator must also specify where the data for the URIs is located and how it can be retrieved.
+ +The Aqueduct extension allows for the creation of wikis where users visualize, analyze, discuss, and enhance semantic data. In a way, the wiki acts as a "lens" into semantic data. This semantic data typically resides outside of the wiki, and can be scattered across multiple datasources and servers. The extension does not copy any data into the wiki; instead, it dispatches requests for external semantic data to the proper server and datasource.
+ +Because data can be scattered across multiple datasources and servers, Aqueduct introduces the concept of "RDF sources". An "RDF source" defines how the triples for a collection of data can be retrieved, and how the URIs for the data can be mapped into the wiki.
+ +To ensure that semantic data can be located unambiguously, Aqueduct defines a mapping between the titles (or "name") of wiki pages and the URIs that they describe. Therefore, the set of possible titles must be partitioned up such that any given title is associated with no more than one URI and RDF source. This is done by associating Mediawiki namespaces with Aqueduct RDF sources. Mediawiki namespaces allow two pages with the same title to be created, as long as they are in different namespaces. By associating RDF sources with namespaces, the same title can be used to describe several URIs, as long as each URI ends up in a different namespace.
+ +This extends the "lens" analogy -- the wiki acts as a lens into multiple sources of semantic data, and these sources are "projected" into the wiki, in a manner such that they do not overlap.
+ + +RDF sources are a key concept in Aqueduct because all data manipulated in Aqueduct flows from an RDF source.
+ +Aqueduct allows users to place data visualization widgets in wikis in order to visualize RDF data. Aqueduct allows different widgets to simultaneously visualize RDF data that comes from several sources. Data can reside on an external server, on the same server as the wiki, or even within the wiki itself. Aqueduct supports the following RDF sources:
+To configure the Aqueduct extension, you must create RDF sources for the data that will be made available through the wiki.
+ +To configure an RDF source, you must fill the following columns:
+The test RDF source allows you to test Aqueduct without being connected to a Blackbook data source. SSL and browser certificates are not required. This helps ensure that the widgets are working as intended.
+ +The following URIs are defined in the test RDF source:
+To enable the test RDF source for a namespace, create a datastore with a type of Test. The other settings can be set however you want; however, it will be easiest to access the test data if you set them as follows:
+If you used the above settings, you will be able to put widgets on pages called "Chicago", "Gary", and "Skokie" (in the namespace that you created).
+ +Once you have the RDF sources set up for the Aqueduct extension, you can place Aqueduct widgets on wiki pages. Aqueduct widgets are potentially interactive controls that allow the user to visualize the RDF associated with a wiki page.
+ +To place a widget on the page, insert the name of the widget surrounded by angle brackets. For example, entering <aqRawWidget> or <aqTableViewWidget> will show the Raw RDF or the Table widget. Each widget will visualize the same data in a different way.
+ +Widgets have no parameters and cannot be configured. Each widget automatically displays the data for the URI associated with the wiki page where it was placed.
+ +The following widgets currently exist:
+