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

Afform - reset managed entities when deleting a dashlet #22957

Merged
merged 1 commit into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CRM/Core/ManagedEntities.php
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@ protected function loadDeclarations(): void {

protected function loadManagedEntityActions(): void {
$managedEntities = Managed::get(FALSE)->addSelect('*')->execute();
$this->managedActions = [];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit test revealed this variable was leaky. The purpose of this function is to rebuild this array, but it needs to be reset first due to the singleton pattern.

foreach ($managedEntities as $managedEntity) {
$key = "{$managedEntity['module']}_{$managedEntity['name']}_{$managedEntity['entity_type']}";
// Set to 'delete' - it will be overwritten below if it is to be updated.
Expand Down
26 changes: 23 additions & 3 deletions Civi/Api4/Generic/BasicBatchAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,36 @@ public function __construct($entityName, $actionName, $doer = NULL) {
}

/**
* We pass the doTask function an array representing one item to update.
* We expect to get the same format back.
* Checks permissions and then delegates to processBatch.
*
* Note: Unconditional logic must go here in the run function, as delegated functions may be overridden.
*
* @param \Civi\Api4\Generic\Result $result
*/
public function _run(Result $result) {
foreach ($this->getBatchRecords() as $item) {
$items = $this->getBatchRecords();
foreach ($items as $item) {
if ($this->checkPermissions && !CoreUtil::checkAccessRecord($this, $item, \CRM_Core_Session::getLoggedInContactID() ?: 0)) {
throw new UnauthorizedException("ACL check failed");
}
}
$this->processBatch($result, $items);
}

/**
* Calls doTask once per item and stores the result.
*
* We pass the doTask function an array representing one item to process.
* We expect to get the same format back.
*
* Note: This function may be overridden by the end api.
*
* @param Result $result
* @param array $items
* @throws NotImplementedException
*/
protected function processBatch(Result $result, array $items) {
foreach ($items as $item) {
$result[] = $this->doTask($item);
}
}
Expand Down
92 changes: 92 additions & 0 deletions ext/afform/core/Civi/Api4/Action/Afform/Revert.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Civi\Api4\Action\Afform;

use Civi\Api4\Generic\Result;

/**
* @inheritDoc
* @package Civi\Api4\Action\Afform
*/
class Revert extends \Civi\Api4\Generic\BasicBatchAction {

/**
* @var bool
*/
private $flushManaged = FALSE;

/**
* @var bool
*/
private $flushMenu = FALSE;

/**
* Revert every record, and flush caches at the end.
*
* @inheritDoc
*/
protected function processBatch(Result $result, array $items) {
parent::processBatch($result, $items);

// We may have changed list of files covered by the cache.
_afform_clear();

if ($this->flushManaged) {
// FIXME: more targeted reconciliation
\CRM_Core_ManagedEntities::singleton()->reconcile();
}
if ($this->flushMenu) {
\CRM_Core_Menu::store();
}
}

/**
* Revert (delete) a record.
*
* @inheritDoc
*/
protected function doTask($item) {
/** @var \CRM_Afform_AfformScanner $scanner */
$scanner = \Civi::service('afform_scanner');
$files = [
\CRM_Afform_AfformScanner::METADATA_FILE,
\CRM_Afform_AfformScanner::LAYOUT_FILE,
];

foreach ($files as $file) {
$metaPath = $scanner->createSiteLocalPath($item['name'], $file);
if (file_exists($metaPath)) {
if (!@unlink($metaPath)) {
throw new \API_Exception("Failed to remove afform overrides in $file");
}
}
}

$original = (array) $scanner->getMeta($item['name']);

// If the dashlet setting changed, managed entities must be reconciled
if (
(empty($item['is_dashlet']) !== empty($original['is_dashlet'])) ||
($item['is_dashlet'] && ($item['title'] ?? '') !== ($original['title'] ?? ''))
) {
$this->flushManaged = TRUE;
}

// If the server_route changed, reset menu cache
if (($item['server_route'] ?? '') !== ($original['server_route'] ?? '')) {
$this->flushMenu = TRUE;
}

return $item;
}

/**
* Adds extra return params so caches can be conditionally flushed.
*
* @return string[]
*/
protected function getSelect() {
return ['name', 'title', 'is_dashlet', 'server_route'];
}

}
29 changes: 3 additions & 26 deletions ext/afform/core/Civi/Api4/Afform.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Civi\Api4;

use Civi\Api4\Generic\BasicBatchAction;
use Civi\Api4\Generic\BasicGetFieldsAction;

/**
Expand Down Expand Up @@ -105,33 +104,11 @@ public static function getOptions($checkPermissions = TRUE) {

/**
* @param bool $checkPermissions
* @return Generic\BasicBatchAction
* @return Action\Afform\Revert
*/
public static function revert($checkPermissions = TRUE) {
return (new BasicBatchAction('Afform', __FUNCTION__, function($item, BasicBatchAction $action) {
$scanner = \Civi::service('afform_scanner');
$files = [
\CRM_Afform_AfformScanner::METADATA_FILE,
\CRM_Afform_AfformScanner::LAYOUT_FILE,
];

foreach ($files as $file) {
$metaPath = $scanner->createSiteLocalPath($item['name'], $file);
if (file_exists($metaPath)) {
if (!@unlink($metaPath)) {
throw new \API_Exception("Failed to remove afform overrides in $file");
}
}
}

// We may have changed list of files covered by the cache.
_afform_clear();

// FIXME if `server_route` changes, then flush the menu cache.
// FIXME if asset-caching is enabled, then flush the asset cache

return $item;
}))->setCheckPermissions($checkPermissions);
return (new Action\Afform\Revert('Afform', __FUNCTION__))
->setCheckPermissions($checkPermissions);
}

/**
Expand Down
13 changes: 6 additions & 7 deletions ext/afform/core/Civi/Api4/Utils/AfformSaveTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,18 @@ protected function writeRecord($item) {
return ($item[$field] ?? NULL) !== ($orig[$field] ?? NULL);
};

if ($isChanged('is_dashlet')) {
// FIXME: more targetted reconciliation
\CRM_Core_ManagedEntities::singleton()->reconcile();
}
elseif (array_key_exists('is_dashlet', (array) $orig) && $orig['is_dashlet'] && $isChanged('title')) {
// FIXME: more targetted reconciliation
// If the dashlet setting changed, managed entities must be reconciled
if (
$isChanged('is_dashlet') ||
(!empty($meta['is_dashlet']) && $isChanged('title'))
) {
// FIXME: more targeted reconciliation
\CRM_Core_ManagedEntities::singleton()->reconcile();
}

// Right now, permission-checks are completely on-demand.
if ($isChanged('server_route') /* || $isChanged('permission') */) {
\CRM_Core_Menu::store();
\CRM_Core_BAO_Navigation::resetNavigation();
}

$item['module_name'] = _afform_angular_module_name($item['name'], 'camel');
Expand Down
22 changes: 20 additions & 2 deletions ext/afform/mock/tests/phpunit/api/v4/AfformTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php
use Civi\Api4\Dashboard;

/**
* Afform.Get API Test Case
Expand Down Expand Up @@ -50,6 +51,18 @@ public function testGetUpdateRevert($formName, $originalMetadata): void {
return isset($arr[$key]) ? $arr[$key] : NULL;
};

$checkDashlet = function($afform) use ($formName) {
$dashlet = Dashboard::get(FALSE)
->addWhere('name', '=', $formName)
->execute();
if (!empty($afform['is_dashlet'])) {
$this->assertCount(1, $dashlet);
}
else {
$this->assertCount(0, $dashlet);
}
};

Civi\Api4\Afform::revert()->addWhere('name', '=', $formName)->execute();

$message = 'The initial Afform.get should return default data';
Expand All @@ -66,13 +79,14 @@ public function testGetUpdateRevert($formName, $originalMetadata): void {
$this->assertEquals(TRUE, $get($result[0], 'has_base'), $message);
$this->assertEquals(FALSE, $get($result[0], 'has_local'), $message);
$this->assertEquals('org.civicrm.afform-mock', $get($result[0], 'base_module'), $message);
$checkDashlet($originalMetadata);

$message = 'After updating with Afform.create, the revised data should be returned';
$result = Civi\Api4\Afform::update()
->addWhere('name', '=', $formName)
->addValue('description', 'The temporary description')
->addValue('permission', 'access foo')
->addValue('is_dashlet', TRUE)
->addValue('is_dashlet', empty($originalMetadata['is_dashlet']))
->execute();
$this->assertEquals($formName, $result[0]['name'], $message);
$this->assertEquals('The temporary description', $result[0]['description'], $message);
Expand All @@ -84,13 +98,14 @@ public function testGetUpdateRevert($formName, $originalMetadata): void {
$this->assertEquals($formName, $result[0]['name'], $message);
$this->assertEquals($get($originalMetadata, 'title'), $get($result[0], 'title'), $message);
$this->assertEquals('The temporary description', $get($result[0], 'description'), $message);
$this->assertEquals(TRUE, $get($result[0], 'is_dashlet'), $message);
$this->assertEquals(empty($originalMetadata['is_dashlet']), $get($result[0], 'is_dashlet'), $message);
$this->assertEquals($get($originalMetadata, 'server_route'), $get($result[0], 'server_route'), $message);
$this->assertEquals('access foo', $get($result[0], 'permission'), $message);
$this->assertTrue(is_array($result[0]['layout']), $message);
$this->assertEquals(TRUE, $get($result[0], 'has_base'), $message);
$this->assertEquals(TRUE, $get($result[0], 'has_local'), $message);
$this->assertEquals('org.civicrm.afform-mock', $get($result[0], 'base_module'), $message);
$checkDashlet($result[0]);

Civi\Api4\Afform::revert()->addWhere('name', '=', $formName)->execute();
$message = 'After reverting, the final Afform.get should return default data';
Expand All @@ -102,10 +117,13 @@ public function testGetUpdateRevert($formName, $originalMetadata): void {
$this->assertEquals($get($originalMetadata, 'description'), $get($result[0], 'description'), $message);
$this->assertEquals($get($originalMetadata, 'server_route'), $get($result[0], 'server_route'), $message);
$this->assertEquals($get($originalMetadata, 'permission'), $get($result[0], 'permission'), $message);
$this->assertEquals($get($originalMetadata, 'is_dashlet'), $get($result[0], 'is_dashlet'), $message);
$this->assertTrue(is_array($result[0]['layout']), $message);
$this->assertEquals(TRUE, $get($result[0], 'has_base'), $message);
$this->assertEquals(FALSE, $get($result[0], 'has_local'), $message);
$this->assertEquals('org.civicrm.afform-mock', $get($result[0], 'base_module'), $message);

$checkDashlet($originalMetadata);
}

public function getFormatExamples() {
Expand Down