diff --git a/app/config/schema/schema.json b/app/config/schema/schema.json index e05ca815b..1eccbb93a 100644 --- a/app/config/schema/schema.json +++ b/app/config/schema/schema.json @@ -142,7 +142,6 @@ "case_sensitive": { "type": "boolean" }, "invalidates": { "type": "boolean" }, "null_equivalents": { "type": "boolean" }, - "required": { "type": "boolean" }, "search_distance": { "type": "integer" }, "search_exact": { "type": "boolean" }, "search_substr_from": { "type": "integer" }, @@ -183,7 +182,9 @@ "id": {}, "rule_id": { "type": "integer", "foreignkey": { "table": "rules", "column": "id" } }, "attribute_id": { "type": "integer", "foreignkey": { "table": "attributes", "column": "id" } }, - "search_type": { "type": "string", "size": 2 } + "crosscheck_attribute_id": { "type": "integer", "foreignkey": { "table": "attributes", "column": "id" } }, + "search_type": { "type": "string", "size": 2 }, + "required": { "type": "boolean" } }, "indexes": { "rule_attributes_i1": { @@ -192,6 +193,10 @@ "rule_attributes_i2": { "comment": "We don't really need this index but DBAL will create it anyway, with a random name", "columns": [ "attribute_id" ] + }, + "rule_attributes_i3": { + "comment": "We don't really need this index but DBAL will create it anyway, with a random name", + "columns": [ "crosscheck_attribute_id" ] } }, "changelog": false diff --git a/app/src/Controller/ApiUsersController.php b/app/src/Controller/ApiUsersController.php index 0554915f5..906565d81 100644 --- a/app/src/Controller/ApiUsersController.php +++ b/app/src/Controller/ApiUsersController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -32,7 +32,7 @@ use Cake\Routing\Router; class ApiUsersController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'ApiUsers.username' => 'asc' ] diff --git a/app/src/Controller/AttributeGroupsController.php b/app/src/Controller/AttributeGroupsController.php index ebc3ea5ab..c0ec1f0d8 100644 --- a/app/src/Controller/AttributeGroupsController.php +++ b/app/src/Controller/AttributeGroupsController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -30,7 +30,7 @@ namespace App\Controller; class AttributeGroupsController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'AttributeGroups.name' => 'asc' ] diff --git a/app/src/Controller/AttributeMappingsController.php b/app/src/Controller/AttributeMappingsController.php index 392f4ab14..808d7d968 100644 --- a/app/src/Controller/AttributeMappingsController.php +++ b/app/src/Controller/AttributeMappingsController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -30,7 +30,7 @@ namespace App\Controller; class AttributeMappingsController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'AttributeMappings.query' => 'asc', 'AttributeMappings.value' => 'asc' diff --git a/app/src/Controller/AttributeMapsController.php b/app/src/Controller/AttributeMapsController.php index b13bd9ddf..f8d6709cc 100644 --- a/app/src/Controller/AttributeMapsController.php +++ b/app/src/Controller/AttributeMapsController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -30,7 +30,7 @@ namespace App\Controller; class AttributeMapsController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'AttributeMaps.name' => 'asc' ] diff --git a/app/src/Controller/AttributesController.php b/app/src/Controller/AttributesController.php index 8352592a0..fda0d1cf6 100644 --- a/app/src/Controller/AttributesController.php +++ b/app/src/Controller/AttributesController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -30,7 +30,7 @@ namespace App\Controller; class AttributesController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'Attributes.name' => 'asc' ] diff --git a/app/src/Controller/MatchgridRecordsController.php b/app/src/Controller/MatchgridRecordsController.php index 7a5734cfe..023c2d9f1 100644 --- a/app/src/Controller/MatchgridRecordsController.php +++ b/app/src/Controller/MatchgridRecordsController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -38,7 +38,7 @@ use \App\Lib\Match\MatchService; class MatchgridRecordsController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'sor' => 'asc', 'sorid' => 'asc', diff --git a/app/src/Controller/MatchgridSettingsController.php b/app/src/Controller/MatchgridSettingsController.php index e8a9cc0da..c1fadc118 100644 --- a/app/src/Controller/MatchgridSettingsController.php +++ b/app/src/Controller/MatchgridSettingsController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -30,7 +30,7 @@ namespace App\Controller; class MatchgridSettingsController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'MatchgridSetting.matchgrid_id' => 'asc' ] diff --git a/app/src/Controller/MatchgridsController.php b/app/src/Controller/MatchgridsController.php index cf173f614..be9faadab 100644 --- a/app/src/Controller/MatchgridsController.php +++ b/app/src/Controller/MatchgridsController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -33,7 +33,7 @@ use \App\Lib\Enum\PermissionEnum; class MatchgridsController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'Matchgrids.table_name' => 'asc' ] diff --git a/app/src/Controller/PermissionsController.php b/app/src/Controller/PermissionsController.php index fe7dc21e1..1b0fc8938 100644 --- a/app/src/Controller/PermissionsController.php +++ b/app/src/Controller/PermissionsController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -30,10 +30,15 @@ namespace App\Controller; class PermissionsController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'Matchgrids.table_name' => 'asc', 'Permissions.name' => 'asc' + ], +// 'sortWhitelist' is renamed 'sortableFields' in Cake 4.1 +// 'sortableFields' => [ + 'sortWhitelist' => [ + 'Matchgrids.table_name' ] ]; diff --git a/app/src/Controller/RuleAttributesController.php b/app/src/Controller/RuleAttributesController.php new file mode 100644 index 000000000..cbe510e9f --- /dev/null +++ b/app/src/Controller/RuleAttributesController.php @@ -0,0 +1,69 @@ + [ + 'Attributes.name' => 'asc' + ], +// 'sortWhitelist' is renamed 'sortableFields' in Cake 4.1 +// 'sortableFields' => [ + 'sortWhitelist' => [ + 'Attributes.name' + ] + ]; + + /** + * Authorization for this Controller, called by Auth component + * - postcondition: $vv_permissions set with calculated permissions for this Controller + * + * @since COmanage Match v1.0.0 + * @param Array $user Array of user data + * @return Boolean True if authorized for the current action, false otherwise + */ + + public function isAuthorized(Array $user) { + $platformAdmin = $this->Authorization->isPlatformAdmin($user['username']); + + $mgAdmin = $this->Authorization->isMatchAdmin($user['username'], $this->cur_mg->id); + + $p = [ + 'add' => $platformAdmin || $mgAdmin, + 'delete' => $platformAdmin || $mgAdmin, + 'edit' => $platformAdmin || $mgAdmin, + 'index' => $platformAdmin || $mgAdmin, + 'view' => false + ]; + + $this->set('vv_permissions', $p); + return $p[$this->request->getParam('action')]; + } +} \ No newline at end of file diff --git a/app/src/Controller/RulesController.php b/app/src/Controller/RulesController.php index cbbaa172b..992be6f86 100644 --- a/app/src/Controller/RulesController.php +++ b/app/src/Controller/RulesController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -30,7 +30,7 @@ namespace App\Controller; class RulesController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'Rules.confidence_mode' => 'asc', 'Rules.ordr' => 'asc' diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index 364da8f6d..7448031dd 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -32,6 +32,9 @@ use InvalidArgumentException; class StandardController extends AppController { + // Pagination defaults should be set in each controller + public $pagination = []; + /** * Handle an add action for a Standard object. * @@ -223,8 +226,10 @@ public function generateRedirect() { public function index() { // $this->name = Models $modelsName = $this->name; + // $table = the actual table object + $table = $this->$modelsName; // $tableName = models - $tableName = $this->$modelsName->getTable(); + $tableName = $table->getTable(); $query = null; @@ -239,10 +244,10 @@ public function index() { if(!empty($link['linkattr']) && $modelsName != 'MatchgridRecords') { // If a link attribute is defined but no value is provided, then query // where the link attribute is NULL - $query = $this->$modelsName->find()->where([$link['linkattr'].' IS' => $this->request->getQuery($link['linkattr'])]); + $query = $table->find()->where([$link['linkattr'].' IS' => $this->request->getQuery($link['linkattr'])]); } else { try { - $query = $this->$modelsName->find(); + $query = $table->find(); } catch(\Cake\Database\Exception $e) { if($modelsName == 'MatchgridRecords' && $e->getCode() == 500) { @@ -257,7 +262,17 @@ public function index() { } } - $this->set($tableName, $this->Paginator->paginate($query, $this->paginate)); + // QueryModificationTrait + if(method_exists($table, "getIndexContains") + && $table->getIndexContains()) { + $query->contain($table->getIndexContains()); + } + + // The Cake documents describe $this->paginate (which worked in Cake 2), + // but it doesn't seem to work in Cake 3/4. So we just use $this->pagination + // ourselves here. + + $this->set($tableName, $this->Paginator->paginate($query, $this->pagination)); $this->set('vv_tablename', $tableName); $this->set('vv_modelname', $modelsName); @@ -333,9 +348,16 @@ protected function populateAutoViewVars(object $obj=null) { // XXX also need to check getData()? if($v) { - $query = $query->find($avv['find'], [$linkFilter => $v]); + $query = $query->where([$linkFilter => $v]); } } + } elseif($avv['find'] == 'filterMatchgrid') { + // In many/most cases, filterPrimaryLink is also filterMatchgrid, + // but for indirect models this will force the filter to be on + // the matchgrid instead of the primary link + + // For now we only support direct relations to matchgrid + $query->where(['matchgrid_id' => $this->cur_mg->id]); } else { // Use the specified finder, if configured $query = $query->find($avv['find']); diff --git a/app/src/Controller/SystemsOfRecordController.php b/app/src/Controller/SystemsOfRecordController.php index 9ccd0fc4a..c1023ab5a 100644 --- a/app/src/Controller/SystemsOfRecordController.php +++ b/app/src/Controller/SystemsOfRecordController.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -30,7 +30,7 @@ namespace App\Controller; class SystemsOfRecordController extends StandardController { - public $paginate = [ + public $pagination = [ 'order' => [ 'SystemsOfRecord.label' => 'asc' ] diff --git a/app/src/Lib/Match/MatchService.php b/app/src/Lib/Match/MatchService.php index e79af3661..d94ddc671 100644 --- a/app/src/Lib/Match/MatchService.php +++ b/app/src/Lib/Match/MatchService.php @@ -517,11 +517,17 @@ protected function search(string $mode, Log::write('debug', $sor . "/" . $sorid . " Searching with confidence mode " . $mode); foreach($this->mgConfig->$ruleObjs as $rule) { - $sql = "SELECT * - FROM " . $this->mgTable . " - WHERE referenceid IS NOT NULL"; // Don't match pending requests + // We generate SQL fragments for each rule attribute, then we OR together + // any SQL fragments for the same attribute (eg: if Crosscheck is in use, + // we want any one of them to succeed) and then AND together the SQL + // fragment across attributes (any attributes specified must match). - $vals = []; + // Each of 'sql' and 'vals' will hold an array of arrays, keyed on the + // attribute id + $attrSql = [ + 'sql' => [], + 'vals' => [] + ]; foreach($rule->rule_attributes as $ruleattr) { if($ruleattr->search_type == SearchTypeEnum::Skip) { @@ -532,29 +538,35 @@ protected function search(string $mode, $val = $attributes->getValueByAttribute($ruleattr->attribute); if(!$val) { - if($ruleattr->attribute->required) { - Log::write('debug', $sor . "/" . $sorid . " No value found for required attribute " . $ruleattr->attribute->name . " skipping rule " . $rule->name); + if($ruleattr->required) { + Log::write('debug', $sor . "/" . $sorid . " No value found for required attribute " . $ruleattr->attribute->name . ", skipping rule " . $rule->name); continue 2; } - Log::write('debug', $sor . "/" . $sorid . " No value found for attribute " . $ruleattr->attribute->name . " skipping"); + Log::write('debug', $sor . "/" . $sorid . " No value found for optional attribute " . $ruleattr->attribute->name . ", ignoring it"); continue; } - $andclause = ""; + // From here, we use the Crosscheck Attribute if it is specified instead + // of the original Attribute. In other words, Crosscheck allows a single + // attribute from the API message to map to multiple database columns. + + $attribute = $ruleattr->crosscheck_attribute ?: $ruleattr->attribute; + + $attrclause = ""; $colclause = ""; // The column name - $colclause = $ruleattr->attribute->name; + $colclause = $attribute->name; // If the attribute is case insensitive, we insert LOWER clauses - if(!$ruleattr->attribute->case_sensitive) { + if(!$attribute->case_sensitive) { $colclause = "LOWER(" . $colclause . ")"; $val = strtolower($val); } // If the attribute is alphanumeric only, we strip out non-alphanumeric characters - if($ruleattr->attribute->alphanumeric) { + if($attribute->alphanumeric) { $colclause = "REGEXP_REPLACE(" . $colclause . ", '[^A-Za-z0-9]', '', 'g')"; $val = preg_replace('/[^A-Za-z0-9]/', '', $val); } @@ -565,51 +577,53 @@ protected function search(string $mode, // how we handle all this switch($ruleattr->search_type) { case SearchTypeEnum::Distance: - $maxdistance = (int)($ruleattr->attribute->search_distance)+1; - $andclause = "LEVENSHTEIN_LESS_EQUAL(" - . $colclause - . ",?," - . $ruleattr->attribute->search_distance - . ") < " - . $maxdistance; + $maxdistance = (int)($attribute->search_distance)+1; + $attrclause = "LEVENSHTEIN_LESS_EQUAL(" + . $colclause + . ",?," + . $attribute->search_distance + . ") < " + . $maxdistance; break; case SearchTypeEnum::Exact: - $andclause = $colclause . "=?"; + $attrclause = $colclause . "=?"; break; case SearchTypeEnum::Mapping: - $qclause = (!$ruleattr->attribute->case_sensitive ? "LOWER(query)" : "query"); - $andclause = "(" . $colclause . " + $qclause = (!$attribute->case_sensitive ? "LOWER(query)" : "query"); + $attrclause = "(" . $colclause . " IN (SELECT value FROM attribute_mappings - WHERE attribute_map_id=" . $ruleattr->attribute->attribute_map_id ." + WHERE attribute_map_id=" . $attribute->attribute_map_id ." AND " . $qclause . "=?) OR " . $colclause . "=?)"; // We need two copies of $val in the param list - $vals[] = (!$ruleattr->attribute->case_sensitive ? strtolower($val) : $val); + $attrSql['vals'][$ruleattr->attribute->id][] = (!$attribute->case_sensitive ? strtolower($val) : $val); break; case SearchTypeEnum::Substring: - $andclause = "SUBSTRING(" - . $colclause - . " FROM " - . $ruleattr->attribute->search_substr_from - . " FOR " - . $ruleattr->attribute->search_substr_for - . ") = SUBSTRING(? FROM " - . $ruleattr->attribute->search_substr_from - . " FOR " - . $ruleattr->attribute->search_substr_for - . ")"; + $attrclause = "SUBSTRING(" + . $colclause + . " FROM " + . $attribute->search_substr_from + . " FOR " + . $attribute->search_substr_for + . ") = SUBSTRING(? FROM " + . $attribute->search_substr_from + . " FOR " + . $attribute->search_substr_for + . ")"; break; default: throw new LogicException(__('match.er.search_type', [$ruleattr->search_type])); break; } - $sql .= " AND " . $andclause; - $vals[] = $val; + // Note here we revert to using the original Attribute ID, since if + // multiple configurations are specified for it we want to OR them together + $attrSql['sql'][$ruleattr->attribute->id][] = $attrclause; + $attrSql['vals'][$ruleattr->attribute->id][] = $val; } - if(count($vals) == 0) { + if(empty($attrSql['vals'])) { // We need at least one attribute to search on. If we didn't process // any in the request, complain. @@ -617,6 +631,28 @@ protected function search(string $mode, continue; } + // Start building the actual SQL + + $sql = "SELECT * + FROM " . $this->mgTable . " + WHERE referenceid IS NOT NULL"; // Don't match pending requests + + $vals = []; + + foreach(array_keys($attrSql['sql']) as $attrId) { + if(count($attrSql['sql'][$attrId]) > 1) { + // We OR together all of the clauses + + $sql .= " AND (" . implode(" OR ", $attrSql['sql'][$attrId]) . ")"; + } else { + // We simply append the clause + + $sql .= " AND " . $attrSql['sql'][$attrId][0]; + } + + $vals = array_merge($vals, $attrSql['vals'][$attrId]); + } + LOG::write('debug', $sor . "/" . $sorid . " SQL: " . $sql); $stmt = $this->dbc->Prepare($sql); @@ -720,7 +756,10 @@ public function setConfig(int $matchgridId) { 'CanonicalRules' => [ // We already pull attributes above, but this makes it // easier to access them in search() - 'RuleAttributes' => ['Attributes' => 'AttributeGroups'], + 'RuleAttributes' => [ + 'Attributes' => 'AttributeGroups', + 'CrosscheckAttributes' => 'AttributeGroups' + ], 'sort' => ['CanonicalRules.ordr' => 'ASC'] ], 'PotentialRules' => [ diff --git a/app/src/Lib/Traits/QueryModificationTrait.php b/app/src/Lib/Traits/QueryModificationTrait.php new file mode 100644 index 000000000..4f1bc7017 --- /dev/null +++ b/app/src/Lib/Traits/QueryModificationTrait.php @@ -0,0 +1,57 @@ +indexContains; + } + + /** + * Set containable models for index actions. + * + * @since COmanage Match v1.0.0 + * @param array $contains Containable models + */ + + public function setIndexContains(array $contains) { + $this->indexContains = $contains; + } +} diff --git a/app/src/Locale/en_US/default.po b/app/src/Locale/en_US/default.po index c6efc1771..c928a6072 100644 --- a/app/src/Locale/en_US/default.po +++ b/app/src/Locale/en_US/default.po @@ -135,6 +135,9 @@ msgstr "{0,plural,=1{System of Record} other{Systems of Record}}" msgid "match.ct.Rules" msgstr "{0,plural,=1{Rule} other{Rules}}" +msgid "match.ct.RuleAttributes" +msgstr "{0,plural,=1{Rule Attribute} other{Rule Attributes}}" + ### Actions msgid "match.ac.PendingRequests" msgstr "{0,plural,=1{Pending Request} other{Pending Requests}}" @@ -339,6 +342,9 @@ msgstr "Value must be a valid SQL identifier, as it will be used to construct th msgid "match.fd.MatchgridRecords.referenceid.desc" msgstr "Manually assigning a Reference Identifier is not recommended, except when forcing a match to an existing Matchgrid entry. For more information, see the documentation." +msgid "match.fd.RuleAttributes.crosscheck_attribute_id" +msgstr "Crosscheck Attribute" + msgid "match.fd.case_sensitive" msgstr "Case Sensitive" @@ -429,8 +435,8 @@ msgstr "Search Substring From" msgid "match.fd.search_substr_for" msgstr "Search Substring For" -msgid "match.fd.search_types" -msgstr "Search Types" +msgid "match.fd.search_type" +msgstr "Search Type" msgid "match.fd.select" msgstr "(Please select a value)" diff --git a/app/src/Model/Table/AttributesTable.php b/app/src/Model/Table/AttributesTable.php index ad9035024..534962bd0 100644 --- a/app/src/Model/Table/AttributesTable.php +++ b/app/src/Model/Table/AttributesTable.php @@ -52,9 +52,11 @@ public function initialize(array $config) { $this->belongsTo('AttributeGroups'); $this->belongsTo('AttributeMaps'); $this->belongsTo('Matchgrids'); - //$this->belongsToMany('Rules', ['through' => 'RuleAttributes']); $this->hasMany('RuleAttributes') ->setDependent(true); + $this->hasMany('CrosscheckAttributes', ['className' => 'RuleAttributes']) + ->setForeignKey('crosscheck_attribute_id') + ->setProperty('crosscheck_attribute'); $this->setDisplayField('name'); @@ -156,13 +158,6 @@ public function validationDefault(Validator $validator) { [ 'rule' => [ 'boolean' ] ] ); $validator->notEmpty('null_equivalents'); - - $validator->add( - 'required', - 'toggle', - [ 'rule' => [ 'boolean' ] ] - ); - $validator->notEmpty('required'); $validator->add( 'search_distance', diff --git a/app/src/Model/Table/RuleAttributesTable.php b/app/src/Model/Table/RuleAttributesTable.php index 68919eacb..26e9c6c60 100644 --- a/app/src/Model/Table/RuleAttributesTable.php +++ b/app/src/Model/Table/RuleAttributesTable.php @@ -35,6 +35,11 @@ use \App\Lib\Enum\SearchTypeEnum; class RuleAttributesTable extends Table { + use \App\Lib\Traits\AutoViewVarsTrait; + use \App\Lib\Traits\MatchgridLinkTrait; + use \App\Lib\Traits\PrimaryLinkTrait; + use \App\Lib\Traits\QueryModificationTrait; + /** * Perform Cake Model initialization. * @@ -47,7 +52,36 @@ public function initialize(array $config) { // Define associations $this->belongsTo('Attributes'); + + $this->belongsTo('CrosscheckAttributes', ['className' => 'Attributes']) + ->setForeignKey('crosscheck_attribute_id') + ->setProperty('crosscheck_attribute'); + $this->belongsTo('Rules'); + + $this->setDisplayField('id'); + + $this->setPrimaryLink('rule_id'); + $this->setRequiresMatchgrid(true); + + $this->setIndexContains(['Attributes']); + + $this->setAutoViewVars([ + 'attributes' => [ + 'type' => 'select', + 'model' => 'Attributes', + 'find' => 'filterMatchgrid' + ], + 'crosscheckAttributes' => [ + 'type' => 'select', + 'model' => 'Attributes', + 'find' => 'filterMatchgrid' + ], + 'searchTypes' => [ + 'type' => 'enum', + 'class' => 'SearchTypeEnum' + ] + ]); } /** @@ -73,6 +107,13 @@ public function validationDefault(Validator $validator) { ); $validator->notEmpty('attribute_id'); + $validator->add( + 'crosscheck_attribute_id', + 'content', + [ 'rule' => 'isInteger' ] + ); + $validator->allowEmpty('crosscheck_attribute_id'); + $validator->add( 'search_type', 'content', @@ -86,6 +127,13 @@ public function validationDefault(Validator $validator) { ); $validator->notEmpty('search_type'); + $validator->add( + 'required', + 'toggle', + [ 'rule' => [ 'boolean' ] ] + ); + $validator->notEmpty('required'); + return $validator; } } \ No newline at end of file diff --git a/app/src/Model/Table/RulesTable.php b/app/src/Model/Table/RulesTable.php index af212a246..e2f37a75c 100644 --- a/app/src/Model/Table/RulesTable.php +++ b/app/src/Model/Table/RulesTable.php @@ -74,10 +74,6 @@ public function initialize(array $config) { 'confidenceModes' => [ 'type' => 'enum', 'class' => 'ConfidenceModeEnum' - ], - 'searchTypes' => [ - 'type' => 'enum', - 'class' => 'SearchTypeEnum' ] ]); } diff --git a/app/src/Template/Attributes/fields.inc b/app/src/Template/Attributes/fields.inc index 69485c9e3..f10424672 100644 --- a/app/src/Template/Attributes/fields.inc +++ b/app/src/Template/Attributes/fields.inc @@ -38,7 +38,6 @@ if($action == 'add' || $action == 'edit') { // CO-1762 // print $this->Field->control('invalidates', [], false); print $this->Field->control('null_equivalents', [], false); - print $this->Field->control('required', [], false); print $this->Field->control('search_distance', [], false); print $this->Field->control('search_exact', [], false); diff --git a/app/src/Template/RuleAttributes/columns.inc b/app/src/Template/RuleAttributes/columns.inc new file mode 100644 index 000000000..e5f0de1f5 --- /dev/null +++ b/app/src/Template/RuleAttributes/columns.inc @@ -0,0 +1,44 @@ + [ + 'type' => 'link', + 'model' => 'attribute', + 'field' => 'name', + 'sortable' => 'Attributes.name' + ], + 'crosscheck_attribute_id' => [ + 'type' => 'fk', + 'label' => __('match.fd.RuleAttributes.crosscheck_attribute_id') + ], + 'search_type' => [ + 'type' => 'enum', + 'class' => 'SearchTypeEnum', + 'sortable' => true + ] +]; \ No newline at end of file diff --git a/app/src/Template/RuleAttributes/fields.inc b/app/src/Template/RuleAttributes/fields.inc new file mode 100644 index 000000000..923238725 --- /dev/null +++ b/app/src/Template/RuleAttributes/fields.inc @@ -0,0 +1,34 @@ +Field->control('attribute_id', ['empty' => true]); + print $this->Field->control('crosscheck_attribute_id', ['empty' => true], false, __('match.fd.RuleAttributes.crosscheck_attribute_id')); + print $this->Field->control('search_type', ['empty' => true]); + print $this->Field->control('required'); +} \ No newline at end of file diff --git a/app/src/Template/Rules/columns.inc b/app/src/Template/Rules/columns.inc index 617a13cd7..f530d3d0c 100644 --- a/app/src/Template/Rules/columns.inc +++ b/app/src/Template/Rules/columns.inc @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -36,4 +36,12 @@ $indexColumns = [ 'ordr' => [ 'type' => 'echo' ], +]; + +$indexActions = [ + [ + 'controller' => 'rule_attributes', + 'action' => 'index', + 'class' => 'linkbutton' + ] ]; \ No newline at end of file diff --git a/app/src/Template/Rules/fields.inc b/app/src/Template/Rules/fields.inc index 545c84227..a21030a87 100644 --- a/app/src/Template/Rules/fields.inc +++ b/app/src/Template/Rules/fields.inc @@ -19,77 +19,16 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ -use \App\Lib\Enum\ConfidenceModeEnum; -use \App\Lib\Enum\SearchTypeEnum; -?> - -Field->control('name'); print $this->Field->control('description', [], false); print $this->Field->control('confidence_mode', ['empty' => true]); print $this->Field->control('ordr'); - -// XXX only list attribute that make sense for canonical vs potential -// eg if "Search Exact" is not ticked (and/or maybe subtring), attribute should not be available for -// canonical rules -// XXX we need $attributes passed directly from the controller (where matchgrid_id=cur_id) - - print "