PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

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

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