Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate APIv4 into civicrm-core #15309

Merged
merged 24 commits into from
Sep 22, 2019
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f2f0f5f
Obsolete api4 extension
colemanw Sep 14, 2019
f74361a
Handle new flattened custom field return format
colemanw Sep 14, 2019
0e0f741
api4 - Port PHPUnit autoloading hacks
Sep 15, 2019
f0b90b0
api4 - Add civicrm_api4() and CRM.api4() entry-points
Sep 15, 2019
caaeea3
api4 - Enable services
Sep 15, 2019
19b53e5
api4 - Import CRM/, Civi/, templates/, ang/, css/, js/, xml/menu
Sep 15, 2019
cdeee72
api4 - Adjust to new name
Sep 15, 2019
e96f62c
api4 - Update test init
Sep 15, 2019
23c2d07
Search for entity-specific actions in core
colemanw Sep 15, 2019
bd2669e
Scan core as well as extensions for api4 entities & services
colemanw Sep 15, 2019
a2fa26f
(NFC) Pass both civilint and ReflectionUtilsTest
totten Sep 15, 2019
235e322
api4 - Fix computation of core path
totten Sep 15, 2019
c1e9641
api4 - Fix container cache/reload behavior
totten Sep 15, 2019
425c975
api4 - Search for entities in core (for realz)
totten Sep 15, 2019
2c07402
Test fixes
colemanw Sep 16, 2019
1d10c3c
Update namespace for phpunit6 compat
colemanw Sep 16, 2019
cddf293
Api4 generated code improvements
colemanw Sep 16, 2019
0b873c9
Fix api explorer module loading
colemanw Sep 16, 2019
13e9117
Add api4 menu item
colemanw Sep 17, 2019
1938279
composer.json - Add "ignore" list for js-yaml
totten Sep 18, 2019
d20bb06
distmaker - Remove steps to download api4 as extension
totten Sep 18, 2019
2e704b9
api4 - Update civicrm_generated.mysql for new nav item
totten Sep 20, 2019
6872a65
Fix number fields in api explorer
colemanw Sep 22, 2019
6f97b1d
Remove extension-specific ts()
colemanw Sep 22, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions CRM/Api4/Page/AJAX.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

class CRM_Api4_Page_AJAX extends CRM_Core_Page {

/**
* Handler for api4 ajax requests
*/
public function run() {
try {
// Call multiple
if (empty($this->urlPath[3])) {
$calls = CRM_Utils_Request::retrieve('calls', 'String', CRM_Core_DAO::$_nullObject, TRUE, NULL, 'POST', TRUE);
$calls = json_decode($calls, TRUE);
$response = [];
foreach ($calls as $index => $call) {
$response[$index] = call_user_func_array([$this, 'execute'], $call);
}
}
// Call single
else {
$entity = $this->urlPath[3];
$action = $this->urlPath[4];
$params = CRM_Utils_Request::retrieve('params', 'String');
$params = $params ? json_decode($params, TRUE) : [];
$index = CRM_Utils_Request::retrieve('index', 'String');
$response = $this->execute($entity, $action, $params, $index);
}
}
catch (Exception $e) {
http_response_code(500);
$response = [
'error_code' => $e->getCode(),
];
if (CRM_Core_Permission::check('view debug output')) {
$response['error_message'] = $e->getMessage();
if (\Civi::settings()->get('backtrace')) {
$response['backtrace'] = $e->getTrace();
}
}
}
CRM_Utils_System::setHttpHeader('Content-Type', 'application/json');
echo json_encode($response);
CRM_Utils_System::civiExit();
}

/**
* Run api call & prepare result for json encoding
*
* @param string $entity
* @param string $action
* @param array $params
* @param string $index
* @return array
*/
protected function execute($entity, $action, $params = [], $index = NULL) {
$params['checkPermissions'] = TRUE;

// Handle numeric indexes later so we can get the count
$itemAt = CRM_Utils_Type::validate($index, 'Integer', FALSE);

$result = civicrm_api4($entity, $action, $params, isset($itemAt) ? NULL : $index);

// Convert arrayObject into something more suitable for json
$vals = ['values' => isset($itemAt) ? $result->itemAt($itemAt) : (array) $result];
foreach (get_class_vars(get_class($result)) as $key => $val) {
$vals[$key] = $result->$key;
}
$vals['count'] = $result->count();
return $vals;
}

}
29 changes: 29 additions & 0 deletions CRM/Api4/Page/Api4Explorer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

