/src/lib/Zikula/Workflow/Parser.php

https://github.com/philipp2/core · PHP · 400 lines · 267 code · 39 blank · 94 comment · 42 complexity · a79dfaf5a8228eac1c29fbcbc7d06db5 MD5 · raw file

  1. <?php
  2. /**
  3. * Copyright Zikula Foundation 2009 - Zikula Application Framework
  4. *
  5. * This work is contributed to the Zikula Foundation under one or more
  6. * Contributor Agreements and licensed to You under the following license:
  7. *
  8. * @license GNU/LGPL3 (or at your option any later version).
  9. * @package Zikula
  10. *
  11. * Please see the NOTICE file distributed with this source code for further
  12. * information regarding copyright and licensing.
  13. */
  14. /**
  15. * Zikula_Workflow_Parser.
  16. *
  17. * Parse workflow schema into associative arrays.
  18. */
  19. class Zikula_Workflow_Parser
  20. {
  21. // Declare object variables;
  22. /**
  23. * XML parser object.
  24. *
  25. * @var object
  26. */
  27. protected $parser;
  28. /**
  29. * Workflow data.
  30. *
  31. * @var array
  32. */
  33. protected $workflow;
  34. /**
  35. * Parse workflow into array format.
  36. */
  37. public function __construct()
  38. {
  39. $this->workflow = array('state' => 'initial');
  40. // create xml parser
  41. $this->parser = xml_parser_create();
  42. xml_set_object($this->parser, $this);
  43. xml_set_element_handler($this->parser, 'startElement', 'endElement');
  44. xml_set_character_data_handler($this->parser, 'characterData');
  45. }
  46. /**
  47. * parse xml
  48. *
  49. * @param string $xmldata XML data.
  50. * @param string $schemaName Schema name.
  51. * @param string $module Module name.
  52. *
  53. * @return mixed Associative array of workflow or false.
  54. */
  55. public function parse($xmldata, $schemaName, $module)
  56. {
  57. // parse XML
  58. if (!xml_parse($this->parser, $xmldata, true)) {
  59. xml_parser_free($this->parser);
  60. z_exit(__f('Unable to parse XML workflow (line %1$s, %2$s): %3$s',
  61. array(xml_get_current_line_number($this->parser),
  62. xml_get_current_column_number($this->parser),
  63. xml_error_string($this->parser))));
  64. }
  65. // close parser
  66. xml_parser_free($this->parser);
  67. // check for errors
  68. if ($this->workflow['state'] == 'error') {
  69. return LogUtil::registerError($this->workflow['errorMessage']);
  70. }
  71. $this->mapWorkflow();
  72. if (!$this->validate()) {
  73. return false;
  74. }
  75. $this->workflow['workflow']['module'] = $module;
  76. $this->workflow['workflow']['id'] = $schemaName;
  77. return $this->workflow;
  78. }
  79. /**
  80. * Map workflow.
  81. *
  82. * Marshall data in to meaningful associative arrays.
  83. *
  84. * @return void
  85. */
  86. public function mapWorkflow()
  87. {
  88. // create associative arrays maps
  89. $stateMap = array();
  90. $actionMap = array();
  91. foreach ($this->workflow['states'] as $state) {
  92. $stateMap[$state['id']] = array('id' => $state['id'],
  93. 'title' => $state['title'],
  94. 'description' => $state['description']);
  95. }
  96. $states = array_keys($stateMap);
  97. foreach ($this->workflow['actions'] as $action) {
  98. if (($action['state'] == 'initial') || ($action['state'] == null) || in_array($action['state'], $states)) {
  99. $action['state'] = $stateID = !empty($action['state']) ? $action['state'] : 'initial';
  100. // change the case of array keys for parameter variables
  101. $operations = &$action['operations'];
  102. foreach (array_keys($operations) as $key) {
  103. $parameters = &$operations[$key]['parameters'];
  104. $parameters = array_change_key_case($parameters, CASE_LOWER);
  105. }
  106. // commit results
  107. $actionMap[$stateID][$action['id']] = $action;
  108. } else {
  109. LogUtil::registerError(__f('Unknown %1$s name \'%2$s\' in action \'%3$s\'.', array('state', $action['state'], $action['title'])));
  110. }
  111. }
  112. // commit new array to workflow
  113. $this->workflow['states'] = $stateMap;
  114. $this->workflow['actions'] = $actionMap;
  115. }
  116. /**
  117. * Validate workflow actions.
  118. *
  119. * @return boolean
  120. */
  121. public function validate()
  122. {
  123. foreach (array_keys($this->workflow['actions']) as $stateID) {
  124. foreach ($this->workflow['actions'][$stateID] as $action) {
  125. if (isset($action['nextState']) && !isset($this->workflow['states'][$action['nextState']])) {
  126. return LogUtil::registerError(__f('Unknown %1$s name \'%2$s\' in action \'%3$s\' of the \'%4$s\' state.', array('next-state', $action['nextState'], $action['title'], $action['state'])));
  127. }
  128. foreach ($action['operations'] as $operation) {
  129. if (isset($operation['parameters']['nextState'])) {
  130. $stateName = $operation['parameters']['nextState'];
  131. if (!isset($this->workflow['states'][$stateName])) {
  132. return LogUtil::registerError(__f('Unknown state name \'%1$s\' in action \'%2$s\', operation \'%3$s\'.', array($stateName, $action['title'], $operation['name'])));
  133. }
  134. }
  135. }
  136. }
  137. }
  138. return true;
  139. }
  140. /**
  141. * XML start element handler.
  142. *
  143. * @param object $parser Parser object.
  144. * @param string $name Element name.
  145. * @param array $attribs Element attributes.
  146. *
  147. * @return void
  148. */
  149. public function startElement($parser, $name, $attribs)
  150. {
  151. $name = strtoupper($name);
  152. $state = &$this->workflow['state'];
  153. switch ($state) {
  154. case 'initial':
  155. if ($name == 'WORKFLOW') {
  156. $state = 'workflow';
  157. $this->workflow['workflow'] = array();
  158. } else {
  159. $state = 'error';
  160. $this->workflow['errorMessage'] = $this->unexpectedXMLError($name, "$state ". __LINE__);
  161. }
  162. break;
  163. case 'workflow':
  164. switch ($name) {
  165. case 'TITLE':
  166. case 'DESCRIPTION':
  167. $this->workflow['value'] = '';
  168. break;
  169. case 'STATES':
  170. $state = 'states';
  171. $this->workflow['states'] = array();
  172. break;
  173. case 'ACTIONS':
  174. $state = 'actions';
  175. $this->workflow['actions'] = array();
  176. break;
  177. default:
  178. $this->workflow['errorMessage'] = $this->unexpectedXMLError($name, "$state ". __LINE__);
  179. $state = 'error';
  180. break;
  181. }
  182. break;
  183. case 'states':
  184. if ($name == 'STATE') {
  185. $this->workflow['stateValue'] = array('id' => trim($attribs['ID']));
  186. $state = 'state';
  187. } else {
  188. $this->workflow['errorMessage'] = $this->unexpectedXMLError($name, "$state ". __LINE__);
  189. $state = 'error';
  190. }
  191. break;
  192. case 'state':
  193. if ($name == 'TITLE' || $name == 'DESCRIPTION') {
  194. $this->workflow['value'] = '';
  195. } else {
  196. $this->workflow['errorMessage'] = $this->unexpectedXMLError($name, "$state ". __LINE__);
  197. $state = 'error';
  198. }
  199. break;
  200. case 'actions':
  201. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  202. if ($name == 'ACTION') {
  203. $this->workflow['action'] = array('id' => trim($attribs['ID']),
  204. 'operations' => array(),
  205. 'state' => null);
  206. $state = 'action';
  207. } else {
  208. $this->workflow['errorMessage'] = $this->unexpectedXMLError($name, "$state ". __LINE__);
  209. $state = 'error';
  210. }
  211. break;
  212. case 'action':
  213. switch ($name) {
  214. case 'TITLE':
  215. case 'DESCRIPTION':
  216. case 'PERMISSION':
  217. case 'STATE':
  218. case 'NEXTSTATE':
  219. $this->workflow['value'] = '';
  220. break;
  221. case 'OPERATION':
  222. $this->workflow['value'] = '';
  223. $this->workflow['operationParameters'] = $attribs;
  224. break;
  225. case 'PARAMETER':
  226. $this->workflow['value'] = '';
  227. $this->workflow['actionParameter'] = $attribs;
  228. break;
  229. default:
  230. $this->workflow['errorMessage'] = $this->unexpectedXMLError($name, "$state ". __LINE__);
  231. $state = 'error';
  232. break;
  233. }
  234. break;
  235. case '':
  236. if ($name == '') {
  237. $state = '';
  238. } else {
  239. $this->workflow['errorMessage'] = $this->unexpectedXMLError($name, "$state ". __LINE__);
  240. $state = 'error';
  241. }
  242. break;
  243. case 'error':
  244. // ignore
  245. break;
  246. default:
  247. $this->workflow['errorMessage'] = __f('Workflow state error: \'%2$s\' tag of the state \'%1$s\'.', array($state, $name));
  248. $state = 'error';
  249. break;
  250. }
  251. }
  252. /**
  253. * XML end element handler.
  254. *
  255. * @param object $parser Parser object.
  256. * @param string $name Element name.
  257. *
  258. * @return void
  259. */
  260. public function endElement($parser, $name)
  261. {
  262. $name = strtoupper($name);
  263. $state = &$this->workflow['state'];
  264. switch ($state) {
  265. case 'workflow':
  266. switch ($name) {
  267. case 'TITLE':
  268. $this->workflow['workflow']['title'] = $this->workflow['value'];
  269. break;
  270. case 'DESCRIPTION':
  271. $this->workflow['workflow']['description'] = $this->workflow['value'];
  272. break;
  273. }
  274. break;
  275. case 'state':
  276. switch ($name) {
  277. case 'TITLE':
  278. $this->workflow['stateValue']['title'] = $this->workflow['value'];
  279. break;
  280. case 'DESCRIPTION':
  281. $this->workflow['stateValue']['description'] = $this->workflow['value'];
  282. break;
  283. case 'STATE':
  284. $this->workflow['states'][] = $this->workflow['stateValue'];
  285. $this->workflow['stateValue'] = null;
  286. $state = 'states';
  287. break;
  288. }
  289. break;
  290. case 'action':
  291. switch ($name) {
  292. case 'TITLE':
  293. $this->workflow['action']['title'] = $this->workflow['value'];
  294. break;
  295. case 'DESCRIPTION':
  296. $this->workflow['action']['description'] = $this->workflow['value'];
  297. break;
  298. case 'PERMISSION':
  299. $this->workflow['action']['permission'] = trim($this->workflow['value']);
  300. break;
  301. case 'STATE':
  302. $this->workflow['action']['state'] = trim($this->workflow['value']);
  303. break;
  304. case 'NEXTSTATE':
  305. $this->workflow['action']['nextState'] = trim($this->workflow['value']);
  306. break;
  307. case 'OPERATION':
  308. $this->workflow['action']['operations'][] = array('name' => trim($this->workflow['value']),
  309. 'parameters' => $this->workflow['operationParameters']);
  310. $this->workflow['operation'] = null;
  311. break;
  312. case 'PARAMETER':
  313. $this->workflow['action']['parameters'][trim($this->workflow['value'])] = $this->workflow['actionParameter'];
  314. break;
  315. case 'ACTION':
  316. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 1);
  317. $this->workflow['actions'][] = $this->workflow['action'];
  318. $this->workflow['action'] = null;
  319. $state = 'actions';
  320. break;
  321. }
  322. break;
  323. case 'actions':
  324. if ($name == 'ACTIONS') {
  325. $state = 'workflow';
  326. }
  327. break;
  328. case 'states':
  329. if ($name == 'STATES') {
  330. $state = 'workflow';
  331. }
  332. break;
  333. }
  334. }
  335. /**
  336. * XML data element handler.
  337. *
  338. * @param object $parser Parser object.
  339. * @param string $data Character data.
  340. *
  341. * @return boolean True.
  342. */
  343. public function characterData($parser, $data)
  344. {
  345. $value = &$this->workflow['value'];
  346. $value .= $data;
  347. return true;
  348. }
  349. /**
  350. * Hander for unexpected XML errors.
  351. *
  352. * @param string $name Tag name.
  353. * @param string $state Workflow state.
  354. *
  355. * @return string
  356. */
  357. public function unexpectedXMLError($name, $state)
  358. {
  359. return __f('Unexpected %1$s tag in %2$s state', array($name, $state));
  360. }
  361. }