Skip to content

Commit

Permalink
Initial Custom Module support and installer. (openemr#2654)
Browse files Browse the repository at this point in the history
Put in the initial work for custom modules to be installed via composer.
A sample module install can be illustrated by doing a composer require openemr/oe-module-faxsms

You can then go on the GUI and from the Modules Installer menu item you can install the faxmodule and enable it.
The fax/SMS module gets added and its appropriate hooks are executed.
  • Loading branch information
adunsulag authored and bradymiller committed Sep 8, 2019
1 parent 821d59f commit b60929f
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
function oemr_zend_load_modules_from_db()
{
// we skip the audit log as it has no bearing on user activity and is core system related...
$resultSet = sqlStatementNoLog($statement = "SELECT mod_name FROM modules WHERE mod_active = 1 ORDER BY `mod_ui_order`, `date`");
$resultSet = sqlStatementNoLog($statement = "SELECT mod_name FROM modules WHERE mod_active = 1 AND type = 1 ORDER BY `mod_ui_order`, `date`");
$db_modules = [];
while ($row = sqlFetchArray($resultSet)) {
$db_modules[] = $row["mod_name"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ $depObj = $this->dependencyObject;
<?php
$form_title_file = @file($GLOBALS['srcdir']."/../{$baseModuleDir}{$customDir}/$fname/info.txt");
if ($form_title_file)
$form_title = $form_title_file[0];
$form_title = trim($form_title_file[0]);
else
$form_title = $fname;
echo $listener->z_xlt($form_title);
Expand Down
17 changes: 16 additions & 1 deletion interface/patient_file/report/patient_report.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
require_once("$srcdir/patient.inc");

use OpenEMR\Core\Header;
use OpenEMR\Events\PatientReport\PatientReportEvent;
use OpenEMR\Menu\PatientMenuRole;
use OpenEMR\OeUI\OemrUI;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;

if (!acl_check('patients', 'pat_rep')) {
die(xlt('Not authorized'));
Expand All @@ -31,6 +34,11 @@
$auth_med = acl_check('patients', 'med');
$auth_demo = acl_check('patients', 'demo');

/**
* @var EventDispatcherInterface $eventDispatcher The event dispatcher / listener object
*/
$eventDispatcher = $GLOBALS['kernel']->getEventDispatcher();

$cmsportal = false;
if ($GLOBALS['gbl_portal_cms_enable']) {
$ptdata = getPatientData($pid, 'cmsportal_login');
Expand Down Expand Up @@ -256,9 +264,12 @@ function show_date_fun(){
<br>
<button type="button" class="genreport btn btn-default btn-save btn-sm" value="<?php echo xla('Generate Report'); ?>" ><?php echo xlt('Generate Report'); ?></button>
<button type="button" class="genpdfrep btn btn-default btn-download btn-sm" value="<?php echo xla('Download PDF'); ?>" ><?php echo xlt('Download PDF'); ?></button>
<?php if ($cmsportal) { ?>
<?php if ($cmsportal) { ?>
<button type="button" class="genportal btn btn-default btn-send-msg btn-sm" value="<?php echo xla('Send to Portal'); ?>" ><?php echo xlt('Send to Portal'); ?></button>
<?php } ?>
<?php
$eventDispatcher->dispatch(OpenEMR\Events\PatientReport::ACTIONS_RENDER_POST, new GenericEvent());
?>
<input type='hidden' name='pdf' value='0'>
<br>

Expand Down Expand Up @@ -739,6 +750,10 @@ function(data) {
});
<?php } ?>

<?php
$eventDispatcher->dispatch(PatientReportEvent::JAVASCRIPT_READY_POST, new GenericEvent());
?>

});

// select/deselect the Forms related to the selected Encounter
Expand Down
47 changes: 45 additions & 2 deletions src/Core/ModulesApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,21 @@

class ModulesApplication
{

/**
* The application reference pointer for the zend mvc modules application
* @var \Zend\Mvc\Application
*/
private $application;

const CUSTOM_MODULE_BOOSTRAP_NAME = 'openemr.bootstrap.php';

public function __construct(Kernel $kernel, $webRootPath, $modulePath, $zendModulePath)
{
$zendConfigurationPath = $webRootPath . DIRECTORY_SEPARATOR . $modulePath . DIRECTORY_SEPARATOR . $zendModulePath;
$customModulePath = $webRootPath . DIRECTORY_SEPARATOR . $modulePath . DIRECTORY_SEPARATOR . "custom_modules" . DIRECTORY_SEPARATOR;
$configuration = require $zendConfigurationPath . DIRECTORY_SEPARATOR . 'config/application.config.php';

// Prepare the service manager
// We customize this and skip using the static Zend\Mvc\Application::init in order to inject the
// Symfony Kernel's EventListener that way we can bridge the two frameworks.
Expand All @@ -55,6 +58,46 @@ public function __construct(Kernel $kernel, $webRootPath, $modulePath, $zendModu
$listeners = array_unique(array_merge($listenersFromConfigService, $listenersFromAppConfig));

$this->application = $serviceManager->get('Application')->bootstrap($listeners);

// we skip the audit log as it has no bearing on user activity and is core system related...
$resultSet = sqlStatementNoLog($statement = "SELECT mod_name FROM modules WHERE mod_active = 1 AND type != 1 ORDER BY `mod_ui_order`, `date`");
$db_modules = [];
while ($row = sqlFetchArray($resultSet)) {
$db_modules[] = $row["mod_name"];
}

$this->bootstrapCustomModules($kernel->getEventDispatcher(), $customModulePath);
}

private function bootstrapCustomModules($eventDispatcher, $customModulePath) {
// we skip the audit log as it has no bearing on user activity and is core system related...
$resultSet = sqlStatementNoLog($statement = "SELECT mod_name, mod_directory FROM modules WHERE mod_active = 1 AND type != 1 ORDER BY `mod_ui_order`, `date`");
$db_modules = [];
while ($row = sqlFetchArray($resultSet)) {
$db_modules[] = ["name" => $row["mod_name"], "directory" => $row['mod_directory'], "path" => $customModulePath . $row['mod_directory'] ];
}
foreach ($db_modules as $module) {
$this->loadCustomModule($module, $eventDispatcher);
}
// TODO: stephen we should fire an event saying we've now loaded all the modules here.
}

private function loadCustomModule($module, $eventDispatcher) {
if (!is_readable($module['path'] . DIRECTORY_SEPARATOR . self::CUSTOM_MODULE_BOOSTRAP_NAME)) {
// TODO: stephen need to escape filename here.
error_log("Custom module file path " . errorLogEscape($module['path'] )
. DIRECTORY_SEPARATOR . self::CUSTOM_MODULE_BOOSTRAP_NAME
. " is not readable. Check directory permissions");
}
try {
// the only thing in scope here is $module and $eventDispatcher which is ok for our bootstrap piece.
// do we really want to just include a file?? Should we go all zend and actually force a class instantiation
// here and then inject the EventDispatcher or even possibly the Symfony Kernel here?
include $module['path'] . DIRECTORY_SEPARATOR . self::CUSTOM_MODULE_BOOSTRAP_NAME;
}
catch (Exception $exception) {
error_log(errorLogEscape($exception->getMessage()));
}
}

public function run()
Expand Down
20 changes: 20 additions & 0 deletions src/Events/PatientReport/PatientReportEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
namespace OpenEMR\Events\PatientReport;

use Symfony\Component\EventDispatcher\Event;

class PatientReportEvent extends Event {

/**
* This event fires after the action buttons for the report have rendered.
* It allows listeners to render additional content or buttons.
*/
const ACTIONS_RENDER_POST = 'patientReport.actions.render.post';

/**
* This event fires after the document.ready event has fired in javascript and all of the javascript functions
* that we want to execute on that ready event have rendered to the screen. Listeners can insert additional
* javascript onto the screen for the patient report.
*/
const JAVASCRIPT_READY_POST = 'patientReport.javascript.load.post';
}

0 comments on commit b60929f

Please sign in to comment.