*/
class CRM_Contact_Page_View_Note extends CRM_Core_Page {
- /**
- * The action links for notes that we need to display for the browse screen
- *
- * @var array
- */
- public static $_links = NULL;
-
- /**
- * The action links for comments that we need to display for the browse screen
- *
- * @var array
- */
- public static $_commentLinks = NULL;
-
/**
* Notes found running the browse function
* @var array
$session->pushUserContext($url);
if (CRM_Utils_Request::retrieve('confirmed', 'Boolean')) {
- CRM_Core_BAO_Note::del($this->_id);
+ $this->delete();
CRM_Utils_System::redirect($url);
}
}
/**
- * Delete the note object from the db.
+ * Delete the note object from the db and set a status msg.
*/
public function delete() {
- CRM_Core_BAO_Note::del($this->_id);
+ CRM_Core_BAO_Note::deleteRecord(['id' => $this->_id]);
+ $status = ts('Selected Note has been deleted successfully.');
+ CRM_Core_Session::setStatus($status, ts('Deleted'), 'success');
}
/**
* Get action links.
*
- * @return array
- * (reference) of action links
+ * @return array[]
*/
- public static function &links() {
- if (!(self::$_links)) {
- $deleteExtra = ts('Are you sure you want to delete this note?');
-
- self::$_links = [
- CRM_Core_Action::VIEW => [
- 'name' => ts('View'),
- 'url' => 'civicrm/contact/view/note',
- 'qs' => 'action=view&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note',
- 'title' => ts('View Note'),
- ],
- CRM_Core_Action::UPDATE => [
- 'name' => ts('Edit'),
- 'url' => 'civicrm/contact/view/note',
- 'qs' => 'action=update&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note',
- 'title' => ts('Edit Note'),
- ],
- CRM_Core_Action::ADD => [
- 'name' => ts('Comment'),
- 'url' => 'civicrm/contact/view/note',
- 'qs' => 'action=add&reset=1&cid=%%cid%%&parentId=%%id%%&selectedChild=note',
- 'title' => ts('Add Comment'),
- ],
- CRM_Core_Action::DELETE => [
- 'name' => ts('Delete'),
- 'url' => 'civicrm/contact/view/note',
- 'qs' => 'action=delete&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note',
- 'title' => ts('Delete Note'),
- ],
- ];
- }
- return self::$_links;
+ public static function links() {
+ return [
+ CRM_Core_Action::VIEW => [
+ 'name' => ts('View'),
+ 'url' => 'civicrm/contact/view/note',
+ 'qs' => 'action=view&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note',
+ 'title' => ts('View Note'),
+ ],
+ CRM_Core_Action::UPDATE => [
+ 'name' => ts('Edit'),
+ 'url' => 'civicrm/contact/view/note',
+ 'qs' => 'action=update&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note',
+ 'title' => ts('Edit Note'),
+ ],
+ CRM_Core_Action::ADD => [
+ 'name' => ts('Comment'),
+ 'url' => 'civicrm/contact/view/note',
+ 'qs' => 'action=add&reset=1&cid=%%cid%%&parentId=%%id%%&selectedChild=note',
+ 'title' => ts('Add Comment'),
+ ],
+ CRM_Core_Action::DELETE => [
+ 'name' => ts('Delete'),
+ 'url' => 'civicrm/contact/view/note',
+ 'qs' => 'action=delete&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note',
+ 'title' => ts('Delete Note'),
+ ],
+ ];
}
/**
* Get action links for comments.
*
- * @return array
- * (reference) of action links
+ * @return array[]
*/
- public static function &commentLinks() {
- if (!(self::$_commentLinks)) {
- self::$_commentLinks = [
- CRM_Core_Action::VIEW => [
- 'name' => ts('View'),
- 'url' => 'civicrm/contact/view/note',
- 'qs' => 'action=view&reset=1&cid=%%cid%%&id={id}&selectedChild=note',
- 'title' => ts('View Comment'),
- ],
- CRM_Core_Action::UPDATE => [
- 'name' => ts('Edit'),
- 'url' => 'civicrm/contact/view/note',
- 'qs' => 'action=update&reset=1&cid=%%cid%%&id={id}&parentId=%%pid%%&selectedChild=note',
- 'title' => ts('Edit Comment'),
- ],
- CRM_Core_Action::DELETE => [
- 'name' => ts('Delete'),
- 'url' => 'civicrm/contact/view/note',
- 'qs' => 'action=delete&reset=1&cid=%%cid%%&id={id}&selectedChild=note',
- 'title' => ts('Delete Comment'),
- ],
- ];
- }
- return self::$_commentLinks;
+ public static function commentLinks() {
+ return [
+ CRM_Core_Action::VIEW => [
+ 'name' => ts('View'),
+ 'url' => 'civicrm/contact/view/note',
+ 'qs' => 'action=view&reset=1&cid=%%cid%%&id={id}&selectedChild=note',
+ 'title' => ts('View Comment'),
+ ],
+ CRM_Core_Action::UPDATE => [
+ 'name' => ts('Edit'),
+ 'url' => 'civicrm/contact/view/note',
+ 'qs' => 'action=update&reset=1&cid=%%cid%%&id={id}&parentId=%%pid%%&selectedChild=note',
+ 'title' => ts('Edit Comment'),
+ ],
+ CRM_Core_Action::DELETE => [
+ 'name' => ts('Delete'),
+ 'url' => 'civicrm/contact/view/note',
+ 'qs' => 'action=delete&reset=1&cid=%%cid%%&id={id}&selectedChild=note',
+ 'title' => ts('Delete Comment'),
+ ],
+ ];
}
}
$note = CRM_Core_BAO_Note::getNote($id, 'civicrm_contribution');
$noteId = key($note);
if ($noteId) {
- CRM_Core_BAO_Note::del($noteId, FALSE);
+ CRM_Core_BAO_Note::deleteRecord(['id' => $noteId]);
}
$dao = new CRM_Contribute_DAO_Contribution();
*/
public static function processNote($params, $contactID, $contributionID, $contributionNoteID = NULL) {
if (CRM_Utils_System::isNull($params['note']) && $contributionNoteID) {
- CRM_Core_BAO_Note::del($contributionNoteID);
+ CRM_Core_BAO_Note::deleteRecord(['id' => $contributionNoteID]);
+ $status = ts('Selected Note has been deleted successfully.');
+ CRM_Core_Session::setStatus($status, ts('Deleted'), 'success');
return;
}
//process note
/**
* BAO object for crm_note table.
*/
-class CRM_Core_BAO_Note extends CRM_Core_DAO_Note {
+class CRM_Core_BAO_Note extends CRM_Core_DAO_Note implements \Civi\Test\HookInterface {
use CRM_Core_DynamicFKAccessTrait;
/**
return $notes;
}
+ /**
+ * Event fired prior to modifying a Note.
+ * @param \Civi\Core\Event\PreEvent $event
+ */
+ public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) {
+ if ($event->action === 'delete' && $event->id) {
+ // When deleting a note, also delete child notes
+ // This causes recursion as this hook is called again while deleting child notes,
+ // So the children of children, etc. will also be deleted.
+ foreach (self::getDescendentIds($event->id) as $child) {
+ self::deleteRecord(['id' => $child]);
+ }
+ }
+ }
+
/**
* Delete the notes.
*
* @param int $id
- * Note id.
- * @param bool $showStatus
- * Do we need to set status or not.
*
- * @return int|null
- * no of deleted notes on success, null otherwise
+ * @deprecated
+ * @return int
*/
- public static function del($id, $showStatus = TRUE) {
- $return = NULL;
- $recent = array($id);
- $note = new CRM_Core_DAO_Note();
- $note->id = $id;
- $note->find();
- $note->fetch();
- if ($note->entity_table == 'civicrm_note') {
- $status = ts('Selected Comment has been deleted successfully.');
- }
- else {
- $status = ts('Selected Note has been deleted successfully.');
- }
-
- // Delete all descendents of this Note
- foreach (self::getDescendentIds($id) as $childId) {
- $childNote = new CRM_Core_DAO_Note();
- $childNote->id = $childId;
- $childNote->delete();
- $recent[] = $childId;
- }
-
- $return = $note->delete();
- if ($showStatus) {
- CRM_Core_Session::setStatus($status, ts('Deleted'), 'success');
- }
+ public static function del($id) {
+ // CRM_Core_Error::deprecatedFunctionWarning('deleteRecord');
+ self::deleteRecord(['id' => $id]);
- // delete the recently created Note
- foreach ($recent as $recentId) {
- $noteRecent = array(
- 'id' => $recentId,
- 'type' => 'Note',
- );
- CRM_Utils_Recent::del($noteRecent);
- }
- return $return;
+ return 1;
}
/**
}
/**
- * Given a note id, get a list of the ids of all notes that are descendents of that note
+ * Get direct children of given parentId note
*
* @param int $parentId
- * Id of the given note.
- * @param array $ids
- * (reference) one-dimensional array to store found descendent ids.
*
* @return array
- * One-dimensional array containing ids of all desendent notes
+ * One-dimensional array containing ids of child notes
*/
- public static function getDescendentIds($parentId, &$ids = []) {
- // get direct children of given parentId note
+ public static function getDescendentIds($parentId) {
+ $ids = [];
$note = new CRM_Core_DAO_Note();
$note->entity_table = 'civicrm_note';
$note->entity_id = $parentId;
$note->find();
while ($note->fetch()) {
- // foreach child, add to ids list, and recurse
$ids[] = $note->id;
- self::getDescendentIds($note->id, $ids);
}
return $ids;
}
$contactNoteId = CRM_Core_DAO::executeQuery($contactQuery, $params);
while ($contactNoteId->fetch()) {
- self::del($contactNoteId->id, FALSE);
+ self::deleteRecord(['id' => $contactNoteId->id]);
}
}
CRM_Core_BAO_Note::add($noteParams, $noteIDs);
}
elseif ($noteId && $hasNoteField) {
- CRM_Core_BAO_Note::del($noteId, FALSE);
+ CRM_Core_BAO_Note::deleteRecord(['id' => $noteId]);
}
}
$note = CRM_Core_BAO_Note::getNote($id, 'civicrm_participant');
$noteId = key($note);
if ($noteId) {
- CRM_Core_BAO_Note::del($noteId, FALSE);
+ CRM_Core_BAO_Note::deleteRecord(['id' => $noteId]);
}
$participant->delete();
}
if ($this->_action & CRM_Core_Action::DELETE) {
- CRM_Core_BAO_Note::del($this->_id);
+ CRM_Core_BAO_Note::deleteRecord(['id' => $this->_id]);
+ $status = ts('Selected Note has been deleted successfully.');
+ CRM_Core_Session::setStatus($status, ts('Deleted'), 'success');
return;
}
* @return array
*/
function civicrm_api3_note_delete($params) {
- $result = new CRM_Core_BAO_Note();
- return $result->del($params['id']) ? civicrm_api3_create_success() : civicrm_api3_create_error('Error while deleting Note');
+ $result = CRM_Core_BAO_Note::deleteRecord($params);
+ return $result ? civicrm_api3_create_success() : civicrm_api3_create_error('Error while deleting Note');
}
/**
case 'block':
$info['definition'] = $this->definition + [
'title' => '',
- 'block' => $this->entity,
+ 'entity_type' => $this->entity,
'layout' => [],
];
break;
if ($embeddedForm['type'] === 'block') {
$info['blocks'][$blockTag] = $embeddedForm;
}
- if (!empty($embeddedForm['join'])) {
- $entities = array_unique(array_merge($entities, [$embeddedForm['join']]));
+ if (!empty($embeddedForm['join_entity'])) {
+ $entities = array_unique(array_merge($entities, [$embeddedForm['join_entity']]));
}
$scanBlocks($embeddedForm['layout']);
}
}
if ($info['definition']['type'] === 'block') {
- $blockEntity = $info['definition']['join'] ?? $info['definition']['block'];
+ $blockEntity = $info['definition']['join_entity'] ?? $info['definition']['entity_type'];
if ($blockEntity !== '*') {
$entities[] = $blockEntity;
}
if (!$newForm) {
$scanBlocks($info['definition']['layout']);
}
- $this->loadAvailableBlocks($entities, $info, [['join', 'IS NULL']]);
+ $this->loadAvailableBlocks($entities, $info, [['join_entity', 'IS NULL']]);
}
// Optimization - since contact fields are a combination of these three,
}
if ($entities) {
$blockInfo = Afform::get($this->checkPermissions)
- ->addSelect('name', 'title', 'block', 'join', 'directive_name', 'repeat')
+ ->addSelect('name', 'title', 'entity_type', 'join_entity', 'directive_name', 'repeat')
->setWhere($where)
->addWhere('type', '=', 'block')
- ->addWhere('block', 'IN', $entities)
+ ->addWhere('entity_type', 'IN', $entities)
->addWhere('directive_name', 'NOT IN', array_keys($info['blocks']))
->execute();
$info['blocks'] = array_merge(array_values($info['blocks']), (array) $blockInfo);
else if (editor.getFormType() === 'block') {
editor.layout['#children'] = editor.afform.layout;
- editor.blockEntity = editor.afform.join || editor.afform.block;
+ editor.blockEntity = editor.afform.join_entity || editor.afform.entity_type;
$scope.entities[editor.blockEntity] = backfillEntityDefaults({
type: editor.blockEntity,
name: editor.blockEntity,
$scope.blockTitles.length = 0;
_.each(afGui.meta.blocks, function(block, directive) {
if ((!search || _.contains(directive, search) || _.contains(block.name.toLowerCase(), search) || _.contains(block.title.toLowerCase(), search)) &&
- (block.block === '*' || block.block === ctrl.entity.type || (ctrl.entity.type === 'Contact' && block.block === ctrl.entity.data.contact_type)) &&
+ (block.entity_type === '*' || block.entity_type === ctrl.entity.type || (ctrl.entity.type === 'Contact' && block.entity_type === ctrl.entity.data.contact_type)) &&
block.name !== ctrl.editor.getAfform().name
) {
- var item = {"#tag": block.join ? "div" : directive};
- if (block.join) {
- item['af-join'] = block.join;
+ var item = {"#tag": block.join_entity ? "div" : directive};
+ if (block.join_entity) {
+ item['af-join'] = block.join_entity;
item['#children'] = [{"#tag": directive}];
}
if (block.repeat) {
};
_.each(afGui.meta.blocks, function(blockInfo, directive) {
- if (directive === ctrl.node['#tag'] || (blockInfo.join && blockInfo.join === ctrl.getFieldEntityType())) {
+ if (directive === ctrl.node['#tag'] || (blockInfo.join_entity && blockInfo.join_entity === ctrl.getFieldEntityType())) {
block.options.push({
id: directive,
text: blockInfo.title
* @return mixed|string
* Ex: '/var/www/sites/default/files/civicrm/afform'.
*/
- private function getSiteLocalPath() {
+ public function getSiteLocalPath() {
// TODO Allow a setting override.
// return Civi::paths()->getPath(Civi::settings()->get('afformPath'));
return Civi::paths()->getPath('[civicrm.files]/ang');
--- /dev/null
+<?php
+use CRM_Afform_ExtensionUtil as E;
+
+/**
+ * Collection of upgrade steps.
+ */
+class CRM_Afform_Upgrader extends CRM_Afform_Upgrader_Base {
+
+ /**
+ * Update names of blocks and joins
+ *
+ * @return TRUE on success
+ * @throws Exception
+ */
+ public function upgrade_1000() {
+ $this->ctx->log->info('Applying update 1000');
+ $scanner = new CRM_Afform_AfformScanner();
+ $localDir = $scanner->getSiteLocalPath();
+
+ // Update form markup with new block directive names
+ $replacements = [
+ 'afjoin-address-default>' => 'afblock-contact-address>',
+ 'afjoin-email-default>' => 'afblock-contact-email>',
+ 'afjoin-i-m-default>' => 'afblock-contact-i-m>',
+ 'afjoin-phone-default>' => 'afblock-contact-phone>',
+ 'afjoin-website-default>' => 'afblock-contact-website>',
+ 'afjoin-custom-' => 'afblock-custom-',
+ ];
+ foreach (glob("$localDir/*." . $scanner::LAYOUT_FILE) as $fileName) {
+ $html = file_get_contents($fileName);
+ $html = str_replace(array_keys($replacements), array_values($replacements), $html);
+ file_put_contents($fileName, $html);
+ }
+
+ // Update form metadata with new block property names
+ $replacements = [
+ 'join' => 'join_entity',
+ 'block' => 'entity_type',
+ ];
+ foreach (glob("$localDir/*." . $scanner::METADATA_FILE) as $fileName) {
+ $meta = json_decode(file_get_contents($fileName), TRUE);
+ foreach ($replacements as $oldKey => $newKey) {
+ if (isset($meta[$oldKey])) {
+ $meta[$newKey] = $meta[$oldKey];
+ unset($meta[$oldKey]);
+ }
+ }
+ if (!empty($meta['entity_type'])) {
+ $meta['type'] = 'block';
+ }
+ file_put_contents($fileName, json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
+ }
+
+ return TRUE;
+ }
+
+}
--- /dev/null
+<?php
+
+// AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file
+use CRM_Afform_ExtensionUtil as E;
+
+/**
+ * Base class which provides helpers to execute upgrade logic
+ */
+class CRM_Afform_Upgrader_Base {
+
+ /**
+ * @var CRM_Afform_Upgrader_Base
+ */
+ public static $instance;
+
+ /**
+ * @var CRM_Queue_TaskContext
+ */
+ protected $ctx;
+
+ /**
+ * @var string
+ * eg 'com.example.myextension'
+ */
+ protected $extensionName;
+
+ /**
+ * @var string
+ * full path to the extension's source tree
+ */
+ protected $extensionDir;
+
+ /**
+ * @var array
+ * sorted numerically
+ */
+ private $revisions;
+
+ /**
+ * @var bool
+ * Flag to clean up extension revision data in civicrm_setting
+ */
+ private $revisionStorageIsDeprecated = FALSE;
+
+ /**
+ * Obtain a reference to the active upgrade handler.
+ */
+ public static function instance() {
+ if (!self::$instance) {
+ self::$instance = new CRM_Afform_Upgrader(
+ 'org.civicrm.afform',
+ E::path()
+ );
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Adapter that lets you add normal (non-static) member functions to the queue.
+ *
+ * Note: Each upgrader instance should only be associated with one
+ * task-context; otherwise, this will be non-reentrant.
+ *
+ * ```
+ * CRM_Afform_Upgrader_Base::_queueAdapter($ctx, 'methodName', 'arg1', 'arg2');
+ * ```
+ */
+ public static function _queueAdapter() {
+ $instance = self::instance();
+ $args = func_get_args();
+ $instance->ctx = array_shift($args);
+ $instance->queue = $instance->ctx->queue;
+ $method = array_shift($args);
+ return call_user_func_array([$instance, $method], $args);
+ }
+
+ /**
+ * CRM_Afform_Upgrader_Base constructor.
+ *
+ * @param $extensionName
+ * @param $extensionDir
+ */
+ public function __construct($extensionName, $extensionDir) {
+ $this->extensionName = $extensionName;
+ $this->extensionDir = $extensionDir;
+ }
+
+ // ******** Task helpers ********
+
+ /**
+ * Run a CustomData file.
+ *
+ * @param string $relativePath
+ * the CustomData XML file path (relative to this extension's dir)
+ * @return bool
+ */
+ public function executeCustomDataFile($relativePath) {
+ $xml_file = $this->extensionDir . '/' . $relativePath;
+ return $this->executeCustomDataFileByAbsPath($xml_file);
+ }
+
+ /**
+ * Run a CustomData file
+ *
+ * @param string $xml_file
+ * the CustomData XML file path (absolute path)
+ *
+ * @return bool
+ */
+ protected function executeCustomDataFileByAbsPath($xml_file) {
+ $import = new CRM_Utils_Migrate_Import();
+ $import->run($xml_file);
+ return TRUE;
+ }
+
+ /**
+ * Run a SQL file.
+ *
+ * @param string $relativePath
+ * the SQL file path (relative to this extension's dir)
+ *
+ * @return bool
+ */
+ public function executeSqlFile($relativePath) {
+ CRM_Utils_File::sourceSQLFile(
+ CIVICRM_DSN,
+ $this->extensionDir . DIRECTORY_SEPARATOR . $relativePath
+ );
+ return TRUE;
+ }
+
+ /**
+ * Run the sql commands in the specified file.
+ *
+ * @param string $tplFile
+ * The SQL file path (relative to this extension's dir).
+ * Ex: "sql/mydata.mysql.tpl".
+ *
+ * @return bool
+ * @throws \CRM_Core_Exception
+ */
+ public function executeSqlTemplate($tplFile) {
+ // Assign multilingual variable to Smarty.
+ $upgrade = new CRM_Upgrade_Form();
+
+ $tplFile = CRM_Utils_File::isAbsolute($tplFile) ? $tplFile : $this->extensionDir . DIRECTORY_SEPARATOR . $tplFile;
+ $smarty = CRM_Core_Smarty::singleton();
+ $smarty->assign('domainID', CRM_Core_Config::domainID());
+ CRM_Utils_File::sourceSQLFile(
+ CIVICRM_DSN, $smarty->fetch($tplFile), NULL, TRUE
+ );
+ return TRUE;
+ }
+
+ /**
+ * Run one SQL query.
+ *
+ * This is just a wrapper for CRM_Core_DAO::executeSql, but it
+ * provides syntactic sugar for queueing several tasks that
+ * run different queries
+ *
+ * @return bool
+ */
+ public function executeSql($query, $params = []) {
+ // FIXME verify that we raise an exception on error
+ CRM_Core_DAO::executeQuery($query, $params);
+ return TRUE;
+ }
+
+ /**
+ * Syntactic sugar for enqueuing a task which calls a function in this class.
+ *
+ * The task is weighted so that it is processed
+ * as part of the currently-pending revision.
+ *
+ * After passing the $funcName, you can also pass parameters that will go to
+ * the function. Note that all params must be serializable.
+ */
+ public function addTask($title) {
+ $args = func_get_args();
+ $title = array_shift($args);
+ $task = new CRM_Queue_Task(
+ [get_class($this), '_queueAdapter'],
+ $args,
+ $title
+ );
+ return $this->queue->createItem($task, ['weight' => -1]);
+ }
+
+ // ******** Revision-tracking helpers ********
+
+ /**
+ * Determine if there are any pending revisions.
+ *
+ * @return bool
+ */
+ public function hasPendingRevisions() {
+ $revisions = $this->getRevisions();
+ $currentRevision = $this->getCurrentRevision();
+
+ if (empty($revisions)) {
+ return FALSE;
+ }
+ if (empty($currentRevision)) {
+ return TRUE;
+ }
+
+ return ($currentRevision < max($revisions));
+ }
+
+ /**
+ * Add any pending revisions to the queue.
+ *
+ * @param CRM_Queue_Queue $queue
+ */
+ public function enqueuePendingRevisions(CRM_Queue_Queue $queue) {
+ $this->queue = $queue;
+
+ $currentRevision = $this->getCurrentRevision();
+ foreach ($this->getRevisions() as $revision) {
+ if ($revision > $currentRevision) {
+ $title = E::ts('Upgrade %1 to revision %2', [
+ 1 => $this->extensionName,
+ 2 => $revision,
+ ]);
+
+ // note: don't use addTask() because it sets weight=-1
+
+ $task = new CRM_Queue_Task(
+ [get_class($this), '_queueAdapter'],
+ ['upgrade_' . $revision],
+ $title
+ );
+ $this->queue->createItem($task);
+
+ $task = new CRM_Queue_Task(
+ [get_class($this), '_queueAdapter'],
+ ['setCurrentRevision', $revision],
+ $title
+ );
+ $this->queue->createItem($task);
+ }
+ }
+ }
+
+ /**
+ * Get a list of revisions.
+ *
+ * @return array
+ * revisionNumbers sorted numerically
+ */
+ public function getRevisions() {
+ if (!is_array($this->revisions)) {
+ $this->revisions = [];
+
+ $clazz = new ReflectionClass(get_class($this));
+ $methods = $clazz->getMethods();
+ foreach ($methods as $method) {
+ if (preg_match('/^upgrade_(.*)/', $method->name, $matches)) {
+ $this->revisions[] = $matches[1];
+ }
+ }
+ sort($this->revisions, SORT_NUMERIC);
+ }
+
+ return $this->revisions;
+ }
+
+ public function getCurrentRevision() {
+ $revision = CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName);
+ if (!$revision) {
+ $revision = $this->getCurrentRevisionDeprecated();
+ }
+ return $revision;
+ }
+
+ private function getCurrentRevisionDeprecated() {
+ $key = $this->extensionName . ':version';
+ if ($revision = \Civi::settings()->get($key)) {
+ $this->revisionStorageIsDeprecated = TRUE;
+ }
+ return $revision;
+ }
+
+ public function setCurrentRevision($revision) {
+ CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision);
+ // clean up legacy schema version store (CRM-19252)
+ $this->deleteDeprecatedRevision();
+ return TRUE;
+ }
+
+ private function deleteDeprecatedRevision() {
+ if ($this->revisionStorageIsDeprecated) {
+ $setting = new CRM_Core_BAO_Setting();
+ $setting->name = $this->extensionName . ':version';
+ $setting->delete();
+ CRM_Core_Error::debug_log_message("Migrated extension schema revision ID for {$this->extensionName} from civicrm_setting (deprecated) to civicrm_extension.\n");
+ }
+ }
+
+ // ******** Hook delegates ********
+
+ /**
+ * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install
+ */
+ public function onInstall() {
+ $files = glob($this->extensionDir . '/sql/*_install.sql');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+ }
+ }
+ $files = glob($this->extensionDir . '/sql/*_install.mysql.tpl');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ $this->executeSqlTemplate($file);
+ }
+ }
+ $files = glob($this->extensionDir . '/xml/*_install.xml');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ $this->executeCustomDataFileByAbsPath($file);
+ }
+ }
+ if (is_callable([$this, 'install'])) {
+ $this->install();
+ }
+ }
+
+ /**
+ * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall
+ */
+ public function onPostInstall() {
+ $revisions = $this->getRevisions();
+ if (!empty($revisions)) {
+ $this->setCurrentRevision(max($revisions));
+ }
+ if (is_callable([$this, 'postInstall'])) {
+ $this->postInstall();
+ }
+ }
+
+ /**
+ * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall
+ */
+ public function onUninstall() {
+ $files = glob($this->extensionDir . '/sql/*_uninstall.mysql.tpl');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ $this->executeSqlTemplate($file);
+ }
+ }
+ if (is_callable([$this, 'uninstall'])) {
+ $this->uninstall();
+ }
+ $files = glob($this->extensionDir . '/sql/*_uninstall.sql');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+ }
+ }
+ }
+
+ /**
+ * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable
+ */
+ public function onEnable() {
+ // stub for possible future use
+ if (is_callable([$this, 'enable'])) {
+ $this->enable();
+ }
+ }
+
+ /**
+ * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable
+ */
+ public function onDisable() {
+ // stub for possible future use
+ if (is_callable([$this, 'disable'])) {
+ $this->disable();
+ }
+ }
+
+ public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) {
+ switch ($op) {
+ case 'check':
+ return [$this->hasPendingRevisions()];
+
+ case 'enqueue':
+ return $this->enqueuePendingRevisions($queue);
+
+ default:
+ }
+ }
+
+}
->alterHtml(';\\.aff\\.html$;', function($doc, $path) {
try {
$module = \Civi::service('angular')->getModule(basename($path, '.aff.html'));
- $meta = \Civi\Api4\Afform::get(FALSE)->addWhere('name', '=', $module['_afform'])->setSelect(['join', 'block'])->execute()->first();
+ $meta = \Civi\Api4\Afform::get(FALSE)->addWhere('name', '=', $module['_afform'])->setSelect(['join_entity', 'entity_type'])->execute()->first();
}
catch (\Exception $e) {
}
- $blockEntity = $meta['join'] ?? $meta['block'] ?? NULL;
+ $blockEntity = $meta['join_entity'] ?? $meta['entity_type'] ?? NULL;
if (!$blockEntity) {
$entities = self::getFormEntities($doc);
}
];
foreach ($toGet as $key => $get) {
if (!in_array($info[$key], $get)) {
- continue;
+ continue 2;
}
}
$record = $scanner->getMeta($name);
protected function getAutoGenerated(&$names, $toGet, $getLayout) {
$values = $groupNames = [];
foreach ($toGet['name'] ?? [] as $name) {
- if (strpos($name, 'afjoinCustom_') === 0 && strlen($name) > 13) {
- $groupNames[] = substr($name, 13);
+ if (strpos($name, 'afblockCustom_') === 0 && strlen($name) > 13) {
+ $groupNames[] = substr($name, 14);
}
}
// Early return if this api call is fetching afforms by name and those names are not custom-related
if ((!empty($toGet['name']) && !$groupNames)
- || (!empty($toGet['module_name']) && !strstr(implode(' ', $toGet['module_name']), 'afjoinCustom'))
- || (!empty($toGet['directive_name']) && !strstr(implode(' ', $toGet['directive_name']), 'afjoin-custom'))
+ || (!empty($toGet['module_name']) && !strstr(implode(' ', $toGet['module_name']), 'afblockCustom'))
+ || (!empty($toGet['directive_name']) && !strstr(implode(' ', $toGet['directive_name']), 'afblock-custom'))
) {
return $values;
}
);
}
foreach ($customApi->execute() as $custom) {
- $name = 'afjoinCustom_' . $custom['name'];
+ $name = 'afblockCustom_' . $custom['name'];
if (!in_array($name, $names)) {
$names[] = $name;
}
'name' => $name,
'type' => 'block',
'requires' => [],
- 'title' => E::ts('%1 block (default)', [1 => $custom['title']]),
+ 'title' => E::ts('%1 block', [1 => $custom['title']]),
'description' => '',
'is_dashlet' => FALSE,
'is_public' => FALSE,
'is_token' => FALSE,
'permission' => 'access CiviCRM',
- 'join' => 'Custom_' . $custom['name'],
- 'block' => $custom['extends'],
+ 'join_entity' => 'Custom_' . $custom['name'],
+ 'entity_type' => $custom['extends'],
'repeat' => $custom['max_multiple'] ?: TRUE,
'has_base' => TRUE,
];
'data_type' => 'Array',
],
[
- 'name' => 'block',
+ 'name' => 'entity_type',
+ 'description' => 'Block used for this entity type',
],
[
- 'name' => 'join',
+ 'name' => 'join_entity',
+ 'description' => 'Used for blocks that join a sub-entity (e.g. Emails for a Contact)',
],
[
'name' => 'title',
[
'name' => 'layout',
'data_type' => 'Array',
+ 'description' => 'HTML form layout; format is controlled by layoutFormat param',
],
];
// Calculated fields returned by get action
// If no name given, create a unique name based on the title
if (empty($item['name'])) {
- $prefix = !empty($item['join']) ? "afjoin-{$item['join']}" : (!empty($item['block']) ? ('afblock-' . str_replace('*', 'all', $item['block'])) : 'afform');
+ $prefix = 'af' . ($item['type'] ?? '');
$item['name'] = _afform_angular_module_name($prefix . '-' . \CRM_Utils_String::munge($item['title'], '-'));
$suffix = '';
while (
--- /dev/null
+{
+ "title": "Contact Address(es)",
+ "type": "block",
+ "entity_type": "Contact",
+ "join_entity": "Address",
+ "repeat": true
+}
--- /dev/null
+{
+ "title": "Contact Email(s)",
+ "type": "block",
+ "entity_type": "Contact",
+ "join_entity": "Email",
+ "repeat": true
+}
--- /dev/null
+{
+ "title": "Contact IM(s)",
+ "type": "block",
+ "entity_type": "Contact",
+ "join_entity": "IM",
+ "repeat": true
+}
--- /dev/null
+{
+ "title": "Contact Phone(s)",
+ "type": "block",
+ "entity_type": "Contact",
+ "join_entity": "Phone",
+ "repeat": true
+}
--- /dev/null
+{
+ "title": "Contact Website(s)",
+ "type": "block",
+ "entity_type": "Contact",
+ "join_entity": "Website",
+ "repeat": true
+}
{
- "title": "Household Name (default)",
+ "title": "Household Name",
"type": "block",
- "block": "Household"
+ "entity_type": "Household"
}
{
- "title": "Individual Name (default)",
+ "title": "Individual Name",
"type": "block",
- "block": "Individual"
+ "entity_type": "Individual"
}
{
- "title": "Organization Name (default)",
+ "title": "Organization Name",
"type": "block",
- "block": "Organization"
+ "entity_type": "Organization"
}
+++ /dev/null
-{
- "title": "Address Block (default)",
- "type": "block",
- "block": "Contact",
- "join": "Address",
- "repeat": true
-}
+++ /dev/null
-{
- "title": "Email (default)",
- "type": "block",
- "block": "Contact",
- "join": "Email",
- "repeat": true
-}
+++ /dev/null
-{
- "title": "IM (default)",
- "type": "block",
- "block": "Contact",
- "join": "IM",
- "repeat": true
-}
+++ /dev/null
-{
- "title": "Phone (default)",
- "type": "block",
- "block": "Contact",
- "join": "Phone",
- "repeat": true
-}
+++ /dev/null
-{
- "title": "Website (default)",
- "type": "block",
- "block": "Contact",
- "join": "Website",
- "repeat": true
-}
<legend class="af-text">Individual 1</legend>
<afblock-name-individual></afblock-name-individual>
<div af-join="Email" min="1" af-repeat="Add">
- <afjoin-email-default></afjoin-email-default>
+ <afblock-contact-email></afblock-contact-email>
</div>
<af-field name="employer_id" defn="{input_type: 'Select', input_attrs: {}}" />
</fieldset>
<af-field name="organization_name" />
</div>
<div af-join="Email">
- <afjoin-email-default></afjoin-email-default>
+ <afblock-contact-email></afblock-contact-email>
</div>
</fieldset>
<button class="af-button btn-primary" crm-icon="fa-check" ng-click="afform.submit()">Submit</button>
<legend class="af-text">Individual 1</legend>
<afblock-name-individual></afblock-name-individual>
<div af-join="Custom_MyThings" af-repeat="Add" max="2">
- <afjoin-custom-my-things></afjoin-custom-my-things>
+ <afblock-custom-my-things></afblock-custom-my-things>
</div>
</fieldset>
<button class="af-button btn-primary" crm-icon="fa-check" ng-click="afform.submit()">Submit</button>
* which can be submitted multiple times
*/
public function testMultiRecordCustomBlock(): void {
- $customGroup = \Civi\Api4\CustomGroup::create(FALSE)
+ \Civi\Api4\CustomGroup::create(FALSE)
->addValue('name', 'MyThings')
->addValue('title', 'My Things')
->addValue('style', 'Tab with table')
// Creating a custom group should automatically create an afform block
$block = \Civi\Api4\Afform::get()
- ->addWhere('name', '=', 'afjoinCustom_MyThings')
+ ->addWhere('name', '=', 'afblockCustom_MyThings')
->setLayoutFormat('shallow')
->setFormatWhitespace(TRUE)
- ->execute()->first();
+ ->execute()->single();
$this->assertEquals(2, $block['repeat']);
+ $this->assertEquals('afblock-custom-my-things', $block['directive_name']);
$this->assertEquals('my_text', $block['layout'][0]['name']);
$this->assertEquals('my_friend', $block['layout'][1]['name']);
--- /dev/null
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+
+namespace api\v4\Entity;
+
+use api\v4\UnitTestCase;
+use Civi\Api4\Note;
+use Civi\Test\TransactionalInterface;
+
+/**
+ * @group headless
+ */
+class NoteTest extends UnitTestCase implements TransactionalInterface {
+
+ public function testDeleteWithChildren() {
+ $c1 = $this->createEntity(['type' => 'Individual']);
+
+ $text = uniqid(__FUNCTION__, TRUE);
+
+ // Create 2 top-level notes.
+ $notes = Note::save(FALSE)
+ ->setRecords([['note' => $text], ['note' => $text]])
+ ->setDefaults([
+ 'entity_id' => $c1['id'],
+ 'entity_table' => 'civicrm_contact',
+ ])->execute();
+
+ // Add 2 children of the first note.
+ $children = Note::save(FALSE)
+ ->setRecords([['note' => $text], ['note' => $text]])
+ ->setDefaults([
+ 'entity_id' => $notes->first()['id'],
+ 'entity_table' => 'civicrm_note',
+ ])->execute();
+
+ // Add 2 children of the first child.
+ $grandChildren = Note::save(FALSE)
+ ->setRecords([['note' => $text], ['note' => $text]])
+ ->setDefaults([
+ 'entity_id' => $children->first()['id'],
+ 'entity_table' => 'civicrm_note',
+ ])->execute();
+
+ // We just created 2 top-level notes and 4 children. Ensure we have a total of 6.
+ $existing = Note::get(FALSE)
+ ->addWhere('note', '=', $text)
+ ->execute();
+ $this->assertCount(6, $existing);
+
+ // Delete parent
+ Note::delete(FALSE)
+ ->addWhere('id', '=', $notes->first()['id'])
+ ->execute();
+
+ // Should have deleted 1 parent + 4 child-notes, for a new total of 1 remaining.
+ $existing = Note::get(FALSE)
+ ->addWhere('note', '=', $text)
+ ->execute();
+ $this->assertCount(1, $existing);
+ }
+
+}