class CRM_Api4_Page_Api4Explorer extends CRM_Core_Page {

public function run() {
$vars = [
'operators' => \CRM_Core_DAO::acceptedSQLOperators(),
'basePath' => Civi::resources()->getUrl('civicrm'),
'schema' => (array) \Civi\Api4\Entity::get()->setChain(['fields' => ['$name', 'getFields']])->execute(),
'links' => (array) \Civi\Api4\Entity::getLinks()->execute(),
];
Civi::resources()
->addVars('api4', $vars)
->addScriptFile('civicrm', 'js/load-bootstrap.js')
->addScriptFile('civicrm', 'bower_components/js-yaml/dist/js-yaml.min.js')
->addScriptFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.js')
->addStyleFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.css');

$loader = new Civi\Angular\AngularLoader();
$loader->setModules(['api4Explorer']);
$loader->setPageName('civicrm/api4');
$loader->useApp([
'defaultRoute' => '/explorer',
]);
$loader->load();
parent::run();
}

}
79 changes: 79 additions & 0 deletions CRM/Api4/Services.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;

class CRM_Api4_Services {

/**
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*/
public static function hook_container($container) {
$loader = new XmlFileLoader($container, new FileLocator(dirname(dirname(__DIR__))));
$loader->load('Civi/Api4/services.xml');

self::loadServices('Civi\Api4\Service\Spec\Provider', 'spec_provider', $container);
self::loadServices('Civi\Api4\Event\Subscriber', 'event_subscriber', $container);

$container->getDefinition('civi_api_kernel')->addMethodCall(
'registerApiProvider',
[new Reference('action_object_provider')]
);

// add event subscribers$container->get(
$dispatcher = $container->getDefinition('dispatcher');
$subscribers = $container->findTaggedServiceIds('event_subscriber');

foreach (array_keys($subscribers) as $subscriber) {
$dispatcher->addMethodCall(
'addSubscriber',
[new Reference($subscriber)]
);
}

// add spec providers
$providers = $container->findTaggedServiceIds('spec_provider');
$gatherer = $container->getDefinition('spec_gatherer');

foreach (array_keys($providers) as $provider) {
$gatherer->addMethodCall(
'addSpecProvider',
[new Reference($provider)]
);
}

if (defined('CIVICRM_UF') && CIVICRM_UF === 'UnitTests') {
$loader->load('tests/phpunit/api/v4/services.xml');
}
}

/**
* Load all services in a given directory
*
* @param string $namespace
* @param string $tag
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*/
public static function loadServices($namespace, $tag, $container) {
$namespace = \CRM_Utils_File::addTrailingSlash($namespace, '\\');
$locations = array_merge([\Civi::paths()->getPath('[civicrm.root]/Civi.php')],
array_column(\CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles(), 'filePath')
);
foreach ($locations as $location) {
$path = \CRM_Utils_File::addTrailingSlash(dirname($location)) . str_replace('\\', DIRECTORY_SEPARATOR, $namespace);
$container->addResource(new \Symfony\Component\Config\Resource\DirectoryResource($path, ';\.php$;'));
foreach (glob("$path*.php") as $file) {
$matches = [];
preg_match('/(\w*).php/', $file, $matches);
$serviceName = $namespace . array_pop($matches);
$serviceClass = new \ReflectionClass($serviceName);
if ($serviceClass->isInstantiable()) {
$definition = $container->register(str_replace('\\', '_', $serviceName), $serviceName);
$definition->addTag($tag);
}
}
}
}

}
2 changes: 2 additions & 0 deletions CRM/Core/Config/Runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ public static function getId() {
\CRM_Utils_Array::value('HTTP_HOST', $_SERVER, ''),
// e.g. port-based vhosts
\CRM_Utils_Array::value('SERVER_PORT', $_SERVER, ''),
// e.g. unit testing
defined('CIVICRM_TEST') ? 1 : 0,
// Depending on deployment arch, these signals *could* be redundant, but who cares?
]));
}
Expand Down
14 changes: 14 additions & 0 deletions CRM/Core/xml/Menu/Api4.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<menu>
<item>
<path>civicrm/ajax/api4</path>
<page_callback>CRM_Api4_Page_AJAX</page_callback>
<access_arguments>access CiviCRM</access_arguments>
</item>
<item>
<path>civicrm/api4</path>
<page_callback>CRM_Api4_Page_Api4Explorer</page_callback>
<title>CiviCRM</title>
<access_arguments>access CiviCRM</access_arguments>
</item>
</menu>
64 changes: 44 additions & 20 deletions CRM/Upgrade/Incremental/php/FiveNineteen.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,51 @@ public function setPostUpgradeMessage(&$postUpgradeMessage, $rev) {
// }
}

/*
* Important! All upgrade functions MUST add a 'runSql' task.
* Uncomment and use the following template for a new upgrade version
* (change the x in the function name):
/**
* Upgrade function.
*
* @param string $rev
*/
public function upgrade_5_19_alpha1($rev) {
$this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
$this->addTask('Add api4 menu', 'api4Menu');
}

// /**
// * Upgrade function.
// *
// * @param string $rev
// */
// public function upgrade_5_0_x($rev) {
// $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
// $this->addTask('Do the foo change', 'taskFoo', ...);
// // Additional tasks here...
// // Note: do not use ts() in the addTask description because it adds unnecessary strings to transifex.
// // The above is an exception because 'Upgrade DB to %1: SQL' is generic & reusable.
// }

// public static function taskFoo(CRM_Queue_TaskContext $ctx, ...) {
// return TRUE;
// }
/**
* Add menu item for api4 explorer; rename v3 explorer menu item.
*
* @param \CRM_Queue_TaskContext $ctx
* @return bool
*/
public static function api4Menu(CRM_Queue_TaskContext $ctx) {
try {
$v3Item = civicrm_api3('Navigation', 'get', [
'name' => 'API Explorer',
'return' => ['id', 'parent_id', 'weight'],
'sequential' => 1,
'domain_id' => CRM_Core_Config::domainID(),
'api.Navigation.create' => ['label' => ts("Api Explorer v3")],
]);
$existing = civicrm_api3('Navigation', 'getcount', [
'name' => "Api Explorer v4",
'domain_id' => CRM_Core_Config::domainID(),
]);
if (!$existing) {
civicrm_api3('Navigation', 'create', [
'parent_id' => $v3Item['values'][0]['parent_id'] ?? 'Developer',
'label' => ts("Api Explorer v4"),
'weight' => $v3Item['values'][0]['weight'] ?? 2,
'name' => "Api Explorer v4",
'permission' => "administer CiviCRM",
'url' => "civicrm/api4#/explorer",
'is_active' => 1,
]);
}
}
catch (Exception $e) {
// Couldn't create menu item.
}
return TRUE;
}

}
2 changes: 2 additions & 0 deletions Civi/Angular/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public function getModules() {
$angularModules['ui.sortable'] = include "$civicrm_root/ang/ui.sortable.ang.php";
$angularModules['unsavedChanges'] = include "$civicrm_root/ang/unsavedChanges.ang.php";
$angularModules['statuspage'] = include "$civicrm_root/ang/crmStatusPage.ang.php";
$angularModules['api4Explorer'] = include "$civicrm_root/ang/api4Explorer.ang.php";
$angularModules['api4'] = include "$civicrm_root/ang/api4.ang.php";

foreach (\CRM_Core_Component::getEnabledComponents() as $component) {
$angularModules = array_merge($angularModules, $component->getAngularModules());
Expand Down
19 changes: 19 additions & 0 deletions Civi/Api4/ACL.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Civi\Api4;

/**
* ACL Entity.
*
* This entity holds the ACL informatiom. With this entity you add/update/delete an ACL permission which consists of
* an Operation (e.g. 'View' or 'Edit'), a set of Data that the operation can be performed on (e.g. a group of contacts),
* and a Role that has permission to do this operation. For more info refer to
* https://docs.civicrm.org/user/en/latest/initial-set-up/permissions-and-access-control for more info.
*
* Creating a new ACL requires at minimum a entity table, entity ID and object_table
*
* @package Civi\Api4
*/
class ACL extends Generic\DAOEntity {

}
50 changes: 50 additions & 0 deletions Civi/Api4/Action/Address/AddressSaveTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Civi\Api4\Action\Address;

/**
* @inheritDoc
* @method bool getStreetParsing()
* @method $this setStreetParsing(bool $streetParsing)
* @method bool getSkipGeocode()
* @method $this setSkipGeocode(bool $skipGeocode)
* @method bool getFixAddress()
* @method $this setFixAddress(bool $fixAddress)
*/
trait AddressSaveTrait {

/**
* Optional param to indicate you want the street_address field parsed into individual params
*
* @var bool
*/
protected $streetParsing = FALSE;

/**
* Optional param to indicate you want to skip geocoding (useful when importing a lot of addresses at once, the job Geocode and Parse Addresses can execute this task after the import)
*
* @var bool
*/
protected $skipGeocode = FALSE;

/**
* When true, apply various fixes to the address before insert.
*
* @var bool
*/
protected $fixAddress = TRUE;

/**
* @inheritDoc
*/
protected function writeObjects($items) {
foreach ($items as &$item) {
if ($this->streetParsing && !empty($item['street_address'])) {
$item = array_merge($item, \CRM_Core_BAO_Address::parseStreetAddress($item['street_address']));
}
$item['skip_geocode'] = $this->skipGeocode;
}
return parent::writeObjects($items);
}

}
11 changes: 11 additions & 0 deletions Civi/Api4/Action/Address/Create.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Civi\Api4\Action\Address;

/**
* @inheritDoc
*/
class Create extends \Civi\Api4\Generic\DAOCreateAction {
use AddressSaveTrait;

}
11 changes: 11 additions & 0 deletions Civi/Api4/Action/Address/Save.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Civi\Api4\Action\Address;

/**
* @inheritDoc
*/
class Save extends \Civi\Api4\Generic\DAOSaveAction {
use AddressSaveTrait;

}
11 changes: 11 additions & 0 deletions Civi/Api4/Action/Address/Update.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Civi\Api4\Action\Address;

/**
* @inheritDoc
*/
class Update extends \Civi\Api4\Generic\DAOUpdateAction {
use AddressSaveTrait;

}
Loading