|
From: <gem...@li...> - 2013-03-22 14:02:21
|
Revision: 1203
http://sourceforge.net/p/gemstracker/code/1203
Author: mennodekker
Date: 2013-03-22 14:02:16 +0000 (Fri, 22 Mar 2013)
Log Message:
-----------
Initial checking of OpenRosa survey source (not tested to work yet)
Modified Paths:
--------------
trunk/library/classes/Gems/Menu.php
trunk/library/classes/Gems/Model.php
trunk/library/classes/GemsEscort.php
trunk/new_project/application/configs/application.ini
Added Paths:
-----------
trunk/library/classes/Gems/Default/OpenrosaAction.php
trunk/library/classes/OpenRosa/
trunk/library/classes/OpenRosa/Model/
trunk/library/classes/OpenRosa/Model/OpenRosaFormModel.php
trunk/library/classes/OpenRosa/Tracker/
trunk/library/classes/OpenRosa/Tracker/Source/
trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/
trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/Form.php
trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/Model.php
trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa.php
trunk/library/controllers/OpenrosaController.php
trunk/new_project/var/settings/pwd.txt
Added: trunk/library/classes/Gems/Default/OpenrosaAction.php
===================================================================
--- trunk/library/classes/Gems/Default/OpenrosaAction.php (rev 0)
+++ trunk/library/classes/Gems/Default/OpenrosaAction.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -0,0 +1,422 @@
+<?php
+
+/**
+ * Handles call like an openRosa compliant server. Implements the api as described on
+ * https://bitbucket.org/javarosa/javarosa/wiki/OpenRosaAPI
+ *
+ * To implement, place the controller in the right directory and allow access without login to the
+ * following actions:
+ * formList - Lists the forms available
+ * submission - Handles receiving a submitted form
+ * download - Download a form
+ */
+class Gems_Default_OpenrosaAction extends Gems_Controller_BrowseEditAction
+{
+ /**
+ * This holds the path to the location where the form definitions will be stored.
+ * Will be set on init to: GEMS_ROOT_DIR . '/var/uploads/openrosa/forms/';
+ *
+ * @var string
+ */
+ public $formDir;
+
+ /**
+ * This holds the path to the location where the uploaded responses and their
+ * backups will be stored.
+ *
+ * Will be set on init to: GEMS_ROOT_DIR . '/var/uploads/openrosa/';
+ *
+ * @var string
+ */
+ public $responseDir;
+
+ /**
+ * @var Zend_Auth
+ */
+ protected $auth;
+
+ /**
+ * This lists the actions that need http-auth. Only applies to the actions that
+ * the openRosa application needs.
+ *
+ * ODK Collect: http://code.google.com/p/opendatakit/wiki/ODKCollect
+ *
+ * @var array Array of actions
+ */
+ protected $authActions = array('formlist', 'submission', 'download');
+
+ /**
+ * This can be used to generate barcodes, use the action
+ *
+ * /openrosa/barcode/code/<tokenid>
+ *
+ * example:
+ * /openrosa/barocde/code/22pq-grkq
+ *
+ * The image will be a png
+ */
+ public function barcodeAction()
+ {
+ $code = $this->getRequest()->getParam('code', 'empty');
+ Zend_Layout::getMvcInstance()->disableLayout();
+ $this->_helper->viewRenderer->setNoRender();
+
+ $barcodeOptions = array('text' => $code);
+ $rendererOptions = array();
+ $barcode = Zend_Barcode::render('code128', 'image', $barcodeOptions, $rendererOptions);
+ $barcode->render();
+ }
+
+ protected function createModel($detailed, $action)
+ {
+ $model = $this->loader->getModels()->getOpenRosaFormModel();
+
+ $model->set('TABLE_ROWS', 'label', $this->_('Responses'), 'elementClass', 'Exhibitor');
+
+ return $model;
+ }
+
+ /**
+ * This action should serve the right form to the downloading application
+ * it should also handle expiration / availability of forms
+ */
+ public function downloadAction()
+ {
+ $filename = $this->getRequest()->getParam('form');
+ $filename = basename($filename); //Strip paths
+
+ $file = $this->formDir . $filename;
+
+ if (!empty($filename) && file_exists($file)) {
+ $this->getHelper('layout')->disableLayout();
+ $this->getResponse()->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ header('Content-Description: File Transfer');
+ header('Content-Type: application/octet-stream');
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ header('Content-Transfer-Encoding: binary');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header('Pragma: public');
+ header('Content-Length: ' . filesize($file));
+ ob_clean();
+ flush();
+ readfile($file);
+ exit;
+ } else {
+ $this->getResponse()->setHttpResponseCode(404);
+ $this->html->div("form $filename not found");
+ }
+ }
+
+ /**
+ * Accessible via formList as defined in the menu and standard for openRosa clients
+ */
+ public function formlistAction()
+ {
+ //first create the baseurl of the form http(s)://projecturl/openrosa/download/form/
+ $helper = new Zend_View_Helper_ServerUrl();
+ $baseUrl = $helper->serverUrl() . Zend_Controller_Front::getInstance()->getBaseUrl() . '/openrosa/download/form/';
+
+ //As we don't have forms defined yet, we pass in an array, but ofcourse this should be dynamic
+ //and come from a helper method
+ $model = $this->getModel();
+ $rawForms = $model->load(array('gof_form_active'=>1));
+ foreach($rawForms as $form) {
+ $forms[] = array(
+ 'formID' => $form['gof_form_id'],
+ 'name' => $form['gof_form_title'],
+ 'version' => $form['gof_form_version'],
+ 'hash' => md5($form['gof_form_id'].$form['gof_form_version']),
+ 'downloadUrl' => $baseUrl . $form['gof_form_xml']
+ );
+ }
+
+ //Now make it a rosaresponse
+ $this->makeRosaResponse();
+
+ $xml = $this->getXml('xforms xmlns="http://openrosa.org/xforms/xformsList"');
+ foreach ($forms as $form) {
+ $xform = $xml->addChild('xform');
+ foreach ($form as $key => $value) {
+ $xform->addChild($key, $value);
+ }
+ }
+
+ echo $xml->asXML();
+ }
+
+ public function getTopic($count = 1)
+ {
+ return 'OpenRosa Form';
+ }
+
+ public function getTopicTitle()
+ {
+ return 'OpenRosa Forms';
+ }
+
+ /**
+ * Create an xml response
+ *
+ * @param string $rootNode
+ * @return SimpleXMLElement
+ */
+ protected function getXml($rootNode)
+ {
+ $this->getResponse()->setHeader('Content-Type', 'text/xml; charset=utf-8');
+
+ $xml = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><$rootNode />");
+
+ return $xml;
+ }
+
+ public function init()
+ {
+ parent::init();
+
+ $this->responseDir = GEMS_ROOT_DIR . '/var/uploads/openrosa/';
+ $this->formDir = $this->responseDir . 'forms/';
+ }
+
+ /**
+ * Each rosa response should have the x-openrosa-version header and disable the layout to allow
+ * for xml repsonses if needed. We don't need a menu etc. on the openrosa responses
+ */
+ protected function makeRosaResponse()
+ {
+ $this->getHelper('layout')->disableLayout();
+ $this->getResponse()->setHeader('X-OpenRosa-Version', '1.0');
+ }
+
+ /**
+ * Handles receiving and storing the data from a form, files are stored on actual upload process
+ * this only handles storing form data and can be used for resubmission too.
+ *
+ * @param type $xmlFile
+ * @return string DeviceID or false on failure
+ */
+ private function processReceivedForm($answerXmlFile)
+ {
+ //Log what we received
+ $log = Gems_Log::getLogger();
+ //$log->log(print_r($xmlFile, true), Zend_Log::ERR);
+
+ $xml = simplexml_load_file($answerXmlFile);
+
+ $formId = $xml->attributes()->id;
+ $formVersion = $xml->attributes()->version;
+ //Lookup what form belongs to this formId and then save
+ $model = $this->getModel();
+ $filter = array(
+ //'gof_form_active' => 1,
+ 'gof_form_id' => $formId,
+ 'gof_form_version' => $formVersion,
+ );
+ if ($formData = $model->loadFirst($filter)) {
+ $form = new OpenRosa_Tracker_Source_OpenRosa_Form($this->formDir . $formData['gof_form_xml']);
+ $form->saveAnswer($answerXmlFile);
+
+ $deviceId = $xml->DeviceId[0];
+ return $deviceId;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Implements HTTP Basic auth
+ */
+ public function preDispatch()
+ {
+ parent::preDispatch();
+
+ $action = strtolower($this->getRequest()->getActionName());
+ if (in_array($action, $this->authActions)) {
+ $auth = Zend_Auth::getInstance();
+ $this->auth = $auth;
+
+ if (!$auth->hasIdentity()) {
+ $config = array(
+ 'accept_schemes' => 'basic',
+ 'realm' => GEMS_PROJECT_NAME,
+ 'nonce_timeout' => 3600,
+ );
+ $adapter = new Zend_Auth_Adapter_Http($config);
+ $basicResolver = new Zend_Auth_Adapter_Http_Resolver_File();
+
+ //This is a basic resolver, use username:realm:password
+ //@@TODO: move to a better db stored authentication system
+
+ $basicResolver->setFile(GEMS_ROOT_DIR . '/var/settings/pwd.txt');
+ $adapter->setBasicResolver($basicResolver);
+ $request = $this->getRequest();
+ $response = $this->getResponse();
+
+ assert($request instanceof Zend_Controller_Request_Http);
+ assert($response instanceof Zend_Controller_Response_Http);
+
+ $adapter->setRequest($request);
+ $adapter->setResponse($response);
+
+ $result = $auth->authenticate($adapter);
+
+ if (!$result->isValid()) {
+ $adapter->getResponse()->sendResponse();
+ print 'Unauthorized';
+ exit;
+ }
+ }
+ }
+ }
+
+ public function scanAction()
+ {
+ $model = $this->getModel();
+
+ //Perform a scan of the form directory, to update the database of forms
+ $eDir = dir($this->formDir);
+
+ $formCnt = 0;
+ $addCnt = 0;
+ while (false !== ($filename = $eDir->read())) {
+ if (substr($filename, -4) == '.xml') {
+ $formCnt++;
+ $form = new OpenRosa_Tracker_Source_OpenRosa_Form($this->formDir . $filename);
+ $filter['gof_form_id'] = $form->getFormID();
+ $filter['gof_form_version'] = $form->getFormVersion();
+ $forms = $model->load($filter);
+
+ if (!$forms) {
+ $newValues = array();
+ $newValues['gof_id'] = null;
+ $newValues['gof_form_id'] = $form->getFormID();
+ $newValues['gof_form_version'] = $form->getFormVersion();
+ $newValues['gof_form_title'] = $form->getTitle();
+ $newValues['gof_form_xml'] = $filename;
+ $newValues = $model->save($newValues);
+ MUtil_Echo::r($newValues, 'added form');
+ $addCnt++;
+ }
+ }
+ }
+
+ $cache = GemsEscort::getInstance()->cache;
+ $cache->clean();
+
+ $this->html[] = sprintf('Checked %s forms and added %s forms', $formCnt, $addCnt);
+ }
+
+ public function scanresponsesAction()
+ {
+ $model = $this->getModel();
+
+ //Perform a scan of the form directory, to update the database of forms
+ $eDir = dir($this->responseDir);
+
+ $formCnt = 0;
+ $addCnt = 0;
+ $rescan = $this->getRequest()->getParam('rescan', false);
+ while (false !== ($filename = $eDir->read())) {
+ $ext = substr($filename, -4);
+ if ($ext == '.xml' || ($ext == '.bak' && $rescan)) {
+ if ($rescan) {
+ $oldname = $filename;
+ $filename = substr($oldname, 0, -4) . '.xml';
+ rename($this->responseDir . $oldname, $this->responseDir . $filename);
+ }
+ $files[] = $filename;
+ $formCnt++;
+ }
+ }
+
+ foreach ($files as $filename) {
+ $result = $this->processReceivedForm($this->responseDir . $filename);
+ if ($result !== false) {
+ $addCnt++;
+ }
+ }
+ $cache = GemsEscort::getInstance()->cache;
+ $cache->clean();
+
+ $this->html[] = sprintf('Checked %s responses and added %s responses', $formCnt, $addCnt);
+ }
+
+ /**
+ * Accepts the form
+ *
+ * Takes two roundtrips:
+ * - first we get a HEAD request that should be answerd with
+ * responsecode 204
+ * - then we get a post that only submits $_FILES (so actual $_POST will be empty)
+ * this will be an xml file for the actuel response and optionally images and/or video
+ * proper responses are
+ * 201 received and stored
+ * 202 received ok, not stored
+ */
+ public function submissionAction()
+ {
+ $this->makeRosaResponse();
+
+ if ($this->getRequest()->isHead()) {
+ $this->getResponse()->setHttpResponseCode(204);
+ } elseif ($this->getRequest()->isPost()) {
+ //Post
+ // We get $_FILES variable holding the formresults as xml and all possible
+ // attachments like photo's and video's
+ $upload = new Zend_File_Transfer_Adapter_Http();
+
+ // We should really add some validators here see http://framework.zend.com/manual/en/zend.file.transfer.validators.html
+ // Returns all known internal file information
+ $files = $upload->getFileInfo();
+
+ foreach ($files as $file => $info) {
+ // file uploaded ?
+ if (!$upload->isUploaded($file)) {
+ print "Why haven't you uploaded the file ?";
+ continue;
+ }
+
+ // validators are ok ?
+ if (!$upload->isValid($file)) {
+ print "Sorry but $file is not what we wanted";
+ continue;
+ }
+ }
+
+ //Dit moet een filter worden (rename filter) http://framework.zend.com/manual/en/zend.file.transfer.filters.html
+ $upload->setDestination($this->responseDir);
+
+ //Hier moeten we denk ik eerst de xml_submission_file uitlezen, en daar
+ //iets mee doen
+ if ($upload->receive('xml_submission_file')) {
+ $xmlFile = $upload->getFileInfo('xml_submission_file');
+ $answerXmlFile = $xmlFile['xml_submission_file']['tmp_name'];
+ $deviceId = $this->processReceivedForm($answerXmlFile);
+ if ($deviceId === false) {
+ //form not accepted!
+ foreach ($xml->children() as $child) {
+ $log->log($child->getName() . ' -> ' . $child, Zend_Log::ERR);
+ }
+ } else {
+ //$log->log(print_r($files, true), Zend_Log::ERR);
+ //$log->log($deviceId, Zend_Log::ERR);
+ foreach ($upload->getFileInfo() as $file => $info) {
+ if ($info['received'] != 1) {
+ //Rename to deviceid_md5(time)_filename
+ //@@TODO: move to form subdir, for better separation
+ $upload->addFilter('Rename', $deviceId . '_' . md5(time()) . '_' . $info['name'], $file);
+ }
+ }
+
+ //Now receive the other files
+ if (!$upload->receive()) {
+ $messages = $upload->getMessages();
+ echo implode("\n", $messages);
+ }
+ $this->getResponse()->setHttpResponseCode(201); //Form received ok
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
Modified: trunk/library/classes/Gems/Menu.php
===================================================================
--- trunk/library/classes/Gems/Menu.php 2013-03-22 13:17:27 UTC (rev 1202)
+++ trunk/library/classes/Gems/Menu.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -224,6 +224,9 @@
$logMaint = $page->addPage($this->_('Maintenance'), 'pr.log.maintenance', 'log-maintenance');
$logMaint->addAutofilterAction();
$logMaint->addEditAction('pr.log.maintenance');
+
+ // OpenRosa
+ $this->addOpenRosaContainer($this->_('OpenRosa'));
return $setup;
}
@@ -274,6 +277,26 @@
}
}
+
+ /**
+ * Shortcut function to add all items needed for OpenRosa
+ *
+ * Should be enabled in application.ini by using useOpenRosa = 1
+ *
+ * @param string $label Label for the container
+ */
+ public function addOpenRosaContainer($label)
+ {
+ if ($this->escort->getOption('useOpenRosa')) {
+ $page = $this->addBrowsePage($label, 'pr.openrosa','openrosa');
+ $page->addButtonOnly($this->_('Scan FormDefs'), 'pr.openrosa.scan', 'openrosa', 'scan');
+ $page->addButtonOnly($this->_('Scan Responses'), 'pr.openrosa.scan', 'openrosa', 'scanresponses');
+ $this->addPage(null, null, 'openrosa', 'submission');
+ $this->addPage(null, null, 'openrosa', 'formList'); //mind the capital L here
+ $this->addPage(null, null, 'openrosa', 'download');
+ $this->addPage(null, null, 'openrosa', 'barcode'); // For barcode rendering
+ }
+ }
/**
* Shortcut function to create the respondent page.
Modified: trunk/library/classes/Gems/Model.php
===================================================================
--- trunk/library/classes/Gems/Model.php 2013-03-22 13:17:27 UTC (rev 1202)
+++ trunk/library/classes/Gems/Model.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -177,7 +177,21 @@
return $model;
}
+
+ /**
+ * Returns the OpenRosaFormModel
+ *
+ * It is special since it can show how many responses each table has
+ *
+ * @return OpenRosa_Model_OpenRosaFormModel
+ */
+ public function getOpenRosaFormModel()
+ {
+ $model = $this->_loadClass('OpenRosaFormModel', true);
+ return $model;
+ }
+
/**
* Load the organization model
*
Modified: trunk/library/classes/GemsEscort.php
===================================================================
--- trunk/library/classes/GemsEscort.php 2013-03-22 13:17:27 UTC (rev 1202)
+++ trunk/library/classes/GemsEscort.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -430,6 +430,25 @@
return $locale;
}
+
+ /**
+ * Initialize the OpenRosa survey source
+ *
+ * @param Zend_Controller_Action $actionController
+ */
+ public function _initOpenRosa(Zend_Controller_Action $actionController = null)
+ {
+ if ($this->getOption('useOpenRosa')) {
+ // First handle dependencies
+ $this->bootstrap(array('db', 'loader', 'util'));
+
+ /**
+ * Add Source for OpenRosa
+ */
+ $tracker = $this->loader->getTracker();
+ $tracker->addSourceClasses(array('OpenRosa'=>'OpenRosa form'));
+ }
+ }
/**
Added: trunk/library/classes/OpenRosa/Model/OpenRosaFormModel.php
===================================================================
--- trunk/library/classes/OpenRosa/Model/OpenRosaFormModel.php (rev 0)
+++ trunk/library/classes/OpenRosa/Model/OpenRosaFormModel.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * Copyright (c) 2011, Erasmus MC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Erasmus MC nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Short description of file
+ *
+ * @package Gems
+ * @subpackage
+ * @copyright Copyright (c) 2011 Erasmus MC
+ * @license New BSD License
+ * @version $Id: Sample.php 215 2011-07-12 08:52:54Z michiel $
+ */
+
+/**
+ * Short description for OpenRosaFormModel
+ *
+ * Long description for class OpenRosaFormModel (if any)...
+ *
+ * @package Gems
+ * @subpackage Sample
+ * @copyright Copyright (c) 2011 Erasmus MC
+ * @license New BSD License
+ * @since Class available since version 1.0
+ * @deprecated Class deprecated since version 2.0
+ */
+class OpenRosa_Model_OpenRosaFormModel extends Gems_Model_JoinModel
+{
+ /**
+ *
+ * @var Zend_Translate_Adapter
+ */
+ public $translate;
+
+ public function __construct()
+ {
+ parent::__construct('orf', 'gems__openrosaforms', 'gof');
+ }
+
+ public function afterRegistry()
+ {
+ parent::afterRegistry();
+
+ $this->setIfExists('gof_form_id', 'label', $this->translate->_('FormID'));
+ $this->setIfExists('gof_form_version', 'label', $this->translate->_('Version'));
+ $this->setIfExists('gof_form_title', 'label', $this->translate->_('Name'));
+ $this->setIfExists('gof_form_active', 'label', $this->translate->_('Active'), 'elementClass', 'checkbox');
+ }
+
+ /**
+ * Get a select statement using a filter and sort
+ *
+ * Modified to add the information schema, only possible like this since
+ * the table has no primary key and can not be added using normal joins
+ *
+ * @param array $filter
+ * @param array $sort
+ * @return Zend_Db_Table_Select
+ */
+ public function _createSelect($filter = null, $sort = null)
+ {
+ $select = parent::_createSelect($filter, $sort);
+
+ $config = $select->getAdapter()->getConfig();
+ if (isset($config['dbname'])) {
+ $constraint = $select->getAdapter()->quoteInto(' AND TABLE_SCHEMA=?', $config['dbname']);
+ } else {
+ $constraint = '';
+ }
+ $select->joinLeft('INFORMATION_SCHEMA.TABLES', "table_name = convert(concat_ws('_','gems__orf_', REPLACE(gof_form_id,'.','_'),gof_form_version) USING utf8)" . $constraint, array('TABLE_ROWS'));
+ return $select;
+ }
+}
\ No newline at end of file
Added: trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/Form.php
===================================================================
--- trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/Form.php (rev 0)
+++ trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/Form.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -0,0 +1,497 @@
+<?php
+/**
+ * Copyright (c) 2011, Erasmus MC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Erasmus MC nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Helper for OpenRosa forms
+ *
+ * It supports a subset of OpenRosa forms and provides a bridge between GemsTracker
+ * models and the xml-formdefinition.
+ *
+ * @package Gems
+ * @subpackage OpenRosa
+ * @copyright Copyright (c) 2011 Erasmus MC
+ * @license New BSD License
+ * @version $Id: Sample.php 215 2011-07-12 08:52:54Z michiel $
+ */
+
+/**
+ * Helper for OpenRosa forms
+ *
+ * It supports a subset of OpenRosa forms and provides a bridge between GemsTracker
+ * models and the xml-formdefinition.
+ *
+ * @package Gems
+ * @subpackage OpenRosa
+ * @copyright Copyright (c) 2011 Erasmus MC
+ * @license New BSD License
+ * @since Class available since version 1.0
+ * @deprecated Class deprecated since version 2.0
+ */
+class OpenRosa_Tracker_Source_OpenRosa_Form
+{
+ /**
+ * @var Gems_Model_JoinModel
+ */
+ private $model;
+ private $bind;
+ private $instance;
+ private $body;
+ private $deviceIdField;
+ private $formID;
+ private $formVersion;
+ private $title;
+
+ /**
+ * @var SimpleXmlElement
+ */
+ private $_xml;
+
+ /**
+ * Create an OpenRosaForm from an existing filename
+ *
+ * @param string $file the sanitized filename (absolute path)
+ */
+ public function __construct($file)
+ {
+ $this->translate = Zend_Registry::getInstance()->get('Zend_Translate');
+ if (!file_exists($file)) {
+ throw new Gems_Exception_Coding(sprintf($this->translate->_('File not found: %s'), $file));
+ }
+
+ //We read the xml file
+ $xml = simplexml_load_file($file);
+ if ($xml === false) {
+ throw new Gems_Exception_Coding(sprintf($this->translate->_('Could not read form definition for form %s'), $file));
+ }
+ $this->_xml = $xml;
+
+ //For working with the namespaces:
+ //$xml->children('h', true)->head->children()->model->bind
+ //use namespace h children for the root element, and find h:head, then use no namespace children
+ //and find model->bind so we get h:head/model/bind elements
+ $this->bind = $this->flattenBind($this->_xml->children('h', true)->head->children()->model->bind);
+ $this->body = $this->flattenBody($xml->children('h', true)->body->children(), $context = '');
+ $this->instance = $this->flattenInstance($this->_xml->children('h', true)->head->children()->model->instance->data->children());
+ }
+
+ private function createTable()
+ {
+ $tableName = $this->getTableName();
+ $tablePrefix = 'orf';
+ $db = Zend_Registry::getInstance()->get('db');
+
+ $sql = 'CREATE TABLE IF NOT EXISTS ' . $db->quoteIdentifier($tableName) . ' ('
+ . $db->quoteIdentifier($tablePrefix . '_id') . " bigint(20) NOT NULL auto_increment,\n";
+
+ foreach ($this->instance as $name => $element) {
+ $bindName = str_replace('_', '/', '_data_' . $name);
+ $bindInfo = $this->bind[$bindName];
+
+ $field = array();
+ switch ($bindInfo['type']) {
+ case 'date':
+ case 'dateTime':
+ $field['type'] = 'datetime';
+ $field['size'] = '';
+ break;
+
+ case 'barcode':
+ // The token field
+ $field['size'] = 32;
+ $field['type'] = 'varchar';
+ $field['size'] = '(' . $field['size'] . ')';
+
+ case 'string':
+ // Always make it text
+ $field['type'] = 'text';
+ $field['size'] = '';
+ break;
+
+ case 'select':
+ //A multi select
+ $field['size'] = '(1)';
+ $field['type'] = 'int';
+ $items = $this->body[$bindName]['item'];
+ foreach ($items as $key => $value) {
+ $multiName = $name . '_' . $key;
+ $sql .= " " . $db->quoteIdentifier($multiName) . " {$field['type']}{$field['size']} DEFAULT 0 NOT NULL,\n";
+ }
+ //So we don't get an extra field
+ unset($field['type']);
+ break;
+
+ case 'select1':
+ //Select one, size can be as a small as largest answeroption
+ $items = $this->body[$bindName]['item'];
+ $field['size'] = 1;
+ foreach ($items as $key => $value) {
+ if (strlen($key) > $field['size']) {
+ $field['size'] = strlen($key);
+ }
+ }
+ $field['type'] = 'varchar';
+ $field['size'] = '(' . $field['size'] . ')';
+ break;
+
+ case 'int':
+ $field['type'] = 'bigint';
+ $field['size'] = '(20)';
+
+ case 'decimal':
+ $field['type'] = 'float';
+ $field['size'] = '';
+
+ default:
+ $field['type'] = 'varchar';
+ $field['size'] = 5;
+ $field['size'] = '(' . $field['size'] . ')';
+ }
+
+ if (isset($field['type'])) {
+ $sql .= " " . $db->quoteIdentifier($name) . " {$field['type']}{$field['size']} DEFAULT NULL,\n";
+ }
+ }
+
+ $sql .= $db->quoteIdentifier($tablePrefix . '_changed') . " timestamp NOT NULL,\n"
+ . $db->quoteIdentifier($tablePrefix . '_changed_by') . " bigint(20) NOT NULL,\n"
+ . $db->quoteIdentifier($tablePrefix . '_created') . " timestamp NOT NULL,\n"
+ . $db->quoteIdentifier($tablePrefix . '_created_by') . " bigint(20) NOT NULL,\n"
+ . 'PRIMARY KEY (`' . $tablePrefix . '_id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;';
+
+ $db->query($sql);
+
+ return new Gems_Model_JoinModel($this->getFormID(), $tableName, $tablePrefix);
+ }
+
+ private function flattenBind($xml)
+ {
+ foreach ($xml as $name => $element) {
+ $attributes = array();
+ foreach ($element->attributes() as $name => $value) {
+ $attributes[$name] = (string) $value;
+ if ($name == 'nodeset') {
+ $ref = (string) $value;
+ }
+ }
+ foreach ($element->attributes('jr', true) as $name => $value) {
+ $attributes['jr'] [$name] = (string) $value;
+ }
+ $output[$ref] = $attributes;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Return flattend element
+ *
+ * @param SimpleXMLElement $xml
+ * @param type $context
+ */
+ private function flattenBody($xml, $context = '')
+ {
+ foreach ($xml as $elementName => $element) {
+ //Check ref first
+ $elementContext = $context;
+ foreach ($element->attributes() as $name => $value) {
+ if ($name == 'ref') {
+ if (!empty($elementContext)) {
+ $elementContext .= '/';
+ } else {
+ $elementContext = '/data/';
+ }
+ if (substr($value, 0, 1) == '/') {
+ $elementContext = '';
+ }
+ $elementContext .= $value;
+ break;
+ }
+ }
+ $result['context'] = $elementContext;
+ $result['name'] = $element->getName();
+
+ //elementName can be label, hint, or a sub element (group, input, select, select1
+ switch ($elementName) {
+ case 'label':
+ $result['label'] = (string) $element;
+ break;
+
+ case 'hint':
+ $result['hint'] = (string) $element;
+ break;
+
+ case 'value':
+ $result['value'] = (string) $element;
+ break;
+
+ case 'input':
+ case 'select':
+ case 'select1':
+ //has only value and label but repeated, add value/label pairs in array
+ $rawItem = $this->flattenBody($element, $elementContext);
+ $rawItem['context'] = $elementContext;
+ $rawItem['name'] = $element->getName();
+ $result[$rawItem['context']] = $rawItem;
+ break;
+
+ case 'item':
+ //has only value and label but repeated, add value/label pairs in array
+ $rawItem = $this->flattenBody($element->children(), $elementContext);
+ unset($rawItem['context']);
+ unset($rawItem['name']);
+ $result['item'][$rawItem['value']] = $rawItem['label'];
+ break;
+
+ case 'group':
+ default:
+ unset($result['context']);
+ unset($result['name']);
+ $subarray = $this->flattenBody($element->children(), $elementContext);
+ unset($subarray['context']);
+ unset($subarray['label']);
+ unset($subarray['hint']);
+ unset($subarray['name']);
+ $result = $result + $subarray;
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ private function flattenInstance($xml, $parent = '')
+ {
+ $output = array();
+ foreach ($xml as $name => $element) {
+ if (!empty($parent)) {
+ $elementName = $parent . '_' . $name;
+ } else {
+ $elementName = $name;
+ }
+ if (count($element->children()) > 0) {
+ $output = $output + $this->flattenInstance($element, $elementName);
+ } else {
+ $output[$elementName] = (string) $element;
+ }
+ }
+ return $output;
+ }
+
+ /**
+ * Returns what field (path) contains the attributes jr:preload="property" jr:preloadParams="deviceid"
+ * from the moden -> bind elements
+ *
+ * @return string
+ */
+ public function getDeviceIdField()
+ {
+ if (empty($this->deviceIdField)) {
+ foreach ($this->_xml->children('h', true)->head->children()->model->bind as $bind) {
+ if ($presets = $bind->attributes('jr', true)) {
+ foreach ($presets as $key => $value) {
+ if ($value == 'deviceid') {
+ $this->deviceIdField = $bind->attributes()->nodeset;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return $this->deviceIdField;
+ }
+
+ /**
+ * Returns the formID from the instance element id attribute
+ *
+ * @return string
+ */
+ public function getFormID()
+ {
+ if (empty($this->formID)) {
+ foreach ($this->_xml->children('h', true)->head->children()->model->instance->children() as $name => $element) {
+ if (!empty($element->attributes()->id)) {
+ $this->formID = $element->attributes()->id;
+ break;
+ }
+ }
+ }
+
+ return $this->formID;
+ }
+
+ /**
+ * Returns the formVersion from the instance element version attribute
+ *
+ * @return string
+ */
+ public function getFormVersion()
+ {
+ if (empty($this->formVersion)) {
+ foreach ($this->_xml->children('h', true)->head->children()->model->instance->children() as $name => $element) {
+ if (!empty($element->attributes()->version)) {
+ $this->formVersion = $element->attributes()->version;
+ break;
+ }
+ }
+ }
+
+ return $this->formVersion;
+
+ }
+
+ /**
+ * @return Gems_Model_JoinModel
+ */
+ public function getModel()
+ {
+ if (empty($this->model)) {
+ try {
+ $model = new Gems_Model_JoinModel($this->getFormID(), $this->getTableName(), 'orf');
+ } catch (Exception $exc) {
+ //Failed, now create the table as it obviously doesn't exists
+ $model = $this->createTable();
+ }
+
+ //Now we have the table, let's add some multi options etc.
+ $checkBox[1] = $this->translate->_('Checked');
+ $checkbox[0] = $this->translate->_('Not checked');
+ foreach ($this->instance as $name => $element) {
+ $bindName = str_replace('_', '/', '_data_' . $name);
+ $bindInfo = $this->bind[$bindName];
+
+ switch ($bindInfo['type']) {
+ case 'select':
+ //A multi select
+ $items = $this->body[$bindName]['item'];
+ foreach ($items as $key => $value) {
+ $multiName = $name . '_' . $key;
+ $label = sprintf('%s [%s]', $this->body[$bindName]['label'], $value);
+ $model->set($multiName, 'multiOptions', $checkBox, 'label', $label);
+ }
+ break;
+
+ case 'select1':
+ $items = $this->body[$bindName]['item'];
+ $model->set($name, 'multiOptions', $items);
+ default:
+ $label = null;
+ if (array_key_exists($bindName, $this->body)) {
+ if (array_key_exists('label', $this->body[$bindName])) {
+ $label = $this->body[$bindName]['label'];
+ if (array_key_exists('hint', $this->body[$bindName])) {
+ $label = sprintf('%s (%s)', $label, $this->body[$bindName]['hint']);
+ }
+ $model->set($name, 'label', $label);
+ }
+ }
+ break;
+ }
+ }
+ $this->model = $model;
+ }
+
+ return $this->model;
+ }
+
+ public function getTableName()
+ {
+ $tableName = str_replace('.', '_', 'gems__orf__' . $this->getFormID() . '_' . $this->getFormVersion());
+
+ return $tableName;
+ }
+
+ /**
+ * Returns the form title from the h:title element
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ if (empty($this->title)) {
+ $this->title = $this->_xml->children('h', true)->head->children('h', true)->title;
+ }
+
+ return $this->title;
+ }
+
+ public function saveAnswer($file, $remove = true)
+ {
+ if (!file_exists($file)) {
+ throw new Gems_Exception_Coding(sprintf($this->translate->_('File not found: %s'), $file));
+ }
+
+ //We read the xml file
+ $xml = simplexml_load_file($file);
+ if ($xml === false) {
+ throw new Gems_Exception_Coding(sprintf($this->translate->_('Could not read form definition for form %s'), $file));
+ }
+
+ $formId = (string) $xml->attributes()->id;
+ if ($formId != $this->getFormID()) {
+ //Can not save to this object as it is a different form!
+ throw new Gems_Exception_Coding(sprintf($this->translate->_('Response is for a different formId: %s <-> %s'), $formId, $this->getFormID()));
+ }
+
+ $answers = $this->flattenInstance($xml);
+ //Now we should parse the response, extract the options given for a (multi)select
+ foreach ($this->instance as $name => $element) {
+ $bindName = str_replace('_', '/', '_data_' . $name);
+ $bindInfo = $this->bind[$bindName];
+
+ if ($bindInfo['type'] == 'dateTime') {
+ $answers[$name] = new Zend_Date($answers[$name], Zend_Date::ISO_8601);
+ }
+ if ($bindInfo['type'] == 'select') {
+ //A multi select
+ $items = explode(' ', $answers[$name]);
+ foreach ($items as $idx => $key) {
+ $multiName = $name . '_' . $key;
+ $answers[$multiName] = 1;
+ }
+ unset($answers[$name]);
+ }
+ }
+
+ $answers['orf_id'] = null;
+ $model = $this->getModel();
+ $answers = $model->save($answers);
+ if ($model->getChanged() && $remove) {
+ $log = Gems_Log::getLogger();
+ $log->log($file . '-->' . substr($file, 0, -3) . 'bak', Zend_Log::ERR);
+ rename($file, substr($file, 0, -3) . 'bak');
+ }
+ // @@TODO: make hook for respondentID lookup too
+ if (isset($answers['token'])) {
+ // We receveid a form linked to a token, signal the 'inSource' for this token.
+ $loader = GemsEscort::getInstance()->getLoader();
+ $token = $loader->getTracker()->getToken($answers['token']);
+ $token->getUrl($loader->getCurrentUser()->getLocale(), $loader->getCurrentUser()->getUserId());
+ }
+
+ return $answers;
+ }
+}
\ No newline at end of file
Added: trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/Model.php
===================================================================
--- trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/Model.php (rev 0)
+++ trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa/Model.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Copyright (c) 2011, Erasmus MC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Erasmus MC nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package Zsd
+ * @subpackage Tracker
+ * @copyright Copyright (c) 2011 Erasmus MC
+ * @license New BSD License
+ * @version $Id: DbSourceSurveyModel.php 223 2011-12-19 09:48:15Z 175780 $
+ */
+
+/**
+ * More correctly a Survey ANSWERS Model as it adds answers to token information
+ *
+ * @package Zsd
+ * @subpackage Tracker
+ * @copyright Copyright (c) 2011 Erasmus MC
+ * @license New BSD License
+ * @since Class available since version 1.5
+ */
+class OpenRosa_Tracker_Source_OpenRosa_Model extends Gems_Tracker_SurveyModel
+{
+ public function addAnswers(array $inputRows)
+ {
+ $tokens = MUtil_Ra::column('gto_id_token', $inputRows);
+
+ $answerRows = $this->source->getRawTokenAnswerRows(array('token' => $tokens), $this->survey->getSurveyId());
+ $emptyRow = array_fill_keys($this->getItemNames(), null);
+ $resultRows = array();
+
+ $answerTokens = MUtil_Ra::column('token', $answerRows);
+
+ foreach ($inputRows as $row) {
+ $tokenId = $row['gto_id_token'];
+ $idx = array_search($tokenId, $answerTokens);
+ if ($idx !== false && isset($answerRows[$idx])) {
+ $resultRows[$tokenId] = $row + $answerRows[$idx] + $emptyRow;
+ } else {
+ $resultRows[$tokenId] = $row + $emptyRow;
+ }
+ }
+ return $resultRows;
+ }
+}
\ No newline at end of file
Added: trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa.php
===================================================================
--- trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa.php (rev 0)
+++ trunk/library/classes/OpenRosa/Tracker/Source/OpenRosa.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -0,0 +1,302 @@
+<?php
+
+/**
+ * Copyright (c) 2011, Erasmus MC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Erasmus MC nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Short description of file
+ *
+ * @package Gems
+ * @subpackage
+ * @copyright Copyright (c) 2011 Erasmus MC
+ * @license New BSD License
+ * @version $Id: Sample.php 215 2011-07-12 08:52:54Z michiel $
+ */
+
+/**
+ * Short description for OpenRosaSource
+ *
+ * Long description for class OpenRosaSource (if any)...
+ *
+ * @package Gems
+ * @subpackage Sample
+ * @copyright Copyright (c) 2011 Erasmus MC
+ * @license New BSD License
+ * @since Class available since version 1.0
+ * @deprecated Class deprecated since version 2.0
+ */
+class OpenRosa_Tracker_Source_OpenRosa extends Gems_Tracker_Source_SourceAbstract
+{
+ /**
+ * This holds the path to the location where the form definitions will be stored.
+ * Will be set on init to: GEMS_ROOT_DIR . '/var/uploads/openrosa/forms/';
+ *
+ * @var string
+ */
+ public $formDir;
+
+ public function __construct(array $sourceData, Zend_Db_Adapter_Abstract $gemsDb)
+ {
+ parent::__construct($sourceData, $gemsDb);
+ $this->formDir = GEMS_ROOT_DIR . '/var/uploads/openrosa/forms/';
+ }
+
+ /**
+ * Returns the source surveyId for a given Gems survey Id
+ *
+ * @param type $surveyId
+ * @return type
+ */
+ private function _getSid($surveyId)
+ {
+ return $this->tracker->getSurvey($surveyId)->getSourceSurveyId();
+ }
+
+ //put your code here
+ public function checkSourceActive($userId)
+ {
+ $active = true;
+
+ $values['gso_active'] = $active ? 1 : 0;
+ $values['gso_status'] = $active ? 'Active' : 'Inactive';
+ $values['gso_last_synch'] = new Zend_Db_Expr('CURRENT_TIMESTAMP');
+
+ $this->_updateSource($values, $userId);
+
+ return $active;
+ }
+
+ public function copyTokenToSource(Gems_Tracker_Token $token, $language, $surveyId, $sourceSurveyId = null)
+ {
+ // Maybe insert meta data here?
+ }
+
+ public function getAnswerDateTime($fieldName, Gems_Tracker_Token $token, $surveyId, $sourceSurveyId = null)
+ {
+
+ }
+
+ public function getCompletionTime(Gems_Tracker_Token $token, $surveyId, $sourceSurveyId = null)
+ {
+
+ }
+
+ public function getDatesList($language, $surveyId, $sourceSurveyId = null)
+ {
+
+ }
+
+ public function getQuestionInformation($language, $surveyId, $sourceSurveyId = null)
+ {
+ $survey = $this->getSurvey($surveyId, $sourceSurveyId);
+ $model = $survey->getModel();
+ $result = array();
+
+ foreach($model->getItemsOrdered() as $name) {
+ if ($label = $model->get($name, 'label')) {
+ $result[$name]['question'] = $label;
+ if ($answers = $model->get($name, 'multiOptions')) {
+ $result[$name]['answers'] = $answers;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ public function getQuestionList($language, $surveyId, $sourceSurveyId = null)
+ {
+ $survey = $this->getSurvey($surveyId, $sourceSurveyId);
+ $model = $survey->getModel();
+ $result = array();
+
+ foreach($model->getItemsOrdered() as $name) {
+ if ($label = $model->get($name, 'label')) {
+ $result[$name] = $label;
+ }
+ }
+
+ return $result;
+ }
+
+ public function getRawTokenAnswerRow($tokenId, $surveyId, $sourceSurveyId = null)
+ {
+ $survey = $this->getSurvey($surveyId, $sourceSurveyId);
+ $model = $survey->getModel();
+
+ $result = $model->loadFirst(array('token' => $tokenId));
+ return $result;
+ }
+
+ public function getRawTokenAnswerRows(array $filter, $surveyId, $sourceSurveyId = null)
+ {
+ $survey = $this->getSurvey($surveyId, $sourceSurveyId);
+ $model = $survey->getModel();
+
+ $result = $model->load();
+
+ if ($result) {
+ return $result;
+ }
+ return array();
+ }
+
+ public function getStartTime(Gems_Tracker_Token $token, $surveyId, $sourceSurveyId = null)
+ {
+
+ }
+
+ public function getSurvey($surveyId, $sourceSurveyId = null)
+ {
+ if (is_null($sourceSurveyId)) {
+ $sourceSurveyId = $this->_getSid($surveyId);
+ }
+
+ $surveyInfo = $this->getSurveyInfo($sourceSurveyId);
+ $survey = new OpenRosa_Tracker_Source_OpenRosa_Form($this->formDir . $surveyInfo['gof_form_xml']);
+
+ return $survey;
+ }
+
+ /**
+ * Return info about the survey (row from gems__openrosaforms)
+ *
+ * @param int $sourceSurveyId
+ * @return array
+ */
+ public function getSurveyInfo($sourceSurveyId)
+ {
+ $db = $this->getSourceDatabase();
+
+ $select = $db->select();
+ $select->from('gems__openrosaforms')
+ ->where('gof_id = ?', $sourceSurveyId);
+
+ return $db->fetchRow($select);
+ }
+
+ public function getSurveyAnswerModel(Gems_Tracker_Survey $survey, $language = null, $sourceSurveyId = null)
+ {
+ if (null === $sourceSurveyId) {
+ $sourceSurveyId = $this->_getSid($survey->getSurveyId());
+ }
+
+ $surveyModel = $this->getSurvey($survey->getSurveyId(), $sourceSurveyId)->getModel();
+ $model = new OpenRosa_Tracker_Source_OpenRosa_Model($survey, $this);
+ $questionsList = $this->getQuestionList($language, $survey->getSurveyId(), $sourceSurveyId);
+ foreach($questionsList as $item => $question) {
+ $allOptions = $surveyModel->get($item);
+ $allowed = array_fill_keys(array('storageFormat', 'dateFormat', 'label', 'multiOptions', 'maxlength', 'type'),1);
+ $options = array_intersect_key($allOptions, $allowed);
+
+ $options['label'] = strip_tags($question);
+
+ //Should also do something to get the better titles...
+ $model->set($item, $options);
+ }
+
+ return $model;
+ }
+
+ public function getTokenUrl(Gems_Tracker_Token $token, $language, $surveyId, $sourceSurveyId)
+ {
+ // There is no url, so return null
+ return;
+ }
+
+ public function inSource(Gems_Tracker_Token $token, $surveyId, $sourceSurveyId = null)
+ {
+ // The token is always in source
+ return true;
+ }
+
+ public function isCompleted(Gems_Tracker_Token $token, $surveyId, $sourceSurveyId = null)
+ {
+ $result = $this->getRawTokenAnswerRow($token->getTokenId(), $surveyId);
+ $completed = !empty($result);
+
+ return $completed;
+ }
+
+ public function setRawTokenAnswers(Gems_Tracker_Token $token, array $answers, $surveyId, $sourceSurveyId = null)
+ {
+
+ }
+
+ public function synchronizeSurveys($userId)
+ {
+ // Surveys in LS
+ $db = $this->getSourceDatabase();
+
+ $select = $db->select();
+ $select->from('gems__openrosaforms');
+
+ $openRosaSurveys = $db->fetchAssoc($select);
+
+ if (!$openRosaSurveys) {
+ //If no surveys present, just use an empty array as array_combine fails
+ $openRosaSurveys = array();
+ } else {
+ $openRosaSurveyIds = array_combine(array_keys($openRosaSurveys), array_keys($openRosaSurveys));
+ }
+
+ // Surveys in Gems
+ $gemsSurveys = $this->_getGemsSurveysForSynchronisation();
+
+ foreach ($gemsSurveys as $surveyId => $sourceSurveyId) {
+ $survey = $this->tracker->getSurveyBySourceId($sourceSurveyId, $this->getId());
+ if (isset($openRosaSurveyIds[$sourceSurveyId])) {
+ // Exists
+ $values['gsu_survey_name'] = $openRosaSurveys[$sourceSurveyId]['gof_form_title'] . ' [' . $openRosaSurveys[$sourceSurveyId]['gof_form_version'] . ']';
+ $values['gsu_surveyor_active'] = $openRosaSurveys[$sourceSurveyId]['gof_form_active'];
+ $values['gsu_status'] = 'Ok';
+ } else {
+ // No longer exists
+ $values['gsu_surveyor_active'] = 0;
+ $values['gsu_status'] = 'No longer exists';
+ }
+
+ $survey->saveSurvey($values, $userId);
+ }
+
+ foreach (array_diff($openRosaSurveyIds, $gemsSurveys) as $sourceSurveyId) {
+ // New survey
+ $values = array();
+ $values['gsu_survey_name'] = $openRosaSurveys[$sourceSurveyId]['gof_form_title'] . ' [' . $openRosaSurveys[$sourceSurveyId]['gof_form_version'] . ']';
+ $values['gsu_surveyor_active'] = $openRosaSurveys[$sourceSurveyId]['gof_form_active'];
+ $values['gsu_active'] = 0;
+ $values['gsu_status'] = '';
+
+ $survey = $this->tracker->getSurveyBySourceId($sourceSurveyId, $this->getId());
+ $survey->exists = false;
+ $survey->saveSurvey($values, $userId);
+ }
+ }
+
+ public function updateConsent(Gems_Tracker_Token $token, $surveyId, $sourceSurveyId = null, $consentCode = null)
+ {
+
+ }
+}
\ No newline at end of file
Added: trunk/library/controllers/OpenrosaController.php
===================================================================
--- trunk/library/controllers/OpenrosaController.php (rev 0)
+++ trunk/library/controllers/OpenrosaController.php 2013-03-22 14:02:16 UTC (rev 1203)
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Copyright (c) 2012, Erasmus MC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Erasmus MC nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WA...
[truncated message content] |