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] |