summaryrefslogtreecommitdiff
path: root/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event
diff options
context:
space:
mode:
Diffstat (limited to 'www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event')
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Events.php25
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/GetSpecEvent.php35
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/PostSelectQueryEvent.php64
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/SchemaMapBuildEvent.php39
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/AbstractPrepareSubscriber.php24
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php40
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php40
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php54
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomFieldPreCreationSubscriber.php91
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php29
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php50
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php65
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php369
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PreCreationSubscriber.php50
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php97
15 files changed, 1072 insertions, 0 deletions
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Events.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Events.php
new file mode 100644
index 00000000..0bf4a993
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Events.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Civi\Api4\Event;
+
+class Events {
+
+ /**
+ * Prepare the specification for a request. Fired from within a request to
+ * get fields.
+ *
+ * @see GetSpecEvent
+ */
+ const GET_SPEC = 'civi.api.get_spec';
+
+ /**
+ * Build the database schema, allow adding of custom joins and tables.
+ */
+ const SCHEMA_MAP_BUILD = 'api.schema_map.build';
+
+ /**
+ * Alter query results of APIv4 select query
+ */
+ const POST_SELECT_QUERY = 'api.select_query.post';
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/GetSpecEvent.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/GetSpecEvent.php
new file mode 100644
index 00000000..cc247853
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/GetSpecEvent.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Civi\Api4\Event;
+
+use Civi\Api4\Generic\AbstractAction;
+use Symfony\Component\EventDispatcher\Event as BaseEvent;
+
+class GetSpecEvent extends BaseEvent {
+ /**
+ * @var AbstractAction
+ */
+ protected $request;
+
+ /**
+ * @param AbstractAction $request
+ */
+ public function __construct(AbstractAction $request) {
+ $this->request = $request;
+ }
+
+ /**
+ * @return AbstractAction
+ */
+ public function getRequest() {
+ return $this->request;
+ }
+
+ /**
+ * @param $request
+ */
+ public function setRequest(AbstractAction $request) {
+ $this->request = $request;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/PostSelectQueryEvent.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/PostSelectQueryEvent.php
new file mode 100644
index 00000000..4489033b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/PostSelectQueryEvent.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Civi\Api4\Event;
+
+use Civi\Api4\Query\Api4SelectQuery;
+use Symfony\Component\EventDispatcher\Event;
+
+class PostSelectQueryEvent extends Event {
+
+ /**
+ * @var array
+ */
+ protected $results;
+
+ /**
+ * @var Api4SelectQuery
+ */
+ protected $query;
+
+ /**
+ * PostSelectQueryEvent constructor.
+ * @param array $results
+ * @param Api4SelectQuery $query
+ */
+ public function __construct(array $results, Api4SelectQuery $query) {
+ $this->results = $results;
+ $this->query = $query;
+ }
+
+ /**
+ * @return array
+ */
+ public function getResults() {
+ return $this->results;
+ }
+
+ /**
+ * @param array $results
+ * @return $this
+ */
+ public function setResults($results) {
+ $this->results = $results;
+
+ return $this;
+ }
+
+ /**
+ * @return Api4SelectQuery
+ */
+ public function getQuery() {
+ return $this->query;
+ }
+
+ /**
+ * @param Api4SelectQuery $query
+ * @return $this
+ */
+ public function setQuery($query) {
+ $this->query = $query;
+
+ return $this;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/SchemaMapBuildEvent.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/SchemaMapBuildEvent.php
new file mode 100644
index 00000000..f79f6b4b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/SchemaMapBuildEvent.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Civi\Api4\Event;
+
+use Civi\Api4\Service\Schema\SchemaMap;
+use Symfony\Component\EventDispatcher\Event as BaseEvent;
+
+class SchemaMapBuildEvent extends BaseEvent {
+ /**
+ * @var SchemaMap
+ */
+ protected $schemaMap;
+
+ /**
+ * @param SchemaMap $schemaMap
+ */
+ public function __construct(SchemaMap $schemaMap) {
+ $this->schemaMap = $schemaMap;
+ }
+
+ /**
+ * @return SchemaMap
+ */
+ public function getSchemaMap() {
+ return $this->schemaMap;
+ }
+
+ /**
+ * @param SchemaMap $schemaMap
+ *
+ * @return $this
+ */
+ public function setSchemaMap($schemaMap) {
+ $this->schemaMap = $schemaMap;
+
+ return $this;
+ }
+
+}
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;
+ }
+
+}