From 4a09564f4190021114c206a9e038a1dd79afd620 Mon Sep 17 00:00:00 2001 From: Grant Copley Date: Wed, 21 Aug 2024 11:07:24 -0500 Subject: [PATCH] #162 CBWIRE is unable to load wires located in an external module location --- models/CBWIREController.cfc | 39 ++++++- models/Component.cfc | 55 ++++++++-- test-harness/config/Coldbox.cfc | 4 +- .../ExternalModule/ModuleConfig.cfc | 101 ++++++++++++++++++ .../ExternalModule/config/Router.cfc | 13 +++ .../ExternalModule/config/Scheduler.cfc | 21 ++++ .../ExternalModule/handlers/Home.cfc | 13 +++ .../ExternalModule/views/home/index.cfm | 3 + .../wires/should_load_external_modules.cfm | 11 ++ test-harness/tests/specs/CBWIRESpec.cfc | 9 +- 10 files changed, 254 insertions(+), 15 deletions(-) create mode 100755 test-harness/modules_external/ExternalModule/ModuleConfig.cfc create mode 100755 test-harness/modules_external/ExternalModule/config/Router.cfc create mode 100755 test-harness/modules_external/ExternalModule/config/Scheduler.cfc create mode 100755 test-harness/modules_external/ExternalModule/handlers/Home.cfc create mode 100755 test-harness/modules_external/ExternalModule/views/home/index.cfm create mode 100644 test-harness/modules_external/ExternalModule/wires/should_load_external_modules.cfm diff --git a/models/CBWIREController.cfc b/models/CBWIREController.cfc index b3a5c53..2dd6dfb 100644 --- a/models/CBWIREController.cfc +++ b/models/CBWIREController.cfc @@ -184,7 +184,8 @@ component singleton { try { // Attempt to create an instance of the component - return variables.wirebox.getInstance(local.fullComponentPath); + return variables.wirebox.getInstance(local.fullComponentPath) + ._withPath( arguments.name ); } catch( Injector.InstanceNotFoundException e ) { local.singleFileComponent = variables.singleFileComponentBuilder .setInitialRender( true ) @@ -205,13 +206,30 @@ component singleton { } } + /** + * Returns the path to the modules folder. + * + * @module string | The name of the module. + * + * @return string + */ + function getModuleRootPath( module ) { + var moduleRegistry = moduleService.getModuleRegistry(); + + if ( moduleRegistry.keyExists( module ) ) { + return moduleRegistry[ module ].invocationPath & "." & module; + } + + throw( type="ModuleNotFound", message = "CBWIRE cannot locate the module '" & arguments.module & "'.") + } + /** * Returns the full dot notation path to a modules component. * * @path String | Name of the cbwire component. * @module String | Name of the module to look for wire in. */ - private function getModuleComponentPath( path, module ) { + function getModuleComponentPath( path, module ) { var moduleConfig = moduleService.getModuleConfigCache(); if ( !moduleConfig.keyExists( module ) ) { throw( type="ModuleNotFound", message = "CBWIRE cannot locate the module '" & arguments.module & "'.") @@ -219,9 +237,24 @@ component singleton { // if there is a dot in the path, then we are referencing a folder within a module otherwise use the default wire location. var moduleRegistry = moduleService.getModuleRegistry(); - return arguments.path contains "." ? + + var result = arguments.path contains "." ? moduleRegistry[ module ].invocationPath & "." & module & "." & arguments.path : moduleRegistry[ module ].invocationPath & "." & module & "." & getWiresLocation() & "." & arguments.path; + + return result; + } + + /** + * Returns the path to the wires folder within a module path. + * + * @module string | The name of the module. + * + * @return string + */ + function getModuleWiresPath( module ) { + local.moduleRegistry = moduleService.getModuleRegistry(); + return arguments } /** diff --git a/models/Component.cfc b/models/Component.cfc index 19d3827..8b8fc00 100644 --- a/models/Component.cfc +++ b/models/Component.cfc @@ -25,6 +25,7 @@ component output="true" { property name="_redirect"; property name="_redirectUsingNavigate"; property name="_isolate"; + property name="_path"; /** * Constructor @@ -312,11 +313,12 @@ component output="true" { } // Instaniate this child component as a new component local.instance = variables._CBWIREController.createInstance(argumentCollection=arguments) - ._withParent( this ) - ._withEvent( variables._event ) - ._withParams( arguments.params, arguments.lazy ) - ._withKey( arguments.key ) - ._withLazy( arguments.lazy ); + ._withPath( arguments.name ) + ._withParent( this ) + ._withEvent( variables._event ) + ._withParams( arguments.params, arguments.lazy ) + ._withKey( arguments.key ) + ._withLazy( arguments.lazy ); // Check if lazy loading is enabled if ( arguments.lazy ) { @@ -574,10 +576,21 @@ component output="true" { } /** - * Passes the current event into our component. + * Passes the path of the component. + * + * @path string | The path of the component. * * @return Component + */ + function _withPath( path ) { + variables._path = arguments.path; + return this; + } + + /** + * Passes the current event into our component. * + * @return Component */ function _withEvent( event ) { variables._event = arguments.event; @@ -832,7 +845,7 @@ component output="true" { local.normalizedPath &= ".cfm"; } // Ensure the path starts with "/wires/" without duplicating it - if (left(local.normalizedPath, 6) != "wires/") { + if (!isModulePath() && left(local.normalizedPath, 6) != "wires/") { local.normalizedPath = "wires/" & local.normalizedPath; } // Prepend a leading slash if not present @@ -1398,7 +1411,21 @@ component output="true" { * Returns the path to the view template file. */ function _getViewPath(){ - return "wires." & _getComponentName(); + if ( isModulePath() ) { + var moduleRoot = variables._CBWIREController.getModuleRootPath( _getModuleName() ); + return moduleRoot & ".wires." & _getComponentName(); + } + + return "wires." & variables._path; + } + + /** + * Returns the module name. + * + * @return string + */ + function _getModuleName() { + return variables._path contains "@" ? variables._path.listLast( "@" ) : ""; } /** @@ -1450,7 +1477,8 @@ component output="true" { if ( variables._metaData.name contains "cbwire.models.tmp." ) { return variables._metaData.name.replaceNoCase( "cbwire.models.tmp.", "", "one" ); } - return variables._metaData.name.replaceNoCase( "wires.", "", "one" ); + // only returns the last part of the name seprate by dots + return variables._metaData.name.listLast( "." ); } /** @@ -1549,6 +1577,15 @@ component output="true" { return local.outerElement.trim(); } + /** + * Returns true if the path contains a module. + * + * @return boolean + */ + function isModulePath() { + return variables._path contains "@"; + } + /** * Returns true if the cbvalidation module is installed. * diff --git a/test-harness/config/Coldbox.cfc b/test-harness/config/Coldbox.cfc index bfc7305..9ddb34e 100644 --- a/test-harness/config/Coldbox.cfc +++ b/test-harness/config/Coldbox.cfc @@ -11,7 +11,9 @@ component{ //Development Settings reinitPassword = "", handlersIndexAutoReload = true, - modulesExternalLocation = [], + modulesExternalLocation = [ + "/modules_external" + ], //Implicit Events defaultEvent = "main.index", diff --git a/test-harness/modules_external/ExternalModule/ModuleConfig.cfc b/test-harness/modules_external/ExternalModule/ModuleConfig.cfc new file mode 100755 index 0000000..d6841ec --- /dev/null +++ b/test-harness/modules_external/ExternalModule/ModuleConfig.cfc @@ -0,0 +1,101 @@ +/** + * Module Directives as public properties + * + * this.title = "Title of the module"; + * this.author = "Author of the module"; + * this.webURL = "Web URL for docs purposes"; + * this.description = "Module description"; + * this.version = "Module Version"; + * this.viewParentLookup = (true) [boolean] (Optional) // If true, checks for views in the parent first, then it the module.If false, then modules first, then parent. + * this.layoutParentLookup = (true) [boolean] (Optional) // If true, checks for layouts in the parent first, then it the module.If false, then modules first, then parent. + * this.entryPoint = "" (Optional) // If set, this is the default event (ex:forgebox:manager.index) or default route (/forgebox) the framework will use to create an entry link to the module. Similar to a default event. + * this.cfmapping = "The CF mapping to create"; + * this.modelNamespace = "The namespace to use for registered models, if blank it uses the name of the module." + * this.dependencies = "The array of dependencies for this module" + * + * structures to create for configuration + * - parentSettings : struct (will append and override parent) + * - settings : struct + * - interceptorSettings : struct of the following keys ATM + * - customInterceptionPoints : string list of custom interception points + * - interceptors : array + * - layoutSettings : struct (will allow to define a defaultLayout for the module) + * - wirebox : The wirebox DSL to load and use + * + * Available objects in variable scope + * - controller + * - appMapping (application mapping) + * - moduleMapping (include,cf path) + * - modulePath (absolute path) + * - log (A pre-configured logBox logger object for this object) + * - binder (The wirebox configuration binder) + * - wirebox (The wirebox injector) + * + * Required Methods + * - configure() : The method ColdBox calls to configure the module. + * + * Optional Methods + * - onLoad() : If found, it is fired once the module is fully loaded + * - onUnload() : If found, it is fired once the module is unloaded + **/ +component { + + // Module Properties + this.title = "ExternalModule"; + this.author = ""; + this.webURL = ""; + this.description = ""; + this.version = "1.0.0"; + // If true, looks for views in the parent first, if not found, then in the module. Else vice-versa + this.viewParentLookup = true; + // If true, looks for layouts in the parent first, if not found, then in module. Else vice-versa + this.layoutParentLookup = true; + // Module Entry Point + this.entryPoint = "ExternalModule"; + // Inherit Entry Point + this.inheritEntryPoint = false; + // Model Namespace + this.modelNamespace = "ExternalModule"; + // CF Mapping + this.cfmapping = ""; + // Auto-map models + this.autoMapModels = true; + // Module Dependencies + this.dependencies = []; + + /** + * Configure the module + */ + function configure(){ + // parent settings + parentSettings = {}; + + // module settings - stored in modules.name.settings + settings = {}; + + // Layout Settings + layoutSettings = { defaultLayout : "" }; + + // Custom Declared Points + interceptorSettings = { customInterceptionPoints : [] }; + + // Custom Declared Interceptors + interceptors = []; + + // Binder Mappings + // binder.map("Alias").to("#moduleMapping#.models.MyService"); + } + + /** + * Fired when the module is registered and activated. + */ + function onLoad(){ + } + + /** + * Fired when the module is unregistered and unloaded + */ + function onUnload(){ + } + +} diff --git a/test-harness/modules_external/ExternalModule/config/Router.cfc b/test-harness/modules_external/ExternalModule/config/Router.cfc new file mode 100755 index 0000000..9435672 --- /dev/null +++ b/test-harness/modules_external/ExternalModule/config/Router.cfc @@ -0,0 +1,13 @@ +/** + * Module Router + * https://coldbox.ortusbooks.com/the-basics/routing/routing-dsl + */ +component{ + + function configure(){ + + route( "/", "Home.index" ); + + } + +} diff --git a/test-harness/modules_external/ExternalModule/config/Scheduler.cfc b/test-harness/modules_external/ExternalModule/config/Scheduler.cfc new file mode 100755 index 0000000..6cecf91 --- /dev/null +++ b/test-harness/modules_external/ExternalModule/config/Scheduler.cfc @@ -0,0 +1,21 @@ +/** + * Module Task Scheduler + * https://coldbox.ortusbooks.com/digging-deeper/scheduled-tasks + */ +component { + + function configure(){ + + /* task( "photoNumbers" ) + .call( () => { + var random = getInstance( "PhotoService" ).getRandom(); + writeDump( var="xxxxxxx> Photo numbers: #random#", output="console" ); + return random; + } ) + .every( 15, "seconds" ) + .delay( 60, "seconds" ) + .onEnvironment( "development" ); */ + + } + +} diff --git a/test-harness/modules_external/ExternalModule/handlers/Home.cfc b/test-harness/modules_external/ExternalModule/handlers/Home.cfc new file mode 100755 index 0000000..3b21a1d --- /dev/null +++ b/test-harness/modules_external/ExternalModule/handlers/Home.cfc @@ -0,0 +1,13 @@ +/** + * The main module handler + */ +component{ + + /** + * Module EntryPoint + */ + function index( event, rc, prc ){ + event.setView( "home/index" ); + } + +} diff --git a/test-harness/modules_external/ExternalModule/views/home/index.cfm b/test-harness/modules_external/ExternalModule/views/home/index.cfm new file mode 100755 index 0000000..51b150f --- /dev/null +++ b/test-harness/modules_external/ExternalModule/views/home/index.cfm @@ -0,0 +1,3 @@ + +

Welcome to my cool module page!

+
diff --git a/test-harness/modules_external/ExternalModule/wires/should_load_external_modules.cfm b/test-harness/modules_external/ExternalModule/wires/should_load_external_modules.cfm new file mode 100644 index 0000000..9e3bbab --- /dev/null +++ b/test-harness/modules_external/ExternalModule/wires/should_load_external_modules.cfm @@ -0,0 +1,11 @@ + +
+

External Module Loaded

+
+
+ + + // @startWire + + // @endWire + \ No newline at end of file diff --git a/test-harness/tests/specs/CBWIRESpec.cfc b/test-harness/tests/specs/CBWIRESpec.cfc index 3c46e2a..bf0af35 100644 --- a/test-harness/tests/specs/CBWIRESpec.cfc +++ b/test-harness/tests/specs/CBWIRESpec.cfc @@ -36,7 +36,9 @@ component extends="coldbox.system.testing.BaseTestCase" { // and prepareMock() is a custom method to mock any dependencies, if necessary. setup(); testComponent = getInstance("wires.TestComponent"); - testComponent._withEvent( getRequestContext( ) ); + testComponent + ._withEvent( getRequestContext( ) ) + ._withPath( "wires.TestComponent" ); CBWIREController = getInstance( "CBWIREController@cbwire" ); prepareMock( testComponent ); }); @@ -1116,7 +1118,10 @@ component extends="coldbox.system.testing.BaseTestCase" { expect( result ).toContain( "Nested module component using default wires location" ); } ); - + it( "can load components from an external modules folder", function() { + var result = cbwireController.wire( "should_load_external_modules@ExternalModule" ); + expect( result ).toInclude( "External Module Loaded" ); + } ); }); describe( "Preprocessors", function() {