PageRenderTime 64ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/app/protected/modules/reports/models/Report.php

https://bitbucket.org/zurmo/zurmo/
PHP | 767 lines | 387 code | 74 blank | 306 comment | 35 complexity | cefbe3fe7620790a1f6520b33dc55c01 MD5 | raw file
Possible License(s): AGPL-3.0, 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. * Class for interacting with Report definitions. Gets information from either a SavedReport or via a POST.
  38. * Contains information about how a report should be constructed including how it looks in the user interface
  39. * when run. The components of a report are filters, orderBys, groupBys, displayAttributes,
  40. * drillDownDisplayAttributes, and a chart.
  41. *
  42. * There are 3 different types of reports: TYPE_ROWS_AND_COLUMNS, TYPE_SUMMATION, and TYPE_MATRIX. Only Summation
  43. * utilizes a chart and can have drillDownDisplayAttributes
  44. */
  45. class Report extends CComponent
  46. {
  47. /**
  48. * Defines a report type of Rows and Columns. This is the basic report type and isa simple list result
  49. */
  50. const TYPE_ROWS_AND_COLUMNS = 'RowsAndColumns';
  51. /**
  52. * Defines a report type of Summation. A summation report can group data into a result grid. It can also
  53. * have a chart and also allow for drill down into each row to get further information about each group
  54. */
  55. const TYPE_SUMMATION = 'Summation';
  56. /**
  57. * Defines a report type of Matrix. Complex report type that allows multiple groupings across both the x and y
  58. * axises.
  59. */
  60. const TYPE_MATRIX = 'Matrix';
  61. /**
  62. * Currency Conversion Type for rendering currency information. "Actual" means the currency will not be converted
  63. * to the base or a spot currency. It can produce mixed results depending on how the data is being aggregated
  64. * for a report.
  65. */
  66. const CURRENCY_CONVERSION_TYPE_ACTUAL = 1;
  67. /**
  68. * Currency Conversion Type for rendering currency information. "Base" means that currency data is converted
  69. * into the system base currency and displayed in this currency
  70. */
  71. const CURRENCY_CONVERSION_TYPE_BASE = 2;
  72. /**
  73. * Currency Conversion Type for rendering currency information. "Spot" means the currency is converted into
  74. * the base currency and then converted into a spot currency defined by the user when creating the report
  75. */
  76. const CURRENCY_CONVERSION_TYPE_SPOT = 3;
  77. /**
  78. * User defined description of the report. This is optional
  79. * @var string
  80. */
  81. private $description;
  82. /**
  83. * Set from SavedReport
  84. * @var object ExplicitReadWriteModelPermissions
  85. */
  86. private $explicitReadWriteModelPermissions;
  87. /**
  88. * Id of the saved report if it has already been saved
  89. * @var integer
  90. */
  91. private $id;
  92. /**
  93. * Module class name that the report is constructed on
  94. * @var string
  95. */
  96. private $moduleClassName;
  97. /**
  98. * User defined name of the report
  99. * @var string
  100. */
  101. private $name;
  102. /**
  103. * Set from the SavedReport
  104. * @var object User
  105. */
  106. private $owner;
  107. /**
  108. * Defines the report type
  109. * @var string
  110. */
  111. private $type;
  112. /**
  113. * Defines the filters structure. An example is "1 AND 2". This example would be used if there are 2 filters
  114. * for the report.
  115. * @var
  116. */
  117. private $filtersStructure;
  118. /**
  119. * Array of of FilterForReportForm objects
  120. * @var array
  121. */
  122. private $filters = array();
  123. /**
  124. * Array of OrderByFoReportForm objects
  125. * @var array
  126. */
  127. private $orderBys = array();
  128. /**
  129. * Array of DisplayAttributeForReportForm objects
  130. * @var array
  131. */
  132. private $displayAttributes = array();
  133. /**
  134. * Array of DrillDownDisplayAttributeForReportForm objects
  135. * @var array
  136. */
  137. private $drillDownDisplayAttributes = array();
  138. /**
  139. * Array of GroupByForReportForm objects
  140. * @var array
  141. */
  142. private $groupBys = array();
  143. /**
  144. * @var object ChartForReportForm
  145. */
  146. private $chart;
  147. /**
  148. * Currency conversion type used for rendering currency data. There are three types
  149. * CURRENCY_CONVERSION_TYPE_ACTUAL, CURRENCY_CONVERSION_TYPE_BASE, and CURRENCY_CONVERSION_TYPE_SPOT
  150. * @var integer
  151. */
  152. private $currencyConversionType;
  153. /**
  154. * If the $currencyConversionType is CURRENCY_CONVERSION_TYPE_SPOT, then this property is utilized to define
  155. * the currency code for spot conversion
  156. * @var string
  157. */
  158. private $spotConversionCurrencyCode;
  159. /**
  160. * @return array of report type values and labels
  161. */
  162. public static function getTypeDropDownArray()
  163. {
  164. return array(self::TYPE_ROWS_AND_COLUMNS => Zurmo::t('ReportsModule', 'Rows and Columns'),
  165. self::TYPE_SUMMATION => Zurmo::t('ReportsModule', 'Summation'),
  166. self::TYPE_MATRIX => Zurmo::t('ReportsModule', 'Matrix'));
  167. }
  168. /**
  169. * Based on the current user, return the reportable modules and their display labels. Only include modules
  170. * that the user has a right to access.
  171. * @return array of module class names and display labels.
  172. */
  173. public static function getReportableModulesAndLabelsForCurrentUser()
  174. {
  175. $moduleClassNamesAndLabels = array();
  176. foreach (self::getReportableModulesClassNamesCurrentUserHasAccessTo() as $moduleClassName)
  177. {
  178. if ($moduleClassName::getStateMetadataAdapterClassName() != null)
  179. {
  180. $reportRules = ReportRules::makeByModuleClassName($moduleClassName);
  181. $label = $reportRules->getVariableStateModuleLabel(Yii::app()->user->userModel);
  182. }
  183. else
  184. {
  185. $label = $moduleClassName::getModuleLabelByTypeAndLanguage('Plural');
  186. }
  187. if ($label != null)
  188. {
  189. $moduleClassNamesAndLabels[$moduleClassName] = $label;
  190. }
  191. }
  192. return $moduleClassNamesAndLabels;
  193. }
  194. /**
  195. * @return array of module class names and display labels the current user has access to
  196. */
  197. public static function getReportableModulesClassNamesCurrentUserHasAccessTo()
  198. {
  199. $moduleClassNames = array();
  200. $modules = Module::getModuleObjects();
  201. foreach ($modules as $module)
  202. {
  203. if ($module::isReportable())
  204. {
  205. if (ReportSecurityUtil::canCurrentUserCanAccessModule(get_class($module)))
  206. {
  207. $moduleClassNames[] = get_class($module);
  208. }
  209. }
  210. }
  211. return $moduleClassNames;
  212. }
  213. public function __toString()
  214. {
  215. if (trim($this->name) == '')
  216. {
  217. return Zurmo::t('Core', '(Unnamed)');
  218. }
  219. return $this->name;
  220. }
  221. /**
  222. * Returns true if the current user can render a report's results properly. This method checks to see if the
  223. * user has full access to all the related modules and data that the report uses in construction. This method
  224. * is needed because it is possible the author of a report added access for users that do not have complete
  225. * rights to the modules that are part of the report. It is also possible this access changed over time and
  226. * a report that was once properly rendered is no longer.
  227. * @return bool
  228. */
  229. public function canCurrentUserProperlyRenderResults()
  230. {
  231. if (!ReportSecurityUtil::canCurrentUserCanAccessModule($this->moduleClassName))
  232. {
  233. return false;
  234. }
  235. if (!ReportSecurityUtil::canCurrentUserAccessAllComponents($this->displayAttributes))
  236. {
  237. return false;
  238. }
  239. if (!ReportSecurityUtil::canCurrentUserAccessAllComponents($this->filters))
  240. {
  241. return false;
  242. }
  243. if (!ReportSecurityUtil::canCurrentUserAccessAllComponents($this->orderBys))
  244. {
  245. return false;
  246. }
  247. if (!ReportSecurityUtil::canCurrentUserAccessAllComponents($this->groupBys))
  248. {
  249. return false;
  250. }
  251. if (!ReportSecurityUtil::canCurrentUserAccessAllComponents($this->drillDownDisplayAttributes))
  252. {
  253. return false;
  254. }
  255. return true;
  256. }
  257. /**
  258. * @return string
  259. */
  260. public function getModuleClassName()
  261. {
  262. return $this->moduleClassName;
  263. }
  264. /**
  265. * @param $moduleClassName string
  266. */
  267. public function setModuleClassName($moduleClassName)
  268. {
  269. assert('is_string($moduleClassName)');
  270. $this->moduleClassName = $moduleClassName;
  271. }
  272. /**
  273. * @return string
  274. */
  275. public function getDescription()
  276. {
  277. return $this->description;
  278. }
  279. /**
  280. * @param $description
  281. */
  282. public function setDescription($description)
  283. {
  284. assert('is_string($description) || $description == null');
  285. $this->description = $description;
  286. }
  287. /**
  288. * @param $filtersStructure string
  289. */
  290. public function setFiltersStructure($filtersStructure)
  291. {
  292. assert('is_string($filtersStructure)');
  293. $this->filtersStructure = $filtersStructure;
  294. }
  295. /**
  296. * @return array of FilterForReportForm objects
  297. */
  298. public function getFiltersStructure()
  299. {
  300. return $this->filtersStructure;
  301. }
  302. /**
  303. * @return int
  304. */
  305. public function getId()
  306. {
  307. return $this->id;
  308. }
  309. /**
  310. * @param $id int
  311. */
  312. public function setId($id)
  313. {
  314. assert('is_int($id)');
  315. $this->id = $id;
  316. }
  317. /**
  318. * @return string
  319. */
  320. public function getName()
  321. {
  322. return $this->name;
  323. }
  324. /**
  325. * @param $name string
  326. */
  327. public function setName($name)
  328. {
  329. assert('is_string($name)');
  330. $this->name = $name;
  331. }
  332. /**
  333. * @return string
  334. */
  335. public function getType()
  336. {
  337. return $this->type;
  338. }
  339. /**
  340. * @param $type string
  341. */
  342. public function setType($type)
  343. {
  344. assert('$type == self::TYPE_ROWS_AND_COLUMNS || $type == self::TYPE_SUMMATION || $type == self::TYPE_MATRIX');
  345. $this->type = $type;
  346. }
  347. /**
  348. * @return int
  349. */
  350. public function getCurrencyConversionType()
  351. {
  352. return $this->currencyConversionType;
  353. }
  354. /**
  355. * @param $currencyConversionType int
  356. */
  357. public function setCurrencyConversionType($currencyConversionType)
  358. {
  359. assert('is_int($currencyConversionType)');
  360. $this->currencyConversionType = $currencyConversionType;
  361. }
  362. /**
  363. * @return string
  364. */
  365. public function getSpotConversionCurrencyCode()
  366. {
  367. return $this->spotConversionCurrencyCode;
  368. }
  369. /**
  370. * @param $spotConversionCurrencyCode string
  371. */
  372. public function setSpotConversionCurrencyCode($spotConversionCurrencyCode)
  373. {
  374. assert('is_string($spotConversionCurrencyCode)');
  375. $this->spotConversionCurrencyCode = $spotConversionCurrencyCode;
  376. }
  377. /**
  378. * @return float
  379. */
  380. public function getFromBaseToSpotRate()
  381. {
  382. return 1 / Yii::app()->currencyHelper->getConversionRateToBase($this->spotConversionCurrencyCode);
  383. }
  384. /**
  385. * @return bool
  386. */
  387. public function isNew()
  388. {
  389. if ($this->id > 0)
  390. {
  391. return false;
  392. }
  393. return true;
  394. }
  395. /**
  396. * @return object
  397. */
  398. public function getOwner()
  399. {
  400. if ($this->owner == null)
  401. {
  402. $this->owner = Yii::app()->user->userModel;
  403. }
  404. return $this->owner;
  405. }
  406. /**
  407. * @param User $owner
  408. */
  409. public function setOwner(User $owner)
  410. {
  411. $this->owner = $owner;
  412. }
  413. /**
  414. * @return array of FilterForReportForm objects
  415. */
  416. public function getFilters()
  417. {
  418. return $this->filters;
  419. }
  420. /**
  421. * @param FilterForReportForm $filter
  422. */
  423. public function addFilter(FilterForReportForm $filter)
  424. {
  425. $this->filters[] = $filter;
  426. }
  427. /**
  428. * Removes all FilterForReportForm objects on this report
  429. */
  430. public function removeAllFilters()
  431. {
  432. $this->filters = array();
  433. }
  434. /**
  435. * Removes all Runtime FilterForReportForm objects on this report
  436. */
  437. public function removeRuntimeFilters()
  438. {
  439. $filters = array();
  440. foreach ($this->filters as $filter)
  441. {
  442. if (!$filter->availableAtRunTime)
  443. {
  444. $filters[] = $filter;
  445. }
  446. }
  447. $this->filters = $filters;
  448. }
  449. /**
  450. * @return array of GroupByForReportForm objects
  451. */
  452. public function getGroupBys()
  453. {
  454. return $this->groupBys;
  455. }
  456. /**
  457. * @param GroupByForReportForm $groupBy
  458. */
  459. public function addGroupBy(GroupByForReportForm $groupBy)
  460. {
  461. $this->groupBys[] = $groupBy;
  462. }
  463. /**
  464. * Removes all GroupByForReportForm objects on this report
  465. */
  466. public function removeAllGroupBys()
  467. {
  468. $this->groupBys = array();
  469. }
  470. /**
  471. * @return array of OrderByForReportForm objects
  472. */
  473. public function getOrderBys()
  474. {
  475. return $this->orderBys;
  476. }
  477. /**
  478. * @param OrderByForReportForm $orderBy
  479. */
  480. public function addOrderBy(OrderByForReportForm $orderBy)
  481. {
  482. $this->orderBys[] = $orderBy;
  483. }
  484. /**
  485. * Removes all OrderByForReportForm objects on this report
  486. */
  487. public function removeAllOrderBys()
  488. {
  489. $this->orderBys = array();
  490. }
  491. /**
  492. * @return array of DisplayAttributeForReportForm objects
  493. */
  494. public function getDisplayAttributes()
  495. {
  496. return $this->displayAttributes;
  497. }
  498. /**
  499. * @param DisplayAttributeForReportForm $displayAttribute
  500. */
  501. public function addDisplayAttribute(DisplayAttributeForReportForm $displayAttribute)
  502. {
  503. $this->displayAttributes[] = $displayAttribute;
  504. }
  505. /**
  506. * Removes all DisplayAttributeForReportForm objects on this report
  507. */
  508. public function removeAllDisplayAttributes()
  509. {
  510. $this->displayAttributes = array();
  511. }
  512. /**
  513. * @return array of DrillDownDisplayAttributeForReportForm objects
  514. */
  515. public function getDrillDownDisplayAttributes()
  516. {
  517. return $this->drillDownDisplayAttributes;
  518. }
  519. /**
  520. * @param DrillDownDisplayAttributeForReportForm $drillDownDisplayAttribute
  521. */
  522. public function addDrillDownDisplayAttribute(DrillDownDisplayAttributeForReportForm $drillDownDisplayAttribute)
  523. {
  524. $this->drillDownDisplayAttributes[] = $drillDownDisplayAttribute;
  525. }
  526. /**
  527. * Removes all DrillDownDisplayAttributeForReportForm objects on this report
  528. */
  529. public function removeAllDrillDownDisplayAttributes()
  530. {
  531. $this->drillDownDisplayAttributes = array();
  532. }
  533. /**
  534. * @return ChartForReportForm|object
  535. */
  536. public function getChart()
  537. {
  538. if ($this->chart == null)
  539. {
  540. $this->chart = new ChartForReportForm();
  541. }
  542. return $this->chart;
  543. }
  544. /**
  545. * @param ChartForReportForm $chart
  546. */
  547. public function setChart(ChartForReportForm $chart)
  548. {
  549. $this->chart = $chart;
  550. }
  551. /**
  552. * Returns true if the report has a chart
  553. * @return bool
  554. */
  555. public function hasChart()
  556. {
  557. if ($this->getChart()->type == null)
  558. {
  559. return false;
  560. }
  561. return true;
  562. }
  563. /**
  564. * @return ExplicitReadWriteModelPermissions|object
  565. */
  566. public function getExplicitReadWriteModelPermissions()
  567. {
  568. if ($this->explicitReadWriteModelPermissions == null)
  569. {
  570. $this->explicitReadWriteModelPermissions = new ExplicitReadWriteModelPermissions();
  571. }
  572. return $this->explicitReadWriteModelPermissions;
  573. }
  574. /**
  575. * Set from the value in the SavedReport
  576. * @param ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions
  577. */
  578. public function setExplicitReadWriteModelPermissions(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
  579. {
  580. $this->explicitReadWriteModelPermissions = $explicitReadWriteModelPermissions;
  581. }
  582. /**
  583. * Returns true if at least one filter is available at runtime.
  584. * @return bool
  585. */
  586. public function hasRuntimeFilters()
  587. {
  588. foreach ($this->getFilters() as $filter)
  589. {
  590. if ($filter->availableAtRunTime)
  591. {
  592. return true;
  593. }
  594. }
  595. return false;
  596. }
  597. /**
  598. * Given an attributeIndexOrDerivedType, return the key of the $displayAttributes that corresponds to the
  599. * DisplayAttributeForReportForm object that has the given attribute
  600. * @param $attribute
  601. * @return int|null|string
  602. */
  603. public function getDisplayAttributeIndex($attribute)
  604. {
  605. foreach ($this->displayAttributes as $key => $displayAttribute)
  606. {
  607. if ($attribute == $displayAttribute->attributeIndexOrDerivedType)
  608. {
  609. return $key;
  610. }
  611. }
  612. return null;
  613. }
  614. /**
  615. * Given an attributeIndexOrDerivedType, return the DisplayAttributeForReportForm object that has that
  616. * attributeIndexOrDerivedType
  617. * @param $attribute
  618. * @return mixed
  619. * @throws NotFoundException if it is not found
  620. */
  621. public function getDisplayAttributeByAttribute($attribute)
  622. {
  623. foreach ($this->getDisplayAttributes() as $displayAttribute)
  624. {
  625. if ($attribute == $displayAttribute->attributeIndexOrDerivedType)
  626. {
  627. return $displayAttribute;
  628. }
  629. }
  630. throw new NotFoundException();
  631. }
  632. /**
  633. * Utilized for summation with drill down rows. For a given group, the grouped value needs to be used
  634. * as a filter for the drilled down row. This method will add that groupBy as a filter and update the
  635. * filterStructure accordingly.
  636. * @param array $getData
  637. */
  638. public function resolveGroupBysAsFilters(Array $getData)
  639. {
  640. $newStartingStructurePosition = count($this->filters) + 1;
  641. $structure = null;
  642. foreach ($this->getGroupBys() as $groupBy)
  643. {
  644. $index = ReportResultsRowData::resolveDataParamKeyForDrillDown($groupBy->attributeIndexOrDerivedType);
  645. if (isset($getData[$index]))
  646. {
  647. $value = $getData[$index];
  648. }
  649. else
  650. {
  651. $value = null;
  652. }
  653. $filter = new FilterForReportForm($groupBy->getModuleClassName(),
  654. $groupBy->getModelClassName(),
  655. $this->type);
  656. $filter->attributeIndexOrDerivedType = $groupBy->attributeIndexOrDerivedType;
  657. self::resolveGroupByAsFilterValue($value, $filter);
  658. $this->addFilter($filter);
  659. if ($structure != null)
  660. {
  661. $structure .= ' AND ';
  662. }
  663. $structure .= $newStartingStructurePosition;
  664. $newStartingStructurePosition++;
  665. }
  666. $structure = '(' . $structure . ')';
  667. if ($this->filtersStructure != null)
  668. {
  669. $this->filtersStructure = '(' . $this->filtersStructure . ')';
  670. $this->filtersStructure .= ' AND ';
  671. }
  672. $this->filtersStructure .= $structure;
  673. }
  674. /**
  675. * Given a value and a filter, resolve the value for being null or not. If null then a different operator
  676. * is used on the value than if it is not null.
  677. * @param $value
  678. * @param FilterForReportForm $filter
  679. */
  680. protected static function resolveGroupByAsFilterValue($value, FilterForReportForm $filter)
  681. {
  682. if ($value !== null)
  683. {
  684. $filter->operator = OperatorRules::TYPE_EQUALS;
  685. $filter->value = $value;
  686. }
  687. else
  688. {
  689. $filter->operator = OperatorRules::TYPE_IS_NULL;
  690. }
  691. }
  692. }
  693. ?>