diff options
Diffstat (limited to 'www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber')
11 files changed, 909 insertions, 0 deletions
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/AbstractPrepareSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/AbstractPrepareSubscriber.php new file mode 100644 index 00000000..d4725e0d --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/AbstractPrepareSubscriber.php @@ -0,0 +1,24 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\API\Event\PrepareEvent; +use Civi\API\Events; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +abstract class AbstractPrepareSubscriber implements EventSubscriberInterface { + /** + * @return array + */ + public static function getSubscribedEvents() { + return [ + Events::PREPARE => 'onApiPrepare', + ]; + } + + /** + * @param PrepareEvent $event + */ + abstract public function onApiPrepare(PrepareEvent $event); + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php new file mode 100644 index 00000000..1938ce0b --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php @@ -0,0 +1,40 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\Api4\Generic\DAOCreateAction; +use Civi\Api4\OptionValue; + +class ActivityPreCreationSubscriber extends PreCreationSubscriber { + /** + * @param DAOCreateAction $request + * @throws \API_Exception + * @throws \Exception + */ + protected function modify(DAOCreateAction $request) { + $activityType = $request->getValue('activity_type'); + if ($activityType) { + $result = OptionValue::get() + ->setCheckPermissions(FALSE) + ->addWhere('name', '=', $activityType) + ->addWhere('option_group.name', '=', 'activity_type') + ->execute(); + + if ($result->count() !== 1) { + throw new \Exception('Activity type must match a *single* type'); + } + + $request->addValue('activity_type_id', $result->first()['value']); + } + } + + /** + * @param DAOCreateAction $request + * + * @return bool + */ + protected function applies(DAOCreateAction $request) { + return $request->getEntityName() === 'Activity'; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php new file mode 100644 index 00000000..52d58397 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php @@ -0,0 +1,40 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\Api4\Event\Events; +use Civi\Api4\Event\SchemaMapBuildEvent; +use Civi\Api4\Service\Schema\Joinable\ActivityToActivityContactAssigneesJoinable; +use Civi\Api4\Service\Schema\Joinable\BridgeJoinable; +use Civi\Api4\Service\Schema\Joinable\Joinable; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use \CRM_Utils_String as StringHelper; + +class ActivitySchemaMapSubscriber implements EventSubscriberInterface { + /** + * @return array + */ + public static function getSubscribedEvents() { + return [ + Events::SCHEMA_MAP_BUILD => 'onSchemaBuild', + ]; + } + + /** + * @param SchemaMapBuildEvent $event + */ + public function onSchemaBuild(SchemaMapBuildEvent $event) { + $schema = $event->getSchemaMap(); + $table = $schema->getTableByName('civicrm_activity'); + + $middleAlias = StringHelper::createRandom(10, implode(range('a', 'z'))); + $middleLink = new ActivityToActivityContactAssigneesJoinable($middleAlias); + + $bridge = new BridgeJoinable('civicrm_contact', 'id', 'assignees', $middleLink); + $bridge->setBaseTable('civicrm_activity_contact'); + $bridge->setJoinType(Joinable::JOIN_TYPE_ONE_TO_MANY); + + $table->addTableLink('contact_id', $bridge); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php new file mode 100644 index 00000000..edea3de6 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php @@ -0,0 +1,54 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\Api4\Event\Events; +use Civi\Api4\Event\SchemaMapBuildEvent; +use Civi\Api4\Service\Schema\Joinable\Joinable; +use Civi\Api4\Service\Schema\Table; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class ContactSchemaMapSubscriber implements EventSubscriberInterface { + /** + * @return array + */ + public static function getSubscribedEvents() { + return [ + Events::SCHEMA_MAP_BUILD => 'onSchemaBuild', + ]; + } + + /** + * @param SchemaMapBuildEvent $event + */ + public function onSchemaBuild(SchemaMapBuildEvent $event) { + $schema = $event->getSchemaMap(); + $table = $schema->getTableByName('civicrm_contact'); + $this->addCreatedActivitiesLink($table); + $this->fixPreferredLanguageAlias($table); + } + + /** + * @param Table $table + */ + private function addCreatedActivitiesLink($table) { + $alias = 'created_activities'; + $joinable = new Joinable('civicrm_activity_contact', 'contact_id', $alias); + $joinable->addCondition($alias . '.record_type_id = 1'); + $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY); + $table->addTableLink('id', $joinable); + } + + /** + * @param Table $table + */ + private function fixPreferredLanguageAlias($table) { + foreach ($table->getExternalLinks() as $link) { + if ($link->getAlias() === 'languages') { + $link->setAlias('preferred_language'); + return; + } + } + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomFieldPreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomFieldPreCreationSubscriber.php new file mode 100644 index 00000000..289e1057 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomFieldPreCreationSubscriber.php @@ -0,0 +1,91 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\Api4\Generic\DAOCreateAction; + +class CustomFieldPreCreationSubscriber extends PreCreationSubscriber { + + const OPTION_TYPE_NEW = 1; + const OPTION_STATUS_ACTIVE = 1; + + /** + * @param DAOCreateAction $request + */ + public function modify(DAOCreateAction $request) { + $this->formatOptionParams($request); + $this->setDefaults($request); + } + + /** + * @param DAOCreateAction $request + * + * @return bool + */ + protected function applies(DAOCreateAction $request) { + return $request->getEntityName() === 'CustomField'; + } + + /** + * Sets defaults required for option group and value creation + * @see CRM_Core_BAO_CustomField::create() + * + * @param DAOCreateAction $request + */ + protected function formatOptionParams(DAOCreateAction $request) { + $options = $request->getValue('options'); + + if (!is_array($options)) { + return; + } + + $dataTypeKey = 'data_type'; + $optionLabelKey = 'option_label'; + $optionWeightKey = 'option_weight'; + $optionStatusKey = 'option_status'; + $optionValueKey = 'option_value'; + $optionTypeKey = 'option_type'; + + $dataType = $request->getValue($dataTypeKey); + $optionLabel = $request->getValue($optionLabelKey); + $optionWeight = $request->getValue($optionWeightKey); + $optionStatus = $request->getValue($optionStatusKey); + $optionValue = $request->getValue($optionValueKey); + $optionType = $request->getValue($optionTypeKey); + + if (!$optionType) { + $request->addValue($optionTypeKey, self::OPTION_TYPE_NEW); + } + + if (!$dataType) { + $request->addValue($dataTypeKey, 'String'); + } + + if (!$optionLabel) { + $request->addValue($optionLabelKey, array_values($options)); + } + + if (!$optionValue) { + $request->addValue($optionValueKey, array_keys($options)); + } + + if (!$optionStatus) { + $statuses = array_fill(0, count($options), self::OPTION_STATUS_ACTIVE); + $request->addValue($optionStatusKey, $statuses); + } + + if (!$optionWeight) { + $request->addValue($optionWeightKey, range(1, count($options))); + } + } + + /** + * @param DAOCreateAction $request + */ + private function setDefaults(DAOCreateAction $request) { + if (!$request->getValue('option_type')) { + $request->addValue('option_type', NULL); + } + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php new file mode 100644 index 00000000..70f6e426 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php @@ -0,0 +1,29 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\Api4\Generic\DAOCreateAction; + +class CustomGroupPreCreationSubscriber extends PreCreationSubscriber { + /** + * @param DAOCreateAction $request + */ + protected function modify(DAOCreateAction $request) { + $extends = $request->getValue('extends'); + $title = $request->getValue('title'); + $name = $request->getValue('name'); + + if (is_string($extends)) { + $request->addValue('extends', [$extends]); + } + + if (NULL === $title && $name) { + $request->addValue('title', $name); + } + } + + protected function applies(DAOCreateAction $request) { + return $request->getEntityName() === 'CustomGroup'; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php new file mode 100644 index 00000000..3e671d61 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php @@ -0,0 +1,50 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\Api4\Generic\DAOCreateAction; +use Civi\Api4\OptionGroup; + +class OptionValuePreCreationSubscriber extends PreCreationSubscriber { + + /** + * @param DAOCreateAction $request + */ + protected function modify(DAOCreateAction $request) { + $this->setOptionGroupId($request); + } + + /** + * @param DAOCreateAction $request + * + * @return bool + */ + protected function applies(DAOCreateAction $request) { + return $request->getEntityName() === 'OptionValue'; + } + + /** + * @param DAOCreateAction $request + * @throws \API_Exception + * @throws \Exception + */ + private function setOptionGroupId(DAOCreateAction $request) { + $optionGroupName = $request->getValue('option_group'); + if (!$optionGroupName || $request->getValue('option_group_id')) { + return; + } + + $optionGroup = OptionGroup::get() + ->setCheckPermissions(FALSE) + ->addSelect('id') + ->addWhere('name', '=', $optionGroupName) + ->execute(); + + if ($optionGroup->count() !== 1) { + throw new \Exception('Option group name must match only a single group'); + } + + $request->addValue('option_group_id', $optionGroup->first()['id']); + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php new file mode 100644 index 00000000..62d542d0 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php @@ -0,0 +1,65 @@ +<?php +/* + +--------------------------------------------------------------------+ + | CiviCRM version 4.7 | + +--------------------------------------------------------------------+ + | Copyright CiviCRM LLC (c) 2004-2017 | + +--------------------------------------------------------------------+ + | This file is a part of CiviCRM. | + | | + | CiviCRM is free software; you can copy, modify, and distribute it | + | under the terms of the GNU Affero General Public License | + | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | + | | + | CiviCRM is distributed in the hope that it will be useful, but | + | WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | + | See the GNU Affero General Public License for more details. | + | | + | You should have received a copy of the GNU Affero General Public | + | License and the CiviCRM Licensing Exception along | + | with this program; if not, contact CiviCRM LLC | + | at info[AT]civicrm[DOT]org. If you have questions about the | + | GNU Affero General Public License or the licensing of CiviCRM, | + | see the CiviCRM license FAQ at http://civicrm.org/licensing | + +--------------------------------------------------------------------+ + */ + +namespace Civi\Api4\Event\Subscriber; + +use Civi\API\Events; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * For any API requests that correspond to a Doctrine entity + * ($apiRequest['doctrineClass']), check permissions specified in + * Civi\API\Annotation\Permission. + */ +class PermissionCheckSubscriber implements EventSubscriberInterface { + /** + * @return array + */ + public static function getSubscribedEvents() { + return [ + Events::AUTHORIZE => [ + ['onApiAuthorize', Events::W_LATE], + ], + ]; + } + + /** + * @param \Civi\API\Event\AuthorizeEvent $event + * API authorization event. + */ + public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) { + /* @var \Civi\Api4\Generic\AbstractAction $apiRequest */ + $apiRequest = $event->getApiRequest(); + if ($apiRequest['version'] == 4) { + if (!$apiRequest->getCheckPermissions() || $apiRequest->isAuthorized()) { + $event->authorize(); + $event->stopPropagation(); + } + } + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php new file mode 100644 index 00000000..ff7e6d20 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php @@ -0,0 +1,369 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\Api4\Event\Events; +use Civi\Api4\Event\PostSelectQueryEvent; +use Civi\Api4\Query\Api4SelectQuery; +use Civi\Api4\Service\Schema\Joinable\Joinable; +use Civi\Api4\Utils\ArrayInsertionUtil; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Changes the results of a select query, doing 1-n joins and unserializing data + */ +class PostSelectQuerySubscriber implements EventSubscriberInterface { + + /** + * @inheritdoc + */ + public static function getSubscribedEvents() { + return [ + Events::POST_SELECT_QUERY => 'onPostQuery' + ]; + } + + /** + * @param PostSelectQueryEvent $event + */ + public function onPostQuery(PostSelectQueryEvent $event) { + $results = $event->getResults(); + $event->setResults($this->postRun($results, $event->getQuery())); + } + + /** + * @param array $results + * @param Api4SelectQuery $query + * + * @return array + */ + protected function postRun(array $results, Api4SelectQuery $query) { + if (empty($results)) { + return $results; + } + + $fieldSpec = $query->getApiFieldSpec(); + $this->unserializeFields($results, $query->getEntity(), $fieldSpec); + + // Group the selects to avoid queries for each field + $groupedSelects = $this->getJoinedDotSelects($query); + foreach ($groupedSelects as $finalAlias => $selects) { + $joinPath = $this->getJoinPathInfo($selects[0], $query); + $selects = $this->formatSelects($finalAlias, $selects, $query); + $joinResults = $this->getJoinResults($query, $finalAlias, $selects); + $this->formatJoinResults($joinResults, $query, $finalAlias); + + // Insert join results into original result + foreach ($results as &$primaryResult) { + $baseId = $primaryResult['id']; + $filtered = array_filter($joinResults, function ($res) use ($baseId) { + return ($res['_base_id'] === $baseId); + }); + $filtered = array_values($filtered); + ArrayInsertionUtil::insert($primaryResult, $joinPath, $filtered); + } + } + + return array_values($results); + } + + /** + * @param array $joinResults + * @param Api4SelectQuery $query + * @param string $alias + */ + private function formatJoinResults(&$joinResults, $query, $alias) { + $join = $query->getJoinedTable($alias); + $fields = []; + foreach ($join->getEntityFields() as $field) { + $name = explode('.', $field->getName()); + $fields[array_pop($name)] = $field->toArray(); + } + if ($fields) { + $this->unserializeFields($joinResults, NULL, $fields); + } + } + + /** + * Unserialize values + * + * @param array $results + * @param string $entity + * @param array $fields + */ + protected function unserializeFields(&$results, $entity, $fields = []) { + if (empty($fields)) { + $params = ['action' => 'get', 'includeCustom' => FALSE]; + $fields = civicrm_api4($entity, 'getFields', $params)->indexBy('name'); + } + + foreach ($results as &$result) { + foreach ($result as $field => &$value) { + if (!empty($fields[$field]['serialize']) && is_string($value)) { + $serializationType = $fields[$field]['serialize']; + $value = \CRM_Core_DAO::unSerializeField($value, $serializationType); + } + } + } + } + + /** + * @param Api4SelectQuery $query + * + * @return array + */ + private function getJoinedDotSelects(Api4SelectQuery $query) { + // Remove selects that are not in a joined table + $fkAliases = $query->getFkSelectAliases(); + $joinedDotSelects = array_filter( + $query->getSelect(), + function ($select) use ($fkAliases) { + return isset($fkAliases[$select]); + } + ); + + $selects = []; + // group related selects by alias so they can be executed in one query + foreach ($joinedDotSelects as $select) { + $parts = explode('.', $select); + $finalAlias = $parts[count($parts) - 2]; + $selects[$finalAlias][] = $select; + } + + // sort by depth, e.g. email selects should be done before email.location + uasort($selects, function ($a, $b) { + $aFirst = $a[0]; + $bFirst = $b[0]; + return substr_count($aFirst, '.') > substr_count($bFirst, '.'); + }); + + return $selects; + } + + + /** + * @param array $selects + * @param $serializationType + * @param Api4SelectQuery $query + * + * @return array + */ + private function getResultsForSerializedField( + array $selects, + $serializationType, + Api4SelectQuery $query + ) { + // Get the alias (Selects are grouped and all target the same table) + $sampleField = current($selects); + $alias = strstr($sampleField, '.', TRUE); + + // Fetch the results with the serialized field + $selects['serialized'] = $query::MAIN_TABLE_ALIAS . '.' . $alias; + $serializedResults = $this->runWithNewSelects($selects, $query); + $newResults = []; + + // Create a new results array, with a separate entry for each option value + foreach ($serializedResults as $result) { + $optionValues = \CRM_Core_DAO::unSerializeField( + $result['serialized'], + $serializationType + ); + unset($result['serialized']); + foreach ($optionValues as $value) { + $newResults[] = array_merge($result, ['value' => $value]); + } + } + + $optionValueValues = array_unique(array_column($newResults, 'value')); + $optionValues = $this->getOptionValuesFromValues( + $selects, + $query, + $optionValueValues + ); + $valueField = $alias . '.value'; + + // Index by value + foreach ($optionValues as $key => $subResult) { + $optionValues[$subResult['value']] = $subResult; + unset($subResult[$key]); + + // Exclude 'value' if not in original selects + if (!in_array($valueField, $selects)) { + unset($optionValues[$subResult['value']]['value']); + } + } + + // Replace serialized with the sub-select results + foreach ($newResults as &$result) { + $result = array_merge($result, $optionValues[$result['value']]); + unset($result['value']); + } + + return $newResults; + } + + + /** + * Prepares selects for the subquery to fetch join results + * + * @param string $alias + * @param array $selects + * @param Api4SelectQuery $query + * + * @return array + */ + private function formatSelects($alias, $selects, Api4SelectQuery $query) { + $mainAlias = $query::MAIN_TABLE_ALIAS; + $selectFields = []; + + foreach ($selects as $select) { + $selectAlias = $query->getFkSelectAliases()[$select]; + $fieldAlias = substr($select, strrpos($select, '.') + 1); + $selectFields[$fieldAlias] = $selectAlias; + } + + $firstSelect = $selects[0]; + $pathParts = explode('.', $firstSelect); + $numParts = count($pathParts); + $parentAlias = $numParts > 2 ? $pathParts[$numParts - 3] : $mainAlias; + + $selectFields['id'] = sprintf('%s.id', $alias); + $selectFields['_parent_id'] = $parentAlias . '.id'; + $selectFields['_base_id'] = $mainAlias . '.id'; + + return $selectFields; + } + + /** + * @param array $selects + * @param Api4SelectQuery $query + * + * @return array + */ + private function runWithNewSelects(array $selects, Api4SelectQuery $query) { + $aliasedSelects = array_map(function ($field, $alias) { + return sprintf('%s as "%s"', $field, $alias); + }, $selects, array_keys($selects)); + + $newSelect = sprintf('SELECT DISTINCT %s', implode(", ", $aliasedSelects)); + $sql = str_replace("\n", ' ', $query->getQuery()->toSQL()); + $originalSelect = substr($sql, 0, strpos($sql, ' FROM')); + $sql = str_replace($originalSelect, $newSelect, $sql); + + $relatedResults = []; + $resultDAO = \CRM_Core_DAO::executeQuery($sql); + while ($resultDAO->fetch()) { + $relatedResult = []; + foreach ($selects as $alias => $column) { + $returnName = $alias; + $alias = str_replace('.', '_', $alias); + if (property_exists($resultDAO, $alias)) { + $relatedResult[$returnName] = $resultDAO->$alias; + } + }; + $relatedResults[] = $relatedResult; + } + + return $relatedResults; + } + + /** + * @param Api4SelectQuery $query + * @param $alias + * @param $selects + * @return array + */ + protected function getJoinResults(Api4SelectQuery $query, $alias, $selects) { + $apiFieldSpec = $query->getApiFieldSpec(); + if (!empty($apiFieldSpec[$alias]['serialize'])) { + $type = $apiFieldSpec[$alias]['serialize']; + $joinResults = $this->getResultsForSerializedField($selects, $type, $query); + } + else { + $joinResults = $this->runWithNewSelects($selects, $query); + } + + // Remove results with no matching entries + $joinResults = array_filter($joinResults, function ($result) { + return !empty($result['id']); + }); + + return $joinResults; + } + + /** + * Separates a string like 'emails.location_type.label' into an array, where + * each value in the array tells whether it is 1-1 or 1-n join type + * + * @param string $pathString + * Dot separated path to the field + * @param Api4SelectQuery $query + * + * @return array + * Index is table alias and value is boolean whether is 1-to-many join + */ + private function getJoinPathInfo($pathString, $query) { + $pathParts = explode('.', $pathString); + array_pop($pathParts); // remove field + $path = []; + $isMultipleChecker = function($alias) use ($query) { + foreach ($query->getJoinedTables() as $table) { + if ($table->getAlias() === $alias) { + return $table->getJoinType() === Joinable::JOIN_TYPE_ONE_TO_MANY; + } + } + return FALSE; + }; + + foreach ($pathParts as $part) { + $path[$part] = $isMultipleChecker($part); + } + + return $path; + } + + /** + * Get all the option_value values required in the query + * + * @param array $selects + * @param Api4SelectQuery $query + * @param array $values + * + * @return array + */ + private function getOptionValuesFromValues( + array $selects, + Api4SelectQuery $query, + array $values + ) { + $sampleField = current($selects); + $alias = strstr($sampleField, '.', TRUE); + + // Get the option value table that was joined + $relatedTable = NULL; + foreach ($query->getJoinedTables() as $joinedTable) { + if ($joinedTable->getAlias() === $alias) { + $relatedTable = $joinedTable; + } + } + + // We only want subselects related to the joined table + $subSelects = array_filter($selects, function ($select) use ($alias) { + return strpos($select, $alias) === 0; + }); + + // Fetch all related option_value entries + $valueField = $alias . '.value'; + $subSelects[] = $valueField; + $tableName = $relatedTable->getTargetTable(); + $conditions = $relatedTable->getExtraJoinConditions(); + $conditions[] = $valueField . ' IN ("' . implode('", "', $values) . '")'; + $subQuery = new \CRM_Utils_SQL_Select($tableName . ' ' . $alias); + $subQuery->where($conditions); + $subQuery->select($subSelects); + $subResults = $subQuery->execute()->fetchAll(); + + return $subResults; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PreCreationSubscriber.php new file mode 100644 index 00000000..6737a9f3 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PreCreationSubscriber.php @@ -0,0 +1,50 @@ +<?php + +namespace Civi\Api4\Event\Subscriber; + +use Civi\API\Event\PrepareEvent; +use Civi\Api4\Generic\DAOCreateAction; + +abstract class PreCreationSubscriber extends AbstractPrepareSubscriber { + /** + * @param PrepareEvent $event + */ + public function onApiPrepare(PrepareEvent $event) { + $apiRequest = $event->getApiRequest(); + if (!$apiRequest instanceof DAOCreateAction) { + return; + } + + $this->addDefaultCreationValues($apiRequest); + if ($this->applies($apiRequest)) { + $this->modify($apiRequest); + } + } + + /** + * Modify the request + * + * @param DAOCreateAction $request + * + * @return void + */ + abstract protected function modify(DAOCreateAction $request); + + /** + * Check if this subscriber should be applied to the request + * + * @param DAOCreateAction $request + * + * @return bool + */ + abstract protected function applies(DAOCreateAction $request); + + /** + * Sets default values common to all creation requests + * + * @param DAOCreateAction $request + */ + protected function addDefaultCreationValues(DAOCreateAction $request) { + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php new file mode 100644 index 00000000..5e2c6f33 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php @@ -0,0 +1,97 @@ +<?php +/* + +--------------------------------------------------------------------+ + | CiviCRM version 4.7 | + +--------------------------------------------------------------------+ + | Copyright CiviCRM LLC (c) 2004-2017 | + +--------------------------------------------------------------------+ + | This file is a part of CiviCRM. | + | | + | CiviCRM is free software; you can copy, modify, and distribute it | + | under the terms of the GNU Affero General Public License | + | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | + | | + | CiviCRM is distributed in the hope that it will be useful, but | + | WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | + | See the GNU Affero General Public License for more details. | + | | + | You should have received a copy of the GNU Affero General Public | + | License and the CiviCRM Licensing Exception along | + | with this program; if not, contact CiviCRM LLC | + | at info[AT]civicrm[DOT]org. If you have questions about the | + | GNU Affero General Public License or the licensing of CiviCRM, | + | see the CiviCRM license FAQ at http://civicrm.org/licensing | + +--------------------------------------------------------------------+ + */ + +namespace Civi\Api4\Event\Subscriber; + +use Civi\API\Event\PrepareEvent; + +/** + * Validate field inputs based on annotations in the action class + */ +class ValidateFieldsSubscriber extends AbstractPrepareSubscriber { + + /** + * @param PrepareEvent $event + * @throws \Exception + */ + public function onApiPrepare(PrepareEvent $event) { + /** @var \Civi\Api4\Generic\AbstractAction $apiRequest */ + $apiRequest = $event->getApiRequest(); + if (is_a($apiRequest, 'Civi\Api4\Generic\AbstractAction')) { + $paramInfo = $apiRequest->getParamInfo(); + foreach ($paramInfo as $param => $info) { + $getParam = 'get' . ucfirst($param); + $value = $apiRequest->$getParam(); + // Required fields + if (!empty($info['required']) && (!$value && $value !== 0 && $value !== '0')) { + throw new \API_Exception('Parameter "' . $param . '" is required.'); + } + if (!empty($info['type']) && !self::checkType($value, $info['type'])) { + throw new \API_Exception('Parameter "' . $param . '" is not of the correct type. Expecting ' . implode(' or ', $info['type']) . '.'); + } + } + } + } + + /** + * Validate variable type on input + * + * @param $value + * @param $types + * @return bool + * @throws \API_Exception + */ + public static function checkType($value, $types) { + if ($value === NULL) { + return TRUE; + } + foreach ($types as $type) { + switch ($type) { + case 'array': + case 'bool': + case 'string': + case 'object': + $tester = 'is_' . $type; + if ($tester($value)) { + return TRUE; + } + break; + + case 'int': + if (\CRM_Utils_Rule::integer($value)) { + return TRUE; + } + break; + + default: + throw new \API_Exception('Unknown parameter type: ' . $type); + } + } + return FALSE; + } + +} |