PageRenderTime 48ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://bitbucket.org/andreustimm/zurmo
PHP | 1179 lines | 891 code | 84 blank | 204 comment | 97 complexity | 6de55c7cb9ea6df1aba1f12594a7b25b MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-3.0, LGPL-2.1, BSD-2-Clause
  1. <?php
  2. /*********************************************************************************
  3. * Zurmo is a customer relationship management program developed by
  4. * Zurmo, Inc. Copyright (C) 2015 Zurmo Inc.
  5. *
  6. * Zurmo is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero 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 Affero General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU Affero 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 27 North Wacker Drive
  24. * Suite 370 Chicago, IL 60606. or at email address contact@zurmo.com.
  25. *
  26. * The interactive user interfaces in original and modified versions
  27. * of this program must display Appropriate Legal Notices, as required under
  28. * Section 5 of the GNU Affero General Public License version 3.
  29. *
  30. * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  31. * these Appropriate Legal Notices must retain the display of the Zurmo
  32. * logo and Zurmo copyright notice. If the display of the logo is not reasonably
  33. * feasible for technical reasons, the Appropriate Legal Notices must display the words
  34. * "Copyright Zurmo Inc. 2015. All rights reserved".
  35. ********************************************************************************/
  36. /**
  37. * Zurmo Modules api controllers
  38. * should extend this class to provide generic functionality
  39. * that is applicable to all standard api modules.
  40. */
  41. abstract class ZurmoModuleApiController extends ZurmoBaseController
  42. {
  43. const RIGHTS_FILTER_PATH = 'application.modules.api.utils.ApiRightsControllerFilter';
  44. public function filters()
  45. {
  46. $filters = array(
  47. 'apiRequest'
  48. );
  49. return array_merge($filters, parent::filters());
  50. }
  51. public function filterApiRequest($filterChain)
  52. {
  53. try
  54. {
  55. $filterChain->run();
  56. }
  57. catch (Exception $e)
  58. {
  59. $resultClassName = Yii::app()->apiRequest->getResultClassName();
  60. $result = new $resultClassName(ApiResponse::STATUS_FAILURE, null, $e->getMessage(), null);
  61. Yii::app()->apiHelper->sendResponse($result);
  62. }
  63. }
  64. /**
  65. * Get model and send response
  66. * @throws ApiException
  67. */
  68. public function actionRead()
  69. {
  70. $params = Yii::app()->apiRequest->getParams();
  71. if (!isset($params['id']))
  72. {
  73. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  74. throw new ApiException($message);
  75. }
  76. $result = $this->processRead((int)$params['id']);
  77. Yii::app()->apiHelper->sendResponse($result);
  78. }
  79. /**
  80. * Get array or models and send response
  81. */
  82. public function actionList()
  83. {
  84. $params = Yii::app()->apiRequest->getParams();
  85. $result = $this->processList($params);
  86. Yii::app()->apiHelper->sendResponse($result);
  87. }
  88. /**
  89. * Get array or models and send response
  90. */
  91. public function actionListAttributes()
  92. {
  93. $params = Yii::app()->apiRequest->getParams();
  94. $result = $this->processListAttributes($params);
  95. Yii::app()->apiHelper->sendResponse($result);
  96. }
  97. /**
  98. * Get array or models and send response
  99. */
  100. public function actionSearch()
  101. {
  102. $params = Yii::app()->apiRequest->getParams();
  103. $result = $this->processSearch($params);
  104. Yii::app()->apiHelper->sendResponse($result);
  105. }
  106. /**
  107. * Create new model, and send response
  108. * @throws ApiException
  109. */
  110. public function actionCreate()
  111. {
  112. $params = Yii::app()->apiRequest->getParams();
  113. if (!isset($params['data']))
  114. {
  115. $message = Zurmo::t('ZurmoModule', 'Please provide data.');
  116. throw new ApiException($message);
  117. }
  118. $result = $this->processCreate($params['data']);
  119. Yii::app()->apiHelper->sendResponse($result);
  120. }
  121. /**
  122. * Update model and send response
  123. * @throws ApiException
  124. */
  125. public function actionUpdate()
  126. {
  127. $params = Yii::app()->apiRequest->getParams();
  128. if (!isset($params['id']))
  129. {
  130. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  131. throw new ApiException($message);
  132. }
  133. $result = $this->processUpdate((int)$params['id'], $params['data']);
  134. Yii::app()->apiHelper->sendResponse($result);
  135. }
  136. /**
  137. * Delete model and send response
  138. * @throws ApiException
  139. */
  140. public function actionDelete()
  141. {
  142. $params = Yii::app()->apiRequest->getParams();
  143. if (!isset($params['id']))
  144. {
  145. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  146. throw new ApiException($message);
  147. }
  148. $result = $this->processDelete((int)$params['id']);
  149. Yii::app()->apiHelper->sendResponse($result);
  150. }
  151. /**
  152. * Add related model to model's relations
  153. */
  154. public function actionAddRelation()
  155. {
  156. $params = Yii::app()->apiRequest->getParams();
  157. $result = $this->processAddRelation($params);
  158. Yii::app()->apiHelper->sendResponse($result);
  159. }
  160. /**
  161. * Remove related model from model's relations
  162. */
  163. public function actionRemoveRelation()
  164. {
  165. $params = Yii::app()->apiRequest->getParams();
  166. $result = $this->processRemoveRelation($params);
  167. Yii::app()->apiHelper->sendResponse($result);
  168. }
  169. /**
  170. * Get module primary model name
  171. */
  172. protected function getModelName()
  173. {
  174. return $this->getModule()->getPrimaryModelName();
  175. }
  176. /**
  177. * Get model by id
  178. * @param int $id
  179. * @throws ApiException
  180. * @return ApiResult
  181. */
  182. protected function processRead($id)
  183. {
  184. assert('is_int($id)');
  185. $modelClassName = $this->getModelName();
  186. try
  187. {
  188. $model = $modelClassName::getById($id);
  189. }
  190. catch (NotFoundException $e)
  191. {
  192. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  193. throw new ApiException($message);
  194. }
  195. try
  196. {
  197. ApiControllerSecurityUtil::resolveAccessCanCurrentUserReadModel($model);
  198. }
  199. catch (SecurityException $e)
  200. {
  201. $message = $e->getMessage();
  202. throw new ApiException($message);
  203. }
  204. try
  205. {
  206. $data = static::getModelToApiDataUtilData($model);
  207. $resultClassName = Yii::app()->apiRequest->getResultClassName();
  208. $result = new $resultClassName(ApiResponse::STATUS_SUCCESS, $data, null, null);
  209. }
  210. catch (Exception $e)
  211. {
  212. $message = $e->getMessage();
  213. throw new ApiException($message);
  214. }
  215. return $result;
  216. }
  217. protected static function getSearchFormClassName()
  218. {
  219. return null;
  220. }
  221. /**
  222. * List all models that satisfy provided criteria
  223. * @param array $params
  224. * @throws ApiException
  225. * @return ApiResult
  226. */
  227. protected function processList($params)
  228. {
  229. $modelClassName = $this->getModelName();
  230. $searchFormClassName = static::getSearchFormClassName();
  231. try
  232. {
  233. $filterParams = array();
  234. if (strtolower($_SERVER['REQUEST_METHOD']) != 'post')
  235. {
  236. if (isset($params['filter']) && $params['filter'] != '')
  237. {
  238. parse_str($params['filter'], $filterParams);
  239. }
  240. }
  241. else
  242. {
  243. $filterParams = $params['data'];
  244. }
  245. $pageSize = Yii::app()->pagination->getGlobalValueByType('apiListPageSize');
  246. if (isset($filterParams['pagination']['pageSize']))
  247. {
  248. $pageSize = (int)$filterParams['pagination']['pageSize'];
  249. }
  250. if (isset($filterParams['pagination']['page']))
  251. {
  252. $_GET[$modelClassName . '_page'] = (int)$filterParams['pagination']['page'];
  253. }
  254. if (isset($filterParams['sort']))
  255. {
  256. $_GET[$modelClassName . '_sort'] = $filterParams['sort'];
  257. }
  258. if (isset($filterParams['search']) && isset($searchFormClassName))
  259. {
  260. $_GET[$searchFormClassName] = $filterParams['search'];
  261. }
  262. if (isset($filterParams['dynamicSearch']) &&
  263. isset($searchFormClassName) &&
  264. !empty($filterParams['dynamicSearch']['dynamicClauses']) &&
  265. !empty($filterParams['dynamicSearch']['dynamicStructure']))
  266. {
  267. // Convert model ids into item ids, so we can perform dynamic search
  268. DynamicSearchUtil::resolveDynamicSearchClausesForModelIdsNeedingToBeItemIds($modelClassName, $filterParams['dynamicSearch']['dynamicClauses']);
  269. $_GET[$searchFormClassName]['dynamicClauses'] = $filterParams['dynamicSearch']['dynamicClauses'];
  270. $_GET[$searchFormClassName]['dynamicStructure'] = $filterParams['dynamicSearch']['dynamicStructure'];
  271. }
  272. $model = new $modelClassName(false);
  273. if (isset($searchFormClassName))
  274. {
  275. $searchForm = new $searchFormClassName($model);
  276. }
  277. else
  278. {
  279. throw new NotSupportedException();
  280. }
  281. $stateMetadataAdapterClassName = $this->resolveStateMetadataAdapterClassName();
  282. $dataProvider = $this->makeRedBeanDataProviderByDataCollection(
  283. $searchForm,
  284. $pageSize,
  285. $stateMetadataAdapterClassName
  286. );
  287. if (isset($filterParams['pagination']['page']) && (int)$filterParams['pagination']['page'] > 0)
  288. {
  289. $currentPage = (int)$filterParams['pagination']['page'];
  290. }
  291. else
  292. {
  293. $currentPage = 1;
  294. }
  295. $totalItems = $dataProvider->getTotalItemCount();
  296. $data = array();
  297. $data['totalCount'] = $totalItems;
  298. $data['currentPage'] = $currentPage;
  299. if ($totalItems > 0)
  300. {
  301. $formattedData = $dataProvider->getData();
  302. foreach ($formattedData as $model)
  303. {
  304. $data['items'][] = static::getModelToApiDataUtilData($model);
  305. }
  306. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  307. }
  308. else
  309. {
  310. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  311. }
  312. }
  313. catch (Exception $e)
  314. {
  315. $message = $e->getMessage();
  316. throw new ApiException($message);
  317. }
  318. return $result;
  319. }
  320. /**
  321. * List all model attributes
  322. * @param $params
  323. * @return ApiResult
  324. * @throws ApiException
  325. */
  326. protected function processListAttributes($params)
  327. {
  328. $data = array();
  329. try
  330. {
  331. $modelClassName = $this->getModelName();
  332. $model = new $modelClassName();
  333. $adapter = new ModelAttributesAdapter($model);
  334. $customAttributes = ArrayUtil::subValueSort($adapter->getCustomAttributes(), 'attributeLabel', 'asort');
  335. $standardAttributes = ArrayUtil::subValueSort($adapter->getStandardAttributes(), 'attributeLabel', 'asort');
  336. $allAttributes = array_merge($customAttributes, $standardAttributes);
  337. $data['items'] = $allAttributes;
  338. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  339. }
  340. catch (Exception $e)
  341. {
  342. $message = $e->getMessage();
  343. throw new ApiException($message);
  344. }
  345. return $result;
  346. }
  347. /**
  348. * Search and list all models that satisfy provided criteria
  349. * @param array $params
  350. * @throws ApiException
  351. * @return ApiResult
  352. */
  353. protected function processSearch($params)
  354. {
  355. try
  356. {
  357. $filterParams = array();
  358. if (strtolower($_SERVER['REQUEST_METHOD']) != 'post')
  359. {
  360. if (isset($params['filter']) && $params['filter'] != '')
  361. {
  362. parse_str($params['filter'], $filterParams);
  363. }
  364. }
  365. else
  366. {
  367. $filterParams = $params['data'];
  368. }
  369. // Check if modelClassName exist and if it is subclass of RedBeanModel
  370. if (@class_exists($filterParams['search']['modelClassName']))
  371. {
  372. $modelClassName = $filterParams['search']['modelClassName'];
  373. @$modelClass = new $modelClassName();
  374. if (!($modelClass instanceof RedBeanModel))
  375. {
  376. $message = Zurmo::t('ZurmoModule', '{modelClassName} should be subclass of RedBeanModel.',
  377. array('{modelClassName}' => $modelClassName));
  378. throw new NotSupportedException($message);
  379. }
  380. }
  381. else
  382. {
  383. $message = Zurmo::t('ZurmoModule', "{modelClassName} class does not exist.",
  384. array('{modelClassName}' => $filterParams['search']['modelClassName']));
  385. throw new NotSupportedException($message);
  386. }
  387. $pageSize = Yii::app()->pagination->getGlobalValueByType('apiListPageSize');
  388. if (isset($filterParams['pagination']['pageSize']))
  389. {
  390. $pageSize = (int)$filterParams['pagination']['pageSize'];
  391. }
  392. // Get offset. Please note that API client provide page number, and we need to convert it into offset,
  393. // which is parameter of RedBeanModel::getSubset function
  394. if (isset($filterParams['pagination']['page']) && (int)$filterParams['pagination']['page'] > 0)
  395. {
  396. $currentPage = (int)$filterParams['pagination']['page'];
  397. }
  398. else
  399. {
  400. $currentPage = 1;
  401. }
  402. $offset = $this->getOffsetFromCurrentPageAndPageSize($currentPage, $pageSize);
  403. $sort = null;
  404. if (isset($filterParams['sort']))
  405. {
  406. $sort = $filterParams['sort'];
  407. }
  408. $stateMetadataAdapterClassName = $this->resolveStateMetadataAdapterClassName();
  409. if ($stateMetadataAdapterClassName != null)
  410. {
  411. $stateMetadataAdapter = new $stateMetadataAdapterClassName($filterParams['search']['searchAttributeData']);
  412. $filterParams['search']['searchAttributeData'] = $stateMetadataAdapter->getAdaptedDataProviderMetadata();
  413. $filterParams['search']['searchAttributeData']['structure'] = '(' . $filterParams['search']['searchAttributeData']['structure'] . ')';
  414. }
  415. $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
  416. $where = RedBeanModelDataProvider::makeWhere($modelClassName,
  417. $filterParams['search']['searchAttributeData'], $joinTablesAdapter);
  418. $results = $modelClassName::getSubset($joinTablesAdapter,
  419. $offset, $pageSize, $where, $sort, $modelClassName, true);
  420. $totalItems = $modelClassName::getCount($joinTablesAdapter, $where, null, true);
  421. $data = array();
  422. $data['totalCount'] = $totalItems;
  423. $data['currentPage'] = $currentPage;
  424. if ($totalItems > 0)
  425. {
  426. foreach ($results as $model)
  427. {
  428. $data['items'][] = static::getModelToApiDataUtilData($model);
  429. }
  430. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  431. }
  432. else
  433. {
  434. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  435. }
  436. }
  437. catch (Exception $e)
  438. {
  439. $message = $e->getMessage();
  440. throw new ApiException($message);
  441. }
  442. return $result;
  443. }
  444. /**
  445. * @param $currentPage
  446. * @param $pageSize
  447. * @return integer || null
  448. */
  449. protected function getOffsetFromCurrentPageAndPageSize($currentPage, $pageSize)
  450. {
  451. $offset = (int)(($currentPage - 1) * $pageSize);
  452. if ($offset == 0)
  453. {
  454. $offset = null;
  455. }
  456. return $offset;
  457. }
  458. /**
  459. * Add model relation
  460. * @param array $params
  461. * @throws ApiException
  462. * @return ApiResult
  463. */
  464. protected function processAddRelation($params)
  465. {
  466. $modelClassName = $this->getModelName();
  467. try
  468. {
  469. $data = array();
  470. if (isset($params['data']) && $params['data'] != '')
  471. {
  472. parse_str($params['data'], $data);
  473. }
  474. $relationName = $data['relationName'];
  475. $modelId = $data['id'];
  476. $relatedId = $data['relatedId'];
  477. $model = $modelClassName::getById(intval($modelId));
  478. $relatedModelClassName = $model->getRelationModelClassName($relationName);
  479. $relatedModel = $relatedModelClassName::getById(intval($relatedId));
  480. if ($model->getRelationType($relationName) == RedBeanModel::HAS_MANY ||
  481. $model->getRelationType($relationName) == RedBeanModel::MANY_MANY)
  482. {
  483. $model->{$relationName}->add($relatedModel);
  484. if ($model->save())
  485. {
  486. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, null);
  487. }
  488. else
  489. {
  490. $message = Zurmo::t('ZurmoModule', 'Could not save relation.');
  491. throw new ApiException($message);
  492. }
  493. }
  494. else
  495. {
  496. $message = Zurmo::t('ZurmoModule', 'Could not use this API call for HAS_ONE relationships.');
  497. throw new ApiException($message);
  498. }
  499. }
  500. catch (Exception $e)
  501. {
  502. $message = $e->getMessage();
  503. throw new ApiException($message);
  504. }
  505. return $result;
  506. }
  507. /**
  508. * Remove model relation
  509. * @param array $params
  510. * @throws ApiException
  511. * @return ApiResult
  512. */
  513. protected function processRemoveRelation($params)
  514. {
  515. $modelClassName = $this->getModelName();
  516. try
  517. {
  518. $data = array();
  519. if (isset($params['data']) && $params['data'] != '')
  520. {
  521. parse_str($params['data'], $data);
  522. }
  523. $relationName = $data['relationName'];
  524. $modelId = $data['id'];
  525. $relatedId = $data['relatedId'];
  526. $model = $modelClassName::getById(intval($modelId));
  527. $relatedModelClassName = $model->getRelationModelClassName($relationName);
  528. $relatedModel = $relatedModelClassName::getById(intval($relatedId));
  529. if ($model->getRelationType($relationName) == RedBeanModel::HAS_MANY ||
  530. $model->getRelationType($relationName) == RedBeanModel::MANY_MANY)
  531. {
  532. $model->{$relationName}->remove($relatedModel);
  533. if ($model->save())
  534. {
  535. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, null);
  536. }
  537. else
  538. {
  539. $message = Zurmo::t('ZurmoModule', 'Could not remove relation.');
  540. throw new ApiException($message);
  541. }
  542. }
  543. else
  544. {
  545. $message = Zurmo::t('ZurmoModule', 'Could not use this API call for HAS_ONE relationships.');
  546. throw new ApiException($message);
  547. }
  548. }
  549. catch (Exception $e)
  550. {
  551. $message = $e->getMessage();
  552. throw new ApiException($message);
  553. }
  554. return $result;
  555. }
  556. /**
  557. * Create new model
  558. * @param $data
  559. * @return ApiResult
  560. * @throws ApiException
  561. */
  562. protected function processCreate($data)
  563. {
  564. $modelClassName = $this->getModelName();
  565. try
  566. {
  567. if (isset($data['modelRelations']))
  568. {
  569. $modelRelations = $data['modelRelations'];
  570. unset($data['modelRelations']);
  571. }
  572. $model = new $modelClassName();
  573. $this->setModelScenarioFromData($model, $data);
  574. $model = $this->attemptToSaveModelFromData($model, $data, null, false);
  575. $id = $model->id;
  576. $model->forget();
  577. if (!count($model->getErrors()))
  578. {
  579. if (isset($modelRelations) && count($modelRelations))
  580. {
  581. try
  582. {
  583. $this->manageModelRelations($model, $modelRelations);
  584. $model->save();
  585. }
  586. catch (Exception $e)
  587. {
  588. $model->delete();
  589. $message = $e->getMessage();
  590. throw new ApiException($message);
  591. }
  592. }
  593. $model = $modelClassName::getById($id);
  594. $data = static::getModelToApiDataUtilData($model);
  595. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  596. }
  597. else
  598. {
  599. $errors = $model->getErrors();
  600. $message = Zurmo::t('ZurmoModule', 'Model was not created.');
  601. $result = new ApiResult(ApiResponse::STATUS_FAILURE, null, $message, $errors);
  602. }
  603. }
  604. catch (Exception $e)
  605. {
  606. $message = $e->getMessage();
  607. throw new ApiException($message);
  608. }
  609. return $result;
  610. }
  611. /**
  612. * Update model
  613. * @param int $id
  614. * @param array $data
  615. * @throws ApiException
  616. * @return ApiResult
  617. */
  618. protected function processUpdate($id, $data)
  619. {
  620. assert('is_int($id)');
  621. $modelClassName = $this->getModelName();
  622. if (isset($data['modelRelations']))
  623. {
  624. $modelRelations = $data['modelRelations'];
  625. unset($data['modelRelations']);
  626. }
  627. try
  628. {
  629. $model = $modelClassName::getById($id);
  630. $this->setModelScenarioFromData($model, $data);
  631. }
  632. catch (NotFoundException $e)
  633. {
  634. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  635. throw new ApiException($message);
  636. }
  637. try
  638. {
  639. ApiControllerSecurityUtil::resolveAccessCanCurrentUserWriteModel($model);
  640. }
  641. catch (SecurityException $e)
  642. {
  643. $message = $e->getMessage();
  644. throw new ApiException($message);
  645. }
  646. try
  647. {
  648. $model = $this->attemptToSaveModelFromData($model, $data, null, false);
  649. $id = $model->id;
  650. if (!count($model->getErrors()))
  651. {
  652. if (isset($modelRelations) && count($modelRelations))
  653. {
  654. try
  655. {
  656. $this->manageModelRelations($model, $modelRelations);
  657. $model->save();
  658. }
  659. catch (Exception $e)
  660. {
  661. $message = Zurmo::t('ZurmoModule', 'Model was updated, but there were issues with relations.');
  662. $message .= ' ' . $e->getMessage();
  663. throw new ApiException($message);
  664. }
  665. }
  666. $model = $modelClassName::getById($id);
  667. $data = static::getModelToApiDataUtilData($model);
  668. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  669. }
  670. else
  671. {
  672. $errors = $model->getErrors();
  673. $message = Zurmo::t('ZurmoModule', 'Model was not updated.');
  674. // To-Do: How to pass $errors and $message to exception
  675. //throw new ApiException($message);
  676. $result = new ApiResult(ApiResponse::STATUS_FAILURE, null, $message, $errors);
  677. }
  678. }
  679. catch (Exception $e)
  680. {
  681. $message = $e->getMessage();
  682. throw new ApiException($message);
  683. }
  684. return $result;
  685. }
  686. /**
  687. * Resolve model scenario from data
  688. * @param array $data
  689. * @return null
  690. */
  691. protected function resolveModelScenario(array & $data)
  692. {
  693. if (isset($data['modelScenario']) && $data['modelScenario'] != '')
  694. {
  695. $scenarioName = $data['modelScenario'];
  696. unset($data['modelScenario']);
  697. return $scenarioName;
  698. }
  699. return null;
  700. }
  701. /**
  702. * Set model scenario
  703. * @param RedBeanModel $model
  704. * @param array $data
  705. */
  706. protected function setModelScenarioFromData(RedBeanModel $model, array & $data)
  707. {
  708. $scenarioName = $this->resolveModelScenario($data);
  709. if (isset($scenarioName) && $scenarioName != '')
  710. {
  711. $model->setScenario($scenarioName);
  712. }
  713. }
  714. /**
  715. * @param RedBeanModel $model
  716. * @param array $modelRelations
  717. * @return bool
  718. * @throws ApiException
  719. */
  720. protected function manageModelRelations($model, $modelRelations)
  721. {
  722. try
  723. {
  724. if (isset($modelRelations) && !empty($modelRelations))
  725. {
  726. foreach ($modelRelations as $modelRelation => $relations)
  727. {
  728. if ($model->isAttribute($modelRelation) &&
  729. ($model->getRelationType($modelRelation) == RedBeanModel::HAS_MANY ||
  730. $model->getRelationType($modelRelation) == RedBeanModel::MANY_MANY))
  731. {
  732. foreach ($relations as $relation)
  733. {
  734. $relatedModelClassName = $relation['modelClassName'];
  735. try
  736. {
  737. $relatedModel = $relatedModelClassName::getById(intval($relation['modelId']));
  738. }
  739. catch (Exception $e)
  740. {
  741. $message = Zurmo::t('ZurmoModule', 'The related model ID specified was invalid.');
  742. throw new NotFoundException($message);
  743. }
  744. if ($relation['action'] == 'add')
  745. {
  746. $model->{$modelRelation}->add($relatedModel);
  747. }
  748. elseif ($relation['action'] == 'remove')
  749. {
  750. $model->{$modelRelation}->remove($relatedModel);
  751. }
  752. else
  753. {
  754. $message = Zurmo::t('ZurmoModule', 'Unsupported action.');
  755. throw new NotSupportedException($message);
  756. }
  757. }
  758. }
  759. else
  760. {
  761. $message = Zurmo::t('ZurmoModule', 'You can add relations only for HAS_MANY and MANY_MANY relations.');
  762. throw new NotSupportedException($message);
  763. }
  764. }
  765. }
  766. }
  767. catch (Exception $e)
  768. {
  769. $message = $e->getMessage();
  770. throw new ApiException($message);
  771. }
  772. return true;
  773. }
  774. /**
  775. * Delete model
  776. * @param int $id
  777. * @throws ApiException
  778. * @return ApiResult
  779. */
  780. protected function processDelete($id)
  781. {
  782. assert('is_int($id)');
  783. $modelClassName = $this->getModelName();
  784. try
  785. {
  786. $model = $modelClassName::getById($id);
  787. }
  788. catch (NotFoundException $e)
  789. {
  790. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  791. throw new ApiException($message);
  792. }
  793. try
  794. {
  795. ApiControllerSecurityUtil::resolveAccessCanCurrentUserDeleteModel($model);
  796. }
  797. catch (SecurityException $e)
  798. {
  799. $message = $e->getMessage();
  800. throw new ApiException($message);
  801. }
  802. try
  803. {
  804. $model->delete();
  805. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, null);
  806. }
  807. catch (Exception $e)
  808. {
  809. $message = $e->getMessage();
  810. throw new ApiException($message);
  811. }
  812. return $result;
  813. }
  814. /**
  815. * Instead of saving from post, we are saving from the API data.
  816. * @see attemptToSaveModelFromPost
  817. */
  818. protected function attemptToSaveModelFromData($model, $data, $redirectUrlParams = null, $redirect = true)
  819. {
  820. assert('is_array($data)');
  821. assert('$redirectUrlParams == null || is_array($redirectUrlParams) || is_string($redirectUrlParams)');
  822. $savedSucessfully = false;
  823. $modelToStringValue = null;
  824. if (isset($data))
  825. {
  826. $this->preAttemptToSaveModelFromDataHook($model, $data);
  827. $controllerUtil = new ZurmoControllerUtil();
  828. $model = $controllerUtil->saveModelFromSanitizedData($data, $model, $savedSucessfully,
  829. $modelToStringValue, false);
  830. }
  831. if ($savedSucessfully && $redirect)
  832. {
  833. $this->actionAfterSuccessfulModelSave($model, $modelToStringValue, $redirectUrlParams);
  834. }
  835. return $model;
  836. }
  837. /**
  838. * Hook to alter $model or $data before we attempt to save it.
  839. * @param RedBeanModel $model
  840. * @param array $data
  841. */
  842. protected function preAttemptToSaveModelFromDataHook(RedBeanModel $model, array & $data)
  843. {
  844. }
  845. /**
  846. * Util used to convert model to array
  847. * @return string
  848. */
  849. protected static function getModelToApiDataUtil()
  850. {
  851. return 'RedBeanModelToApiDataUtil';
  852. }
  853. /**
  854. * Returns data array for provided model using getModelToApiDataUtil
  855. * @param RedBeanModel $model
  856. * @return array
  857. */
  858. protected static function getModelToApiDataUtilData(RedBeanModel $model)
  859. {
  860. $dataUtil = static::getModelToApiDataUtil();
  861. $redBeanModelToApiDataUtil = new $dataUtil($model);
  862. $data = $redBeanModelToApiDataUtil->getData();
  863. return $data;
  864. }
  865. /**
  866. * Resolve StateMetadataAdapterClassName
  867. * @return mixed
  868. */
  869. protected function resolveStateMetadataAdapterClassName()
  870. {
  871. // In case of ContactState model, we can't use Module::getStateMetadataAdapterClassName() function,
  872. // because it references to Contact model, so we defined new function
  873. // ContactsContactStateApiController::getStateMetadataAdapterClassName() which return null.
  874. if (method_exists($this, 'getStateMetadataAdapterClassName'))
  875. {
  876. $stateMetadataAdapterClassName = $this->getStateMetadataAdapterClassName();
  877. }
  878. else
  879. {
  880. $stateMetadataAdapterClassName = $this->getModule()->getStateMetadataAdapterClassName();
  881. }
  882. return $stateMetadataAdapterClassName;
  883. }
  884. /**
  885. * Get array of deleted items since beginning or since datetime in past
  886. * @param array $params
  887. * @return ApiResult
  888. * @throws ApiException
  889. */
  890. protected function processGetDeletedItems($params)
  891. {
  892. $modelClassName = $this->getModelName();
  893. $stateMetadataAdapterClassName = $this->resolveStateMetadataAdapterClassName();
  894. if (!isset($params['sinceTimestamp']))
  895. {
  896. $sinceTimestamp = 0;
  897. }
  898. else
  899. {
  900. $sinceTimestamp = (int)$params['sinceTimestamp'];
  901. }
  902. $pageSize = Yii::app()->pagination->getGlobalValueByType('apiListPageSize');
  903. if (isset($params['pagination']['pageSize']))
  904. {
  905. $pageSize = (int)$params['pagination']['pageSize'];
  906. }
  907. // Get offset. Please note that API client provide page number, and we need to convert it into offset,
  908. // which is parameter of RedBeanModel::getSubset function
  909. if (isset($params['pagination']['page']) && (int)$params['pagination']['page'] > 0)
  910. {
  911. $currentPage = (int)$params['pagination']['page'];
  912. }
  913. else
  914. {
  915. $currentPage = 1;
  916. }
  917. $offset = $this->getOffsetFromCurrentPageAndPageSize($currentPage, $pageSize);
  918. $modelIds = ModelStateChangesSubscriptionUtil::getDeletedModelIds('API', $modelClassName, $pageSize, $offset, $sinceTimestamp, $stateMetadataAdapterClassName);
  919. $totalItems = ModelStateChangesSubscriptionUtil::getDeletedModelsCount('API', $modelClassName, $sinceTimestamp, $stateMetadataAdapterClassName);
  920. $data = array(
  921. 'totalCount' => $totalItems,
  922. 'pageSize' => $pageSize,
  923. 'currentPage' => $currentPage
  924. );
  925. if ($totalItems > 0 && is_array($modelIds) && !empty($modelIds))
  926. {
  927. foreach ($modelIds as $modelId)
  928. {
  929. $data['items'][] = $modelId;
  930. }
  931. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  932. }
  933. else
  934. {
  935. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  936. }
  937. return $result;
  938. }
  939. /**
  940. * Get array of newly created items since beginning or since datetime in past
  941. * @param $params
  942. * @return ApiResult
  943. * @throws ApiException
  944. */
  945. protected function processGetCreatedItems($params)
  946. {
  947. $modelClassName = $this->getModelName();
  948. $stateMetadataAdapterClassName = $this->resolveStateMetadataAdapterClassName();
  949. if (!isset($params['sinceTimestamp']))
  950. {
  951. $sinceTimestamp = 0;
  952. }
  953. else
  954. {
  955. $sinceTimestamp = (int)$params['sinceTimestamp'];
  956. }
  957. $pageSize = Yii::app()->pagination->getGlobalValueByType('apiListPageSize');
  958. if (isset($params['pagination']['pageSize']))
  959. {
  960. $pageSize = (int)$params['pagination']['pageSize'];
  961. }
  962. // Get offset. Please note that API client provide page number, and we need to convert it into offset,
  963. // which is parameter of RedBeanModel::getSubset function
  964. if (isset($params['pagination']['page']) && (int)$params['pagination']['page'] > 0)
  965. {
  966. $currentPage = (int)$params['pagination']['page'];
  967. }
  968. else
  969. {
  970. $currentPage = 1;
  971. }
  972. $offset = $this->getOffsetFromCurrentPageAndPageSize($currentPage, $pageSize);
  973. $models = ModelStateChangesSubscriptionUtil::getCreatedModels('API', $modelClassName, $pageSize, $offset, $sinceTimestamp, $stateMetadataAdapterClassName, Yii::app()->user->userModel);
  974. $totalItems = ModelStateChangesSubscriptionUtil::getCreatedModelsCount('API', $modelClassName, $sinceTimestamp, $stateMetadataAdapterClassName, Yii::app()->user->userModel);
  975. $data = array(
  976. 'totalCount' => $totalItems,
  977. 'pageSize' => $pageSize,
  978. 'currentPage' => $currentPage
  979. );
  980. if (is_array($models) && !empty($models))
  981. {
  982. foreach ($models as $model)
  983. {
  984. $data['items'][] = static::getModelToApiDataUtilData($model);
  985. }
  986. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  987. }
  988. else
  989. {
  990. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  991. }
  992. return $result;
  993. }
  994. /**
  995. * Get array of modified items since beginning or since datetime in past
  996. * @param $params
  997. * @return ApiResult
  998. * @throws ApiException
  999. */
  1000. public function processGetModifiedItems($params)
  1001. {
  1002. $modelClassName = $this->getModelName();
  1003. $stateMetadataAdapterClassName = $this->resolveStateMetadataAdapterClassName();
  1004. if (!isset($params['sinceTimestamp']))
  1005. {
  1006. $sinceTimestamp = 0;
  1007. }
  1008. else
  1009. {
  1010. $sinceTimestamp = (int)$params['sinceTimestamp'];
  1011. }
  1012. $pageSize = Yii::app()->pagination->getGlobalValueByType('apiListPageSize');
  1013. if (isset($params['pagination']['pageSize']))
  1014. {
  1015. $pageSize = (int)$params['pagination']['pageSize'];
  1016. }
  1017. // Get offset. Please note that API client provide page number, and we need to convert it into offset,
  1018. // which is parameter of RedBeanModel::getSubset function
  1019. if (isset($params['pagination']['page']) && (int)$params['pagination']['page'] > 0)
  1020. {
  1021. $currentPage = (int)$params['pagination']['page'];
  1022. }
  1023. else
  1024. {
  1025. $currentPage = 1;
  1026. }
  1027. $offset = $this->getOffsetFromCurrentPageAndPageSize($currentPage, $pageSize);
  1028. $models = ModelStateChangesSubscriptionUtil::getUpdatedModels($modelClassName, $pageSize, $offset, $sinceTimestamp, $stateMetadataAdapterClassName, Yii::app()->user->userModel);
  1029. $totalItems = ModelStateChangesSubscriptionUtil::getUpdatedModelsCount($modelClassName, $sinceTimestamp, $stateMetadataAdapterClassName, Yii::app()->user->userModel);
  1030. $data = array(
  1031. 'totalCount' => $totalItems,
  1032. 'pageSize' => $pageSize,
  1033. 'currentPage' => $currentPage
  1034. );
  1035. if (is_array($models) && !empty($models))
  1036. {
  1037. foreach ($models as $model)
  1038. {
  1039. $data['items'][] = static::getModelToApiDataUtilData($model);
  1040. }
  1041. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  1042. }
  1043. else
  1044. {
  1045. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  1046. }
  1047. return $result;
  1048. }
  1049. public function processGetManyManyRelationshipModels($params)
  1050. {
  1051. try
  1052. {
  1053. $modelId = $params['id'];
  1054. $relationName = $params['relationName'];
  1055. $modelClassName = $params['modelClassName'];
  1056. if (!class_exists($modelClassName, false))
  1057. {
  1058. $message = Zurmo::t('ZurmoModule', 'The specified class name was invalid.');
  1059. throw new ApiException($message);
  1060. }
  1061. try
  1062. {
  1063. $model = $modelClassName::getById(intval($modelId));
  1064. }
  1065. catch (NotFoundException $e)
  1066. {
  1067. $message = Zurmo::t('ZurmoModule', 'The ID specified was invalid.');
  1068. throw new ApiException($message);
  1069. }
  1070. $relatedModelClassName = $model->getRelationModelClassName($relationName);
  1071. if ($model->isRelation($relationName) &&
  1072. $model->getRelationType($relationName) == RedBeanModel::MANY_MANY)
  1073. {
  1074. $data = array();
  1075. foreach ($model->{$relationName} as $item)
  1076. {
  1077. $data[$relationName][] = array('class' => $relatedModelClassName, 'id' => $item->id);
  1078. }
  1079. $result = new ApiResult(ApiResponse::STATUS_SUCCESS, $data, null, null);
  1080. }
  1081. else
  1082. {
  1083. $message = Zurmo::t('ZurmoModule', 'The specified relationship name does not exist or is not MANY_MANY type.');
  1084. throw new ApiException($message);
  1085. }
  1086. }
  1087. catch (Exception $e)
  1088. {
  1089. $message = $e->getMessage();
  1090. throw new ApiException($message);
  1091. }
  1092. return $result;
  1093. }
  1094. }
  1095. ?>