@@ -134,38 +134,57 @@ public static function initSMW() {
134134 * @return void
135135 */
136136 public static function onBeforePageDisplay ( $ out , $ skin ) {
137- $ out ->addModules ( 'ext.KnowledgeGraph ' );
137+ // Ensure that the KnowledgeGraphOptions page exists
138+ self ::ensureKnowledgeGraphOptionsPageExists ();
138139 return true ;
139140 }
140141
141142 /**
142- * @param Parser $parser
143- */
144- public static function onParserFirstCallInit ( Parser $ parser ) {
145- $ parser ->setFunctionHook ( 'knowledgegraph ' , [ self ::class, 'parserFunctionKnowledgeGraph ' ] );
146- }
147-
148- /**
149- * @param DatabaseUpdater|null $updater
143+ * Ensure that the KnowledgeGraphOptions page exists in the MediaWiki namespace.
144+ * Creates it lazily if missing.
145+ *
146+ * @return void
150147 */
151- public static function onLoadExtensionSchemaUpdates ( DatabaseUpdater $ updater = null ) {
152- $ text = file_get_contents ( __DIR__ . '/../data/KnowledgeGraphOptions.js ' );
153- $ user = RequestContext::getMain ()->getUser ();
154- $ title = TitleClass::makeTitleSafe ( NS_MEDIAWIKI , 'KnowledgeGraphOptions ' );
148+ private static function ensureKnowledgeGraphOptionsPageExists () {
149+ $ title = Title::makeTitleSafe ( NS_MEDIAWIKI , 'KnowledgeGraphOptions ' );
150+ if ( !$ title ) {
151+ return ;
152+ }
155153
156154 $ wikiPage = self ::getWikiPage ( $ title );
157- $ pageUpdater = $ wikiPage ->newPageUpdater ( $ user );
155+ if ( $ wikiPage ->exists () ) {
156+ return ;
157+ }
158+
159+ // Create page content
160+ $ filePath = __DIR__ . '/../data/KnowledgeGraphOptions.js ' ;
161+ if ( !file_exists ( $ filePath ) ) {
162+ wfDebugLog ( 'KnowledgeGraph ' , 'Missing KnowledgeGraphOptions.js template file. ' );
163+ return ;
164+ }
165+
166+ $ text = file_get_contents ( $ filePath );
167+ $ content = ContentHandler::makeContent (
168+ $ text ,
169+ $ title ,
170+ CONTENT_MODEL_JAVASCRIPT
171+ );
158172
159- // @see includes/Defines.php
160- $ modelId = CONTENT_MODEL_JAVASCRIPT ;
161- $ slotContent = ContentHandler::makeContent ( $ text , $ title , $ modelId );
162- $ slotName = SlotRecord::MAIN ;
163- $ pageUpdater ->setContent ( $ slotName , $ slotContent );
173+ $ user = User::newSystemUser ( 'MediaWiki default ' , [ 'steal ' => true ] );
164174
165- $ summary = "KnowledgeGraph " ;
166- $ flags = EDIT_INTERNAL ;
167- $ comment = CommentStoreComment::newUnsavedComment ( $ summary );
168- $ pageUpdater ->saveRevision ( $ comment , $ flags );
175+ $ pageUpdater = $ wikiPage ->newPageUpdater ( $ user );
176+ $ pageUpdater ->setContent ( SlotRecord::MAIN , $ content );
177+ $ pageUpdater ->saveRevision (
178+ CommentStoreComment::newUnsavedComment ( 'Initialize KnowledgeGraphOptions ' ),
179+ EDIT_SUPPRESS_RC
180+ );
181+ }
182+
183+ /**
184+ * @param Parser $parser
185+ */
186+ public static function onParserFirstCallInit ( Parser $ parser ) {
187+ $ parser ->setFunctionHook ( 'knowledgegraph ' , [ self ::class, 'parserFunctionKnowledgeGraph ' ] );
169188 }
170189
171190 /**
@@ -277,6 +296,7 @@ public static function parserFunctionKnowledgeGraph( Parser $parser, ...$argv )
277296 $ params ['graphOptions ' ] = $ graphOptions ;
278297 $ params ['propertyOptions ' ] = $ propertyOptions ;
279298 self ::$ graphs [] = $ params ;
299+ self ::$ data = [];
280300
281301 $ out ->setExtensionData ( 'knowledgegraphs ' , self ::$ graphs );
282302
@@ -290,8 +310,9 @@ public static function parserFunctionKnowledgeGraph( Parser $parser, ...$argv )
290310 'wgKnowledgeGraphColorPalette ' => $ colors
291311 ] );
292312
313+ $ index = count ( self ::$ graphs ) - 1 ;
293314 return [
294- '<div class="KnowledgeGraph" id="knowledgegraph-wrapper- ' . key ( self :: $ graphs ) . '"> '
315+ '<div class="KnowledgeGraph" id="knowledgegraph-wrapper- ' . $ index . '"> '
295316 . wfMessage ( 'knowledge-graph-wrapper-loading ' )->text () . '</div> ' ,
296317 'noparse ' => true ,
297318 'isHTML ' => true
@@ -358,6 +379,75 @@ public static function getSubjectsByProperty( $propertyText, $limit = 100, $offs
358379 return $ ret ;
359380 }
360381
382+ /**
383+ * Get all properties for a given node.
384+ * @param string $nodeTitleText
385+ * @return array
386+ */
387+ public static function getAllPropertiesForNode ( string $ nodeTitleText ): array {
388+ $ ret = [];
389+
390+ $ title = Title::newFromText ( $ nodeTitleText );
391+ if ( !$ title || !$ title ->isKnown () ) {
392+ wfDebugLog ( 'KnowledgeGraph ' , "Invalid or unknown node: ' $ nodeTitleText' " );
393+ return [];
394+ }
395+
396+ $ apiParams = [
397+ 'action ' => 'smwbrowse ' ,
398+ 'format ' => 'json ' ,
399+ 'browse ' => 'subject ' ,
400+ 'params ' => json_encode ( [
401+ 'subject ' => $ nodeTitleText ,
402+ 'ns ' => $ title ->getNamespace (),
403+ ] ),
404+ ];
405+
406+ $ request = new \FauxRequest ( $ apiParams , false );
407+ $ api = new \ApiMain ( $ request );
408+ $ api ->execute ();
409+ $ data = $ api ->getResult ()->getResultData ();
410+
411+ if ( empty ( $ data [ 'query ' ][ 'data ' ] ) ) {
412+ wfDebugLog ( 'KnowledgeGraph ' , "No properties returned from smwbrowse for ' $ nodeTitleText' " );
413+ return [];
414+ }
415+
416+ foreach ( $ data ['query ' ]['data ' ] as $ propertyEntry ) {
417+ $ propKey = $ propertyEntry ['property ' ] ?? null ;
418+ $ direction = $ propertyEntry ['direction ' ] ?? 'direct ' ;
419+
420+ if ( !$ propKey ) {
421+ continue ;
422+ }
423+
424+ if (
425+ ( isset ( self ::$ exclude ) && in_array ( $ propKey , self ::$ exclude ) ) ||
426+ str_starts_with ( $ propKey , '_ ' ) ||
427+ str_starts_with ( $ propKey , '___ ' ) ||
428+ ctype_upper ( str_replace ( '_ ' , '' , $ propKey ) )
429+ ) {
430+ continue ;
431+ }
432+
433+ $ propKey = str_replace ( '_ ' , ' ' , $ propKey );
434+
435+ if ( $ direction === 'inverse ' ) {
436+ $ propKey = '- ' . $ propKey ;
437+ }
438+
439+ $ ret [] = $ propKey ;
440+ }
441+
442+ wfDebugLog ( 'KnowledgeGraph ' , sprintf (
443+ "getAllPropertiesForNode (smwbrowse): node=%s, properties=%d " ,
444+ $ nodeTitleText ,
445+ count ( $ ret )
446+ ) );
447+
448+ return array_unique ( $ ret );
449+ }
450+
361451 /**
362452 * @param Title|MediaWiki\Title\Title $title $title
363453 * @return string|null
@@ -403,6 +493,9 @@ public static function onOutputPageParserOutput( OutputPage $out, ParserOutput $
403493 $ out ->addJsConfigVars ( [
404494 'knowledgegraphs ' => json_encode ( $ data )
405495 ] );
496+
497+ // add the required JavaScript module if graphs are present
498+ $ out ->addModules ( 'ext.KnowledgeGraph ' );
406499 }
407500 }
408501
@@ -534,7 +627,16 @@ public static function setSemanticDataFromApi( Title $title, $onlyProperties, $d
534627 return ;
535628 }
536629
537- if ( $ depth > $ maxDepth ) {
630+ // If maxDepth is 0, only create the root node without loading SMW data
631+ if ( $ maxDepth === 0 ) {
632+ self ::$ data [$ titleText ] = [
633+ 'properties ' => [],
634+ 'categories ' => [],
635+ ];
636+ return ;
637+ }
638+
639+ if ( $ depth >= $ maxDepth ) {
538640 return ;
539641 }
540642
@@ -640,8 +742,19 @@ public static function setSemanticDataFromApi( Title $title, $onlyProperties, $d
640742
641743 foreach ( $ entry ['dataitem ' ] ?? [] as $ item ) {
642744 if ( $ item ['type ' ] === 9 ) {
643- $ linkedTitle = explode ( '# ' , $ item ['item ' ] )[0 ];
644- $ linkedTitle = $ linkedTitle ? str_replace ( '_ ' , ' ' , $ linkedTitle ) : null ;
745+ $ parts = explode ( '# ' , $ item ['item ' ] );
746+ $ dbkey = $ parts [0 ] ?? '' ;
747+ $ nsId = isset ( $ parts [1 ] ) && is_numeric ( $ parts [1 ] ) ? (int )$ parts [1 ] : 0 ;
748+
749+ $ namespaceInfo = MediaWiki \MediaWikiServices::getInstance ()->getNamespaceInfo ();
750+ $ nsName = $ namespaceInfo ->getCanonicalName ( $ nsId );
751+
752+ $ linkedTitle = $ dbkey ;
753+ if ( $ nsName !== '' && $ nsName !== false ) {
754+ $ linkedTitle = $ nsName . ': ' . $ dbkey ;
755+ }
756+
757+ $ linkedTitle = str_replace ( '_ ' , ' ' , $ linkedTitle );
645758 if ( !$ linkedTitle ) {
646759 continue ;
647760 }
0 commit comments