diff --git a/app/config/schema/schema.json b/app/config/schema/schema.json index 00ff6b6fc..bda138bd3 100644 --- a/app/config/schema/schema.json +++ b/app/config/schema/schema.json @@ -10,11 +10,11 @@ "columns": { "co_id": { "type": "integer", "foreignkey": { "table": "cos", "column": "id" }, "notnull": true }, - "co_person_id": { "type": "integer", "foreignkey": { "table": "co_people", "column": "id" } }, "description": { "type": "string", "size": 128 }, + "external_identity_id": { "type": "integer", "foreignkey": { "table": "external_identities", "column": "id" } }, "id": { "type": "integer", "autoincrement": true, "primarykey": true }, "name": { "type": "string", "size": 128, "notnull": true }, - "org_identity_id": { "type": "integer", "foreignkey": { "table": "org_identities", "column": "id" } }, + "person_id": { "type": "integer", "foreignkey": { "table": "people", "column": "id" } }, "status": { "type": "string", "size": 2 }, "type_id": { "type": "integer", "foreignkey": { "table": "types", "column": "id" }, "notnull": true }, "valid_from": { "type": "datetime" }, @@ -106,7 +106,7 @@ } }, - "co_people": { + "people": { "columns": { "id": {}, "co_id": {}, @@ -115,14 +115,14 @@ "date_of_birth": { "type": "date" } }, "indexes": { - "co_people_i1": { "columns": [ "co_id" ] } + "people_i1": { "columns": [ "co_id" ] } } }, - "org_identities": { + "external_identities": { "columns": { "id": {}, - "co_person_id": { "notnull": true }, + "person_id": { "notnull": true }, "status": {}, "affiliation": { "type": "string", "size": 32 }, "date_of_birth": { "type": "date" }, @@ -133,7 +133,7 @@ "valid_through": {} }, "indexes": { - "org_identities_i1": { "columns": [ "co_person_id" ] } + "external_identities_i1": { "columns": [ "person_id" ] } } }, @@ -152,7 +152,7 @@ "indexes": { "names_i1": { "columns": [ "type_id"] } }, - "mvea": [ "co_person", "org_identity" ], + "mvea": [ "person", "external_identity" ], "sourced": true }, @@ -166,11 +166,11 @@ "status": {} }, "indexes": { - "identifiers_i1": { "columns": [ "identifier", "type_id", "co_person_id" ] }, - "identifiers_i2": { "columns": [ "identifier", "type_id", "org_identity_id" ] }, + "identifiers_i1": { "columns": [ "identifier", "type_id", "person_id" ] }, + "identifiers_i2": { "columns": [ "identifier", "type_id", "external_identity_id" ] }, "identifiers_i3": { "columns": [ "type_id"] } }, - "mvea": [ "co_person", "org_identity" ], + "mvea": [ "person", "external_identity" ], "sourced": true } }, diff --git a/app/resources/locales/en_US/default.po b/app/resources/locales/en_US/default.po index f3f879763..4aedbdc17 100644 --- a/app/resources/locales/en_US/default.po +++ b/app/resources/locales/en_US/default.po @@ -53,7 +53,7 @@ msgid "registry.cmd.opt.admin-username" msgstr "Username of initial platform administrator" msgid "registry.cmd.opt.force" -msgstr "Force a rerun of setup (only if you know what you are doing)"" +msgstr "Force a rerun of setup (only if you know what you are doing)" msgid "registry.cmd.opt.not" msgstr "Calculate changes but do not apply" @@ -74,9 +74,6 @@ msgstr "Generating salt file" msgid "registry.ct.ApiUsers" msgstr "{0,plural,=1{API User} other{API Users}}" -msgid "registry.ct.CoPeople" -msgstr "{0,plural,=1{CO Person} other{CO People}}" - msgid "registry.ct.CoSettings" msgstr "{0,plural,=1{CO Setting} other{CO Settings}}" @@ -89,6 +86,12 @@ msgstr "{0,plural,=1{COU} other{COUs}}" msgid "registry.ct.Dashboards" msgstr "{0,plural,=1{Dashboard} other{Dashboards}}" +msgid "registry.ct.ExternalIdentities" +msgstr "{0,plural,=1{External Identity} other{External Identities}}" + +msgid "registry.ct.People" +msgstr "{0,plural,=1{Person} other{People}}" + ### Enumerations msgid "registry.en.BooleanEnum.0" msgstr "False" diff --git a/app/src/Command/DatabaseCommand.php b/app/src/Command/DatabaseCommand.php index 2df112b80..1bf1e500b 100644 --- a/app/src/Command/DatabaseCommand.php +++ b/app/src/Command/DatabaseCommand.php @@ -1,6 +1,6 @@ addOption('not', [ 'short' => 'n', 'boolean' => true, - 'help' => __(__('product.code').'.cmd.opt.not') + 'help' => __('registry.cmd.opt.not') ]); return $parser; @@ -67,7 +63,7 @@ protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOption /** * Execute the Database Command. * - * @since COmanage Match v1.0.0, COmanage Registry v5.0.0 + * @since COmanage Registry v5.0.0 * @param Arguments $args Command Arguments * @param ConsoleIo $io Console IO * @throws RuntimeException @@ -82,18 +78,15 @@ public function execute(Arguments $args, ConsoleIo $io) { // debug and poorly maintained. DBAL doesn't have a schema format (like axmls) // but it does everything else, and specifying a schema format is easy. - // What component are we? - $COmponent = __('product.code'); - // First try to parse our schema file $schemaFile = ROOT . DS . 'config' . DS . 'schema' . DS . 'schema.json'; if(!is_readable($schemaFile)) { - throw new \RuntimeException(__($COmponent.'.er.file', [$schemaFile])); + throw new \RuntimeException(__('registry.er.file', [$schemaFile])); } - $io->out(__($COmponent.'.cmd.db.schema', [$schemaFile])); + $io->out(__('registry.cmd.db.schema', [$schemaFile])); $json = file_get_contents($schemaFile); @@ -105,7 +98,7 @@ public function execute(Arguments $args, ConsoleIo $io) { // - An unmatched brace { } // - A trailing comma (permitted in PHP but not JSON) // - Single quotes instead of double quotes - throw new \RuntimeException(__($COmponent.'.er.schema.parse', [$schemaFile])); + throw new \RuntimeException(__('registry.er.schema.parse', [$schemaFile])); } // Use the ConnectionManager to get the database config to pass to adodb. @@ -143,7 +136,7 @@ public function execute(Arguments $args, ConsoleIo $io) { (array)$cCfg); if(!isset($colCfg->type)) { - throw new \RuntimeException(__('match.er.schema.column', [$tName, $cName])); + throw new \RuntimeException(__('registry.er.schema.column', [$tName, $cName])); } // For type definitions see https://www.doctrine-project.org/projects/doctrine-dbal/en/2.12/reference/types.html#types @@ -296,9 +289,9 @@ public function execute(Arguments $args, ConsoleIo $io) { } if(!$doSQL) { - $io->out(__($COmponent.'.cmd.db.noop')); + $io->out(__('registry.cmd.db.noop')); } else { - $io->out(__($COmponent.'.cmd.db.ok')); + $io->out(__('registry.cmd.db.ok')); } } catch(\Exception $e) { diff --git a/app/src/Command/TransmogrifyCommand.php b/app/src/Command/TransmogrifyCommand.php index 4713dbfc8..919fbc820 100644 --- a/app/src/Command/TransmogrifyCommand.php +++ b/app/src/Command/TransmogrifyCommand.php @@ -71,27 +71,35 @@ class TransmogrifyCommand extends Command { 'displayField' => 'name' ], //'dashboards' => [ 'source' => 'cm_co_dashboards' ] - 'co_people' => [ + 'people' => [ 'source' => 'cm_co_people', 'displayField' => 'id', - 'cache' => [ 'co_id' ] + 'cache' => [ 'co_id' ], + 'fieldMap' => [ + // Rename the changelog key + 'co_person_id' => 'person_id' + ] ], - 'org_identities' => [ + 'external_identities' => [ 'source' => 'cm_org_identities', 'displayField' => 'id', 'fieldMap' => [ 'co_id' => null, - 'co_person_id' => '&map_org_identity_co_person_id', + 'person_id' => '&map_org_identity_co_person_id', 'o' => 'organization', - 'ou' => 'department' + 'ou' => 'department', + // Rename the changelog key + 'org_identity_id' => 'external_identity_id' ], - 'cache' => [ 'co_person_id' ] + 'cache' => [ 'person_id' ] ], 'names' => [ 'source' => 'cm_names', 'displayField' => 'id', 'booleans' => [ 'primary_name' ], 'fieldMap' => [ + 'co_person_id' => 'person_id', + 'org_identity_id' => 'external_identity_id', // We need to map type_id before we null out type 'type_id' => '&map_name_type', 'type' => null @@ -102,6 +110,8 @@ class TransmogrifyCommand extends Command { 'displayField' => 'id', 'booleans' => [ 'login' ], 'fieldMap' => [ + 'co_person_id' => 'person_id', + 'org_identity_id' => 'external_identity_id', 'type_id' => '&map_identifier_type', 'type' => null, // XXX temporary until tables are migrated @@ -276,7 +286,7 @@ public function execute(Arguments $args, ConsoleIo $io) { $io->out(floor(($tally * 100)/$count) . "% done"); } - $max = $this->inconn->fetchColumn('SELECT MAX(id) FROM ' . $this->tables[$t]['source']); + $max = $this->inconn->fetchOne('SELECT MAX(id) FROM ' . $this->tables[$t]['source']); $max++; $io->out("= New max: " . $max); @@ -302,17 +312,17 @@ protected function findCoId(array $row) { // By the time we're called, we should have transmogrified the Org Identity // and CO Person data, so we can just walk the caches - if(!empty($row['co_person_id'])) { - if(isset($this->cache['co_people']['id'][ $row['co_person_id'] ]['co_id'])) { - return $this->cache['co_people']['id'][ $row['co_person_id'] ]['co_id']; + if(!empty($row['person_id'])) { + if(isset($this->cache['people']['id'][ $row['person_id'] ]['co_id'])) { + return $this->cache['people']['id'][ $row['person_id'] ]['co_id']; } - } elseif(!empty($row['org_identity_id'])) { + } elseif(!empty($row['external_identity_id'])) { // Map the OrgIdentity to a CO Person, then to the CO - if(!empty($this->cache['org_identities']['id'][ $row['org_identity_id'] ]['co_person_id'])) { - $coPersonId = $this->cache['org_identities']['id'][ $row['org_identity_id'] ]['co_person_id']; + if(!empty($this->cache['external_identities']['id'][ $row['external_identity_id'] ]['person_id'])) { + $personId = $this->cache['external_identities']['id'][ $row['external_identity_id'] ]['person_id']; - if(isset($this->cache['co_people']['id'][ $coPersonId ]['co_id'])) { - return $this->cache['co_people']['id'][ $coPersonId ]['co_id']; + if(isset($this->cache['people']['id'][ $personId ]['co_id'])) { + return $this->cache['people']['id'][ $personId ]['co_id']; } } } @@ -463,7 +473,7 @@ protected function map_now(array $row) { protected function map_org_identity_co_person_id(array $row) { // PE eliminates OrgIdentityLink, so we need to map each Org Identity to - // a CO Person ID. This is a bit trickier than it sounds, since an Org Identity + // a Person ID. This is a bit trickier than it sounds, since an Org Identity // could have been relinked. // Before Transmogrification, we require that Org Identities are unpooled. @@ -479,7 +489,7 @@ protected function map_org_identity_co_person_id(array $row) { // do that.) Historical information remains available in history_records, // and if the deployer keeps an archive of the old database. - // To figure out which co_person_id to use, we pull the record with the + // To figure out which person_id to use, we pull the record with the // highest revision number. Note we might be transmogrifying a deleted row, // so we can't ignore deleted rows here. diff --git a/app/src/Controller/Component/RegistryAuthComponent.php b/app/src/Controller/Component/RegistryAuthComponent.php index 29cbfd721..4f4d9a049 100644 --- a/app/src/Controller/Component/RegistryAuthComponent.php +++ b/app/src/Controller/Component/RegistryAuthComponent.php @@ -321,12 +321,12 @@ public function getMenuPermissions() { $permissions = []; // XXX need to set permissions according to current user's roles - // Can manage CO People in the current CO - $permissions['co_people'] = true; - // Can access the Configuration Dashboard for the current CO $permissions['configuration'] = true; + // Can manage People in the current CO + $permissions['people'] = true; + return $permissions; } diff --git a/app/src/Controller/CoPeopleController.php b/app/src/Controller/PeopleController.php similarity index 57% rename from app/src/Controller/CoPeopleController.php rename to app/src/Controller/PeopleController.php index a7f710ffa..7b5687003 100644 --- a/app/src/Controller/CoPeopleController.php +++ b/app/src/Controller/PeopleController.php @@ -1,6 +1,6 @@ [ +/* +We should add a more configurable permissions setting that controls CO Person +visibility, probably via CO Settings. eg: + CO Admin - Only CO Admins can see CO Person records + COU Admin - COU Admins can see CO Person records, plus CO Person Role records they manage + Any Admin - Any CO or COU Admin can see any CO Person and CO Person Role record + CO Group - (intended for helpdesk, maybe create a special helpdesk group instead?) Any Admin + members of the Group + + We might also want to introduce a new "Permission" object to abstract this out here + and in other places (like Enrollment Flow Authz). Though Permissions would still be + managed in the relevant UI (eg: CO Settings), the model abstraction would handle + rendering a View Element and processing the Permission at run time + + See also: CO-931, CO-1156, CO-1524 + */ 'canvas' => ['platformAdmin', 'coAdmin'], 'delete' => ['platformAdmin', 'coAdmin'], 'edit' => ['platformAdmin', 'coAdmin'], @@ -50,6 +65,23 @@ class CoPeopleController extends StandardController { ] ]; + public $pagination = [ + 'order' => [ +// XXX this will sort by family name, but it this universally correct? +// so we need a configuration, or can we do something automagic? +// (ie: what is CJK sort order?) +// C=pinyin, so basically latin; J=KSTNHMYRW/AIUEO; K=hangugl +// so basically a mess... let's just use family name for now and wait for +// (and we haven't even gotten to other languages like Hindi) +// someone to file an RFE + 'PrimaryName.family' => 'asc' + ], + 'sortableFields' => [ + 'PrimaryName.given', + 'PrimaryName.family' + ] + ]; + /** * Handle a canvas request. * diff --git a/app/src/Model/Entity/OrgIdentity.php b/app/src/Model/Entity/ExternalIdentity.php similarity index 93% rename from app/src/Model/Entity/OrgIdentity.php rename to app/src/Model/Entity/ExternalIdentity.php index 77580ed21..f0beb80f7 100644 --- a/app/src/Model/Entity/OrgIdentity.php +++ b/app/src/Model/Entity/ExternalIdentity.php @@ -1,6 +1,6 @@ true, 'id' => false, diff --git a/app/src/Model/Entity/CoPerson.php b/app/src/Model/Entity/Person.php similarity index 94% rename from app/src/Model/Entity/CoPerson.php rename to app/src/Model/Entity/Person.php index ede40d399..be8001256 100644 --- a/app/src/Model/Entity/CoPerson.php +++ b/app/src/Model/Entity/Person.php @@ -1,6 +1,6 @@ true, 'id' => false, diff --git a/app/src/Model/Table/CoPeopleTable.php b/app/src/Model/Table/PeopleTable.php similarity index 93% rename from app/src/Model/Table/CoPeopleTable.php rename to app/src/Model/Table/PeopleTable.php index ca4eaa5a7..2af4b48b7 100644 --- a/app/src/Model/Table/CoPeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -1,6 +1,6 @@ setRequiresCO(true); $this->setAllowLookupPrimaryLink(['canvas']); +// XXX does some of this stuff really belong in the controller? + $this->setEditContains(['PrimaryName']); $this->setIndexContains(['PrimaryName']); $this->setAutoViewVars([ 'statuses' => [ 'type' => 'enum', 'class' => 'StatusEnum' + ], + 'types' => [ + 'type' => 'type', + 'where' => ['attribute' => 'Name.type'] ] ]); } @@ -106,7 +112,6 @@ public function validationDefault(Validator $validator): Validator { $validator->add( 'status', 'content', -// XXX if this works, backport to other tables [ 'rule' => [ 'inList', StatusEnum::getConstValues() ]] /* [ 'rule' => [ 'inList', [ TemplateableStatusEnum::Active, diff --git a/app/src/View/Helper/FieldHelper.php b/app/src/View/Helper/FieldHelper.php index 7224baa1c..242f0c4b5 100644 --- a/app/src/View/Helper/FieldHelper.php +++ b/app/src/View/Helper/FieldHelper.php @@ -195,7 +195,7 @@ protected function formNameDiv(string $fieldName, string $labelText=null) { if(preg_match('/^(.*?)_id$/', $fn, $f)) { // Map foriegn keys (foo_id) to the controller label - $label = __("registry.ct.".Inflector::pluralize($f[1]), [1]); + $label = __("registry.ct.".Inflector::camelize(Inflector::pluralize($f[1])), [1]); } else { // Just look up the key $label = __("registry.fd.".$fn); diff --git a/app/templates/CoPeople/columns.inc b/app/templates/People/columns.inc similarity index 84% rename from app/templates/CoPeople/columns.inc rename to app/templates/People/columns.inc index e45a6efa7..318c35aeb 100644 --- a/app/templates/CoPeople/columns.inc +++ b/app/templates/People/columns.inc @@ -1,6 +1,6 @@ [ 'type' => 'link', 'model' => 'primary_name', - 'field' => 'common_name' + 'field' => 'common_name', +// XXX see comments in the controller about sorting on given vs family + 'sortable' => 'PrimaryName.family' ], 'status' => [ 'type' => 'enum', - 'class' => 'StatusEnum' + 'class' => 'StatusEnum', + 'sortable' => true ] ]; diff --git a/app/templates/CoPeople/fields.inc b/app/templates/People/fields.inc similarity index 69% rename from app/templates/CoPeople/fields.inc rename to app/templates/People/fields.inc index 4f9c24219..bd6916b9a 100644 --- a/app/templates/CoPeople/fields.inc +++ b/app/templates/People/fields.inc @@ -1,6 +1,6 @@ Field->control('names.0.given'); print $this->Field->control('names.0.family'); +// XXX should cluster name fields together so it's obvious type relates to name +// XXX how do we set a default value here? ['default' => X], but what is X? it's +// an integer that varies according to the CO, we'd need a find where co=x and attribute=Name.type and name=official + print $this->Field->control('names.0.type_id', ['empty' => false]); + print $this->Field->control('status', ['empty' => false]); print $this->Field->control('date_of_birth'); -} elseif($vv_action == 'edit') { + + $hidden = [ + // The initial name must be primary + 'names.0.primary_name' => true + ]; +} elseif($vv_action == 'canvas') { // XXX This is a placeholder for canvas... maybe it should become a separate page // rather than overload fields.inc? + + print $vv_obj->primary_name->common_name; } \ No newline at end of file diff --git a/app/templates/element/menuMain.php b/app/templates/element/menuMain.php index bc70b166a..487358370 100644 --- a/app/templates/element/menuMain.php +++ b/app/templates/element/menuMain.php @@ -46,11 +46,11 @@ $menuItems = [ [ - 'permission' => 'co_people', - 'controller' => 'co_people', + 'permission' => 'people', + 'controller' => 'people', 'action' => 'index', 'icon' => 'person', - 'label' => __('registry.me.co.co_people') + 'label' => __('registry.me.co.people') ], [ 'permission' => 'configuration',