PageRenderTime 55ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/app/protected/modules/zurmo/components/ZurmoModuleApiController.php

https://bitbucket.org/ddonthula/zurmofeb
PHP | 705 lines | 538 code | 50 blank | 117 comment | 53 complexity | 96dd917e0ec6b9bfc9142a9cdad31736 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-2-Clause, GPL-3.0, BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /*********************************************************************************
  3. * Zurmo is a customer relationship management program developed by
  4. * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
  5. *
  6. * Zurmo is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU General Public License version 3 as published by the
  8. * Free Software Foundation with the addition of the following permission added
  9. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  10. * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
  11. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  12. *
  13. * Zurmo is distributed in the hope that it will be useful, but WITHOUT
  14. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU General Public License along with
  19. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  20. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  21. * 02110-1301 USA.
  22. *
  23. * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
  24. * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
  25. ********************************************************************************/
  26. /**
  27. * Zurmo Modules api controllers
  28. * should extend this class to provide generic functionality
  29. * that is applicable to all standard api modules.
  30. */
  31. abstract class ZurmoModuleApiController extends ZurmoBaseController
  32. {
  33. const RIGHTS_FILTER_PATH = 'application.modules.api.utils.ApiRightsControllerFilter';
  34. public function filters()
  35. {
  36. $filters = array(
  37. 'apiRequest'
  38. );
  39. return array_merge($filters, parent::filters());
  40. }
  41. public function filterApiRequest($filterChain)
  42. {
  43. try
  44. {
  45. $filterChain->run();
  46. }
  47. catch (Exception $e)
  48. {
  49. $resultClassName = Yii::app()->apiRequest->getResultClassName();
  50. $result = new $resultClassName(ApiResponse::STATUS_FAILURE, null, $e->getMessage(), null);
  51. Yii::app()->apiHelper->sendResponse($result);
  52. }
  53. }
  54. /**
  55. * Get model and send response
  56. * @throws ApiException
  57. */
  58. public function actionRead()
  59. {
  60. $params = Yii::app()->apiRequest->getParams();
  61. if (!isset($params['id']))
  62. {
  63. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  64. throw new ApiException($message);
  65. }
  66. $result = $this->processRead((int)$params['id']);
  67. Yii::app()->apiHelper->sendResponse($result);
  68. }
  69. /**
  70. * Get array or models and send response
  71. */
  72. public function actionList()
  73. {
  74. $params = Yii::app()->apiRequest->getParams();
  75. $result = $this->processList($params);
  76. Yii::app()->apiHelper->sendResponse($result);
  77. }
  78. /**
  79. * Create new model, and send response
  80. * @throws ApiException
  81. */
  82. public function actionCreate()
  83. {
  84. $params = Yii::app()->apiRequest->getParams();
  85. if (!isset($params['data']))
  86. {
  87. $message = Zurmo::t('ZurmoModule', 'Please provide data.');
  88. throw new ApiException($message);
  89. }
  90. $result = $this->processCreate($params['data']);
  91. Yii::app()->apiHelper->sendResponse($result);
  92. }
  93. /**
  94. * Update model and send response
  95. * @throws ApiException
  96. */
  97. public function actionUpdate()
  98. {
  99. $params = Yii::app()->apiRequest->getParams();
  100. if (!isset($params['id']))
  101. {
  102. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  103. throw new ApiException($message);
  104. }
  105. $result = $this->processUpdate((int)$params['id'], $params['data']);
  106. Yii::app()->apiHelper->sendResponse($result);
  107. }
  108. /**
  109. * Delete model and send response
  110. * @throws ApiException
  111. */
  112. public function actionDelete()
  113. {
  114. $params = Yii::app()->apiRequest->getParams();
  115. if (!isset($params['id']))
  116. {
  117. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  118. throw new ApiException($message);
  119. }
  120. $result = $this->processDelete((int)$params['id']);
  121. Yii::app()->apiHelper->sendResponse($result);
  122. }
  123. /**
  124. * Add related model to model's relations
  125. */
  126. public function actionAddRelation()
  127. {
  128. $params = Yii::app()->apiRequest->getParams();
  129. $result = $this->processAddRelation($params);
  130. Yii::app()->apiHelper->sendResponse($result);
  131. }
  132. /**
  133. * Remove related model from model's relations
  134. */
  135. public function actionRemoveRelation()
  136. {
  137. $params = Yii::app()->apiRequest->getParams();
  138. $result = $this->processRemoveRelation($params);
  139. Yii::app()->apiHelper->sendResponse($result);
  140. }
  141. /**
  142. * Get module primary model name
  143. */
  144. protected function getModelName()
  145. {
  146. return $this->getModule()->getPrimaryModelName();
  147. }
  148. /**
  149. * Get model by id
  150. * @param int $id
  151. * @throws ApiException
  152. * @return ApiResult
  153. */
  154. protected function processRead($id)
  155. {
  156. assert('is_int($id)');
  157. $modelClassName = $this->getModelName();
  158. try
  159. {
  160. $model = $modelClassName::getById($id);
  161. }
  162. catch (NotFoundException $e)
  163. {
  164. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  165. throw new ApiException($message);
  166. }
  167. try
  168. {
  169. ApiControllerSecurityUtil::resolveAccessCanCurrentUserReadModel($model);
  170. }
  171. catch (SecurityException $e)
  172. {
  173. $message = $e->getMessage();
  174. throw new ApiException($message);
  175. }
  176. try
  177. {
  178. $redBeanModelToApiDataUtil = new RedBeanModelToApiDataUtil($model);
  179. $data = $redBeanModelToApiDataUtil->getData();
  180. $resultClassName = Yii::app()->apiRequest->getResultClassName();
  181. $result = new $resultClassName(ApiResponse::STATUS_SUCCESS, $data, null, null);
  182. }
  183. catch (Exception $e)
  184. {
  185. $message = $e->getMessage();
  186. throw new ApiException($message);
  187. }
  188. return $result;
  189. }
  190. protected static function getSearchFormClassName()
  191. {
  192. return null;
  193. }
  194. /**
  195. * List all models that satisfy provided criteria
  196. * @param array $params
  197. * @throws ApiException
  198. * @return ApiResult
  199. */
  200. protected function processList($params)
  201. {
  202. $modelClassName = $this->getModelName();
  203. $searchFormClassName = static::getSearchFormClassName();
  204. try
  205. {
  206. $filterParams = array();
  207. if (strtolower($_SERVER['REQUEST_METHOD']) != 'post')
  208. {
  209. if (isset($params['filter']) && $params['filter'] != '')
  210. {
  211. parse_str($params['filter'], $filterParams);
  212. }
  213. }
  214. else
  215. {
  216. $filterParams = $params['data'];
  217. }
  218. $pageSize = Yii::app()->pagination->getGlobalValueByType('apiListPageSize');
  219. if (isset($filterParams['pagination']['pageSize']))
  220. {
  221. $pageSize = (int)$filterParams['pagination']['pageSize'];
  222. }
  223. if (isset($filterParams['pagination']['page']))
  224. {
  225. $_GET[$modelClassName . '_page'] = (int)$filterParams['pagination']['page'];
  226. }
  227. if (isset($filterParams['sort']))
  228. {
  229. $_GET[$modelClassName . '_sort'] = $filterParams['sort'];
  230. }
  231. if (isset($filterParams['search']) && isset($searchFormClassName))
  232. {
  233. $_GET[$searchFormClassName] = $filterParams['search'];
  234. }
  235. if (isset($filterParams['dynamicSearch']) &&
  236. isset($searchFormClassName) &&
  237. !empty($filterParams['dynamicSearch']['dynamicClauses']) &&
  238. !empty($filterParams['dynamicSearch']['dynamicStructure']))
  239. {
  240. // Convert model ids into item ids, so we can perform dynamic search
  241. DynamicSearchUtil::resolveDynamicSearchClausesForModelIdsNeedingToBeItemIds($modelClassName, $filterParams['dynamicSearch']['dynamicClauses']);
  242. $_GET[$searchFormClassName]['dynamicClauses'] = $filterParams['dynamicSearch']['dynamicClauses'];
  243. $_GET[$searchFormClassName]['dynamicStructure'] = $filterParams['dynamicSearch']['dynamicStructure'];
  244. }
  245. $model = new $modelClassName(false);
  246. if (isset($searchFormClassName))
  247. {
  248. $searchForm = new $searchFormClassName($model);
  249. }
  250. else
  251. {
  252. throw new NotSupportedException();
  253. }
  254. // In case of ContactState model, we can't use Module::getStateMetadataAdapterClassName() function,
  255. // because it references to Contact model, so we defined new function
  256. // ContactsContactStateApiController::getStateMetadataAdapterClassName() which return null.
  257. if (method_exists($this, 'getStateMetadataAdapterClassName'))
  258. {
  259. $stateMetadataAdapterClassName = $this->getStateMetadataAdapterClassName();
  260. }
  261. else
  262. {
  263. $stateMetadataAdapterClassName = $this->getModule()->getStateMetadataAdapterClassName();
  264. }
  265. $dataProvider = $this->makeRedBeanDataProviderByDataCollection(
  266. $searchForm,
  267. $pageSize,
  268. $stateMetadataAdapterClassName
  269. );
  270. if (isset($filterParams['pagination']['page']) && (int)$filterParams['pagination']['page'] > 0)
  271. {
  272. $currentPage = (int)$filterParams['pagination']['page'];
  273. }
  274. else
  275. {
  276. $currentPage = 1;
  277. }
  278. $totalItems = $dataProvider->getTotalItemCount();
  279. $data = array();
  280. $data['totalCount'] = $totalItems;
  281. $data['currentPage'] = $currentPage;
  282. if ($totalItems > 0)
  283. {
  284. $formattedData = $dataProvider->getData();
  285. foreach ($formattedData as $model)
  286. {
  287. $redBeanModelToApiDataUtil = new RedBeanModelToApiDataUtil($model);
  288. $data['items'][] = $redBeanModelToApiDataUtil->getData();
  289. }
  290. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  291. }
  292. else
  293. {
  294. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  295. }
  296. }
  297. catch (Exception $e)
  298. {
  299. $message = $e->getMessage();
  300. throw new ApiException($message);
  301. }
  302. return $result;
  303. }
  304. /**
  305. * Add model relation
  306. * @param array $params
  307. * @throws ApiException
  308. * @return ApiResult
  309. */
  310. protected function processAddRelation($params)
  311. {
  312. $modelClassName = $this->getModelName();
  313. try
  314. {
  315. $data = array();
  316. if (isset($params['data']) && $params['data'] != '')
  317. {
  318. parse_str($params['data'], $data);
  319. }
  320. $relationName = $data['relationName'];
  321. $modelId = $data['id'];
  322. $relatedId = $data['relatedId'];
  323. $model = $modelClassName::getById(intval($modelId));
  324. $relatedModelClassName = $model->getRelationModelClassName($relationName);
  325. $relatedModel = $relatedModelClassName::getById(intval($relatedId));
  326. if ($model->getRelationType($relationName) == RedBeanModel::HAS_MANY ||
  327. $model->getRelationType($relationName) == RedBeanModel::MANY_MANY)
  328. {
  329. $model->{$relationName}->add($relatedModel);
  330. if ($model->save())
  331. {
  332. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, null);
  333. }
  334. else
  335. {
  336. $message = Zurmo::t('ZurmoModule', 'Could not save relation.');
  337. throw new ApiException($message);
  338. }
  339. }
  340. else
  341. {
  342. $message = Zurmo::t('ZurmoModule', 'Could not use this API call for HAS_ONE relationships.');
  343. throw new ApiException($message);
  344. }
  345. }
  346. catch (Exception $e)
  347. {
  348. $message = $e->getMessage();
  349. throw new ApiException($message);
  350. }
  351. return $result;
  352. }
  353. /**
  354. * Remove model relation
  355. * @param array $params
  356. * @throws ApiException
  357. * @return ApiResult
  358. */
  359. protected function processRemoveRelation($params)
  360. {
  361. $modelClassName = $this->getModelName();
  362. try
  363. {
  364. $data = array();
  365. if (isset($params['data']) && $params['data'] != '')
  366. {
  367. parse_str($params['data'], $data);
  368. }
  369. $relationName = $data['relationName'];
  370. $modelId = $data['id'];
  371. $relatedId = $data['relatedId'];
  372. $model = $modelClassName::getById(intval($modelId));
  373. $relatedModelClassName = $model->getRelationModelClassName($relationName);
  374. $relatedModel = $relatedModelClassName::getById(intval($relatedId));
  375. if ($model->getRelationType($relationName) == RedBeanModel::HAS_MANY ||
  376. $model->getRelationType($relationName) == RedBeanModel::MANY_MANY)
  377. {
  378. $model->{$relationName}->remove($relatedModel);
  379. if ($model->save())
  380. {
  381. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, null);
  382. }
  383. else
  384. {
  385. $message = Zurmo::t('ZurmoModule', 'Could not remove relation.');
  386. throw new ApiException($message);
  387. }
  388. }
  389. else
  390. {
  391. $message = Zurmo::t('ZurmoModule', 'Could not use this API call for HAS_ONE relationships.');
  392. throw new ApiException($message);
  393. }
  394. }
  395. catch (Exception $e)
  396. {
  397. $message = $e->getMessage();
  398. throw new ApiException($message);
  399. }
  400. return $result;
  401. }
  402. /**
  403. * Create new model
  404. * @param array $data
  405. * @throws ApiException
  406. */
  407. protected function processCreate($data)
  408. {
  409. $modelClassName = $this->getModelName();
  410. try
  411. {
  412. if (isset($data['modelRelations']))
  413. {
  414. $modelRelations = $data['modelRelations'];
  415. unset($data['modelRelations']);
  416. }
  417. $model = $this->attemptToSaveModelFromData(new $modelClassName, $data, null, false);
  418. $id = $model->id;
  419. $model->forget();
  420. if (!count($model->getErrors()))
  421. {
  422. if (isset($modelRelations) && count($modelRelations))
  423. {
  424. try
  425. {
  426. $this->manageModelRelations($model, $modelRelations);
  427. $model->save();
  428. }
  429. catch (Exception $e)
  430. {
  431. $model->delete();
  432. $message = $e->getMessage();
  433. throw new ApiException($message);
  434. }
  435. }
  436. $model = $modelClassName::getById($id);
  437. $redBeanModelToApiDataUtil = new RedBeanModelToApiDataUtil($model);
  438. $data = $redBeanModelToApiDataUtil->getData();
  439. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  440. }
  441. else
  442. {
  443. $errors = $model->getErrors();
  444. $message = Zurmo::t('ZurmoModule', 'Model was not created.');
  445. $result = new ApiResult(ApiResponse::STATUS_FAILURE, null, $message, $errors);
  446. }
  447. }
  448. catch (Exception $e)
  449. {
  450. $message = $e->getMessage();
  451. throw new ApiException($message);
  452. }
  453. return $result;
  454. }
  455. /**
  456. * Update model
  457. * @param int $id
  458. * @param array $data
  459. * @throws ApiException
  460. * @return ApiResult
  461. */
  462. protected function processUpdate($id, $data)
  463. {
  464. assert('is_int($id)');
  465. $modelClassName = $this->getModelName();
  466. if (isset($data['modelRelations']))
  467. {
  468. $modelRelations = $data['modelRelations'];
  469. unset($data['modelRelations']);
  470. }
  471. try
  472. {
  473. $model = $modelClassName::getById($id);
  474. }
  475. catch (NotFoundException $e)
  476. {
  477. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  478. throw new ApiException($message);
  479. }
  480. try
  481. {
  482. ApiControllerSecurityUtil::resolveAccessCanCurrentUserWriteModel($model);
  483. }
  484. catch (SecurityException $e)
  485. {
  486. $message = $e->getMessage();
  487. throw new ApiException($message);
  488. }
  489. try
  490. {
  491. $model = $this->attemptToSaveModelFromData($model, $data, null, false);
  492. $id = $model->id;
  493. if (!count($model->getErrors()))
  494. {
  495. if (isset($modelRelations) && count($modelRelations))
  496. {
  497. try
  498. {
  499. $this->manageModelRelations($model, $modelRelations);
  500. $model->save();
  501. }
  502. catch (Exception $e)
  503. {
  504. $message = Zurmo::t('ZurmoModule', 'Model was updated, but there were issues with relations.');
  505. $message .= ' ' . $e->getMessage();
  506. throw new ApiException($message);
  507. }
  508. }
  509. $model = $modelClassName::getById($id);
  510. $redBeanModelToApiDataUtil = new RedBeanModelToApiDataUtil($model);
  511. $data = $redBeanModelToApiDataUtil->getData();
  512. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  513. }
  514. else
  515. {
  516. $errors = $model->getErrors();
  517. $message = Zurmo::t('ZurmoModule', 'Model was not updated.');
  518. // To-Do: How to pass $errors and $message to exception
  519. //throw new ApiException($message);
  520. $result = new ApiResult(ApiResponse::STATUS_FAILURE, null, $message, $errors);
  521. }
  522. }
  523. catch (Exception $e)
  524. {
  525. $message = $e->getMessage();
  526. throw new ApiException($message);
  527. }
  528. return $result;
  529. }
  530. /**
  531. *
  532. * @param RedBeanModel $model
  533. * @param array $modelRelations
  534. * @throws NotSupportedException
  535. * @throws FailedToSaveModelException
  536. * @throws ApiException
  537. */
  538. protected function manageModelRelations($model, $modelRelations)
  539. {
  540. try
  541. {
  542. if (isset($modelRelations) && !empty($modelRelations))
  543. {
  544. foreach ($modelRelations as $modelRelation => $relations)
  545. {
  546. if ($model->isAttribute($modelRelation) &&
  547. ($model->getRelationType($modelRelation) == RedBeanModel::HAS_MANY ||
  548. $model->getRelationType($modelRelation) == RedBeanModel::MANY_MANY))
  549. {
  550. foreach ($relations as $relation)
  551. {
  552. $relatedModelClassName = $relation['modelClassName'];
  553. try
  554. {
  555. $relatedModel = $relatedModelClassName::getById(intval($relation['modelId']));
  556. }
  557. catch (Exception $e)
  558. {
  559. $message = Zurmo::t('ZurmoModule', 'The related model ID specified was invalid.');
  560. throw new NotFoundException($message);
  561. }
  562. if ($relation['action'] == 'add')
  563. {
  564. $model->{$modelRelation}->add($relatedModel);
  565. }
  566. elseif ($relation['action'] == 'remove')
  567. {
  568. $model->{$modelRelation}->remove($relatedModel);
  569. }
  570. else
  571. {
  572. $message = Zurmo::t('ZurmoModule', 'Unsupported action.');
  573. throw new NotSupportedException($message);
  574. }
  575. }
  576. }
  577. else
  578. {
  579. $message = Zurmo::t('ZurmoModule', 'You can add relations only for HAS_MANY and MANY_MANY relations.');
  580. throw new NotSupportedException($message);
  581. }
  582. }
  583. }
  584. }
  585. catch (Exception $e)
  586. {
  587. $message = $e->getMessage();
  588. throw new ApiException($message);
  589. }
  590. return true;
  591. }
  592. /**
  593. * Delete model
  594. * @param int $id
  595. * @throws ApiException
  596. * @return ApiResult
  597. */
  598. protected function processDelete($id)
  599. {
  600. assert('is_int($id)');
  601. $modelClassName = $this->getModelName();
  602. try
  603. {
  604. $model = $modelClassName::getById($id);
  605. }
  606. catch (NotFoundException $e)
  607. {
  608. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  609. throw new ApiException($message);
  610. }
  611. try
  612. {
  613. ApiControllerSecurityUtil::resolveAccessCanCurrentUserDeleteModel($model);
  614. }
  615. catch (SecurityException $e)
  616. {
  617. $message = $e->getMessage();
  618. throw new ApiException($message);
  619. }
  620. try
  621. {
  622. $model->delete();
  623. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, null);
  624. }
  625. catch (Exception $e)
  626. {
  627. $message = $e->getMessage();
  628. throw new ApiException($message);
  629. }
  630. return $result;
  631. }
  632. /**
  633. * Instead of saving from post, we are saving from the API data.
  634. * @see attemptToSaveModelFromPost
  635. */
  636. protected function attemptToSaveModelFromData($model, $data, $redirectUrlParams = null, $redirect = true)
  637. {
  638. assert('is_array($data)');
  639. assert('$redirectUrlParams == null || is_array($redirectUrlParams) || is_string($redirectUrlParams)');
  640. $savedSucessfully = false;
  641. $modelToStringValue = null;
  642. if (isset($data))
  643. {
  644. $controllerUtil = new ZurmoControllerUtil();
  645. $model = $controllerUtil->saveModelFromSanitizedData($data, $model,
  646. $savedSucessfully, $modelToStringValue);
  647. }
  648. if ($savedSucessfully && $redirect)
  649. {
  650. $this->actionAfterSuccessfulModelSave($model, $modelToStringValue, $redirectUrlParams);
  651. }
  652. return $model;
  653. }
  654. }
  655. ?>