Skip to Content.
Sympa Menu

comanage-dev - [comanage-dev] r334 - in registry/trunk/app: Config Config/Schema Controller Lib Model View View/CoPeople View/CoPersonRoles View/HistoryRecords View/HistoryRecords/json View/HistoryRecords/xml View/Layouts View/OrgIdentities

Subject: COmanage Developers List

List archive

[comanage-dev] r334 - in registry/trunk/app: Config Config/Schema Controller Lib Model View View/CoPeople View/CoPersonRoles View/HistoryRecords View/HistoryRecords/json View/HistoryRecords/xml View/Layouts View/OrgIdentities


Chronological Thread 
  • From:
  • To:
  • Subject: [comanage-dev] r334 - in registry/trunk/app: Config Config/Schema Controller Lib Model View View/CoPeople View/CoPersonRoles View/HistoryRecords View/HistoryRecords/json View/HistoryRecords/xml View/Layouts View/OrgIdentities
  • Date: Sat, 11 Aug 2012 22:03:06 -0400

Author: benno
Date: 2012-08-11 22:03:06 -0400 (Sat, 11 Aug 2012)
New Revision: 334

Added:
registry/trunk/app/Controller/HistoryRecordsController.php
registry/trunk/app/Model/HistoryRecord.php
registry/trunk/app/View/HistoryRecords/
registry/trunk/app/View/HistoryRecords/index.ctp
registry/trunk/app/View/HistoryRecords/json/
registry/trunk/app/View/HistoryRecords/json/add.ctp
registry/trunk/app/View/HistoryRecords/json/index.ctp
registry/trunk/app/View/HistoryRecords/json/view.ctp
registry/trunk/app/View/HistoryRecords/xml/
registry/trunk/app/View/HistoryRecords/xml/add.ctp
registry/trunk/app/View/HistoryRecords/xml/index.ctp
registry/trunk/app/View/HistoryRecords/xml/view.ctp
Modified:
registry/trunk/app/Config/Schema/schema.xml
registry/trunk/app/Config/routes.php
registry/trunk/app/Controller/AppController.php
registry/trunk/app/Controller/CoOrgIdentityLinksController.php
registry/trunk/app/Controller/CoPeopleController.php
registry/trunk/app/Controller/CoPersonRolesController.php
registry/trunk/app/Controller/OrgIdentitiesController.php
registry/trunk/app/Controller/StandardController.php
registry/trunk/app/Lib/enum.php
registry/trunk/app/Lib/lang.php
registry/trunk/app/Model/CoEnrollmentAttribute.php
registry/trunk/app/Model/CoIdentifierAssignment.php
registry/trunk/app/Model/CoPerson.php
registry/trunk/app/Model/CoPersonRole.php
registry/trunk/app/Model/CoPetition.php
registry/trunk/app/Model/OrgIdentity.php
registry/trunk/app/View/CoPeople/fields.inc
registry/trunk/app/View/CoPersonRoles/fields.inc
registry/trunk/app/View/CoPersonRoles/index.ctp
registry/trunk/app/View/Layouts/default.ctp
registry/trunk/app/View/OrgIdentities/fields.inc
registry/trunk/app/View/OrgIdentities/index.ctp
Log:
Implement transaction level history records framework (CO-96)

Modified: registry/trunk/app/Config/Schema/schema.xml
===================================================================
--- registry/trunk/app/Config/Schema/schema.xml 2012-08-12 00:48:24 UTC (rev
333)
+++ registry/trunk/app/Config/Schema/schema.xml 2012-08-12 02:03:06 UTC (rev
334)
@@ -672,6 +672,8 @@
<field name="ethnicity" type="C" size="2" />
<field name="race" type="C" size="5" />
<field name="disability" type="C" size="4" />
+ <field name="created" type="T" />
+ <field name="modified" type="T" />

<index name="co_nsf_demographics_i1">
<col>co_person_id</col>
@@ -698,6 +700,8 @@
<field name="maximum" type="I" />
<field name="collision_resolution" type="C" size="64" />
<field name="exclusions" type="C" size="8" />
+ <field name="created" type="T" />
+ <field name="modified" type="T" />

<index name="co_identifier_assignments_i1">
<col>co_id</col>
@@ -715,6 +719,8 @@
</field>
<field name="affix" type="C" size="256" />
<field name="last" type="I" />
+ <field name="created" type="T" />
+ <field name="modified" type="T" />

<index name="co_sequential_identifier_assignments_i1">
<col>co_identifier_assignment_id</col>
@@ -722,4 +728,37 @@
<unique />
</index>
</table>
+
+ <table name="history_records">
+ <field name="id" type="I">
+ <key />
+ <autoincrement />
+ </field>
+ <field name="co_person_id" type="I">
+ <constraint>REFERENCES cm_co_people(id)</constraint>
+ </field>
+ <field name="co_person_role_id" type="I">
+ <constraint>REFERENCES cm_co_person_roles(id)</constraint>
+ </field>
+ <field name="org_identity_id" type="I">
+ <constraint>REFERENCES cm_org_identities(id)</constraint>
+ </field>
+ <field name="actor_co_person_id" type="I">
+ <constraint>REFERENCES cm_co_people(id)</constraint>
+ </field>
+ <field name="action" type="C" size="4" />
+ <field name="comment" type="C" size="256" />
+ <field name="created" type="T" />
+ <field name="modified" type="T" />
+
+ <index name="co_history_records_i1">
+ <col>co_person_id</col>
+ </index>
+ <index name="co_history_records_i2">
+ <col>co_person_role_id</col>
+ </index>
+ <index name="co_history_records_i3">
+ <col>org_identity_id</col>
+ </index>
+ </table>
</schema>

Modified: registry/trunk/app/Config/routes.php
===================================================================
--- registry/trunk/app/Config/routes.php 2012-08-12 00:48:24 UTC (rev
333)
+++ registry/trunk/app/Config/routes.php 2012-08-12 02:03:06 UTC (rev
334)
@@ -74,6 +74,7 @@
'cos',
'cous',
'email_addresses',
+ 'history_records',
'identifiers',
'org_identities',
'organizations',

Modified: registry/trunk/app/Controller/AppController.php
===================================================================
--- registry/trunk/app/Controller/AppController.php 2012-08-12 00:48:24
UTC (rev 333)
+++ registry/trunk/app/Controller/AppController.php 2012-08-12 02:03:06
UTC (rev 334)
@@ -8,12 +8,12 @@
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright 2005-2011, Cake Software Foundation, Inc.
(http://cakefoundation.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc.
(http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright 2005-2011, Cake Software Foundation, Inc.
(http://cakefoundation.org)
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc.
(http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package registry
* @since COmanage Registry v0.1, CakePHP(tm) v 0.2.9
@@ -138,6 +138,8 @@

$this->Auth->authenticate = array('Basic');

+// debug(AuthComponent::password($_SERVER['PHP_AUTH_PW']));
+
if(!$this->Auth->login()) {
$this->restResultHeader(401, "Unauthorized");
// We force an exit here to prevent any views from rendering, but
also
@@ -171,7 +173,6 @@
*/

function beforeRender() {
-
// Determine what is shown for menus
// Called before each render in case permissions change
if($this->restful != true
@@ -319,6 +320,117 @@
}

/**
+ * Compare two arrays and generate a string describing what changed,
suitable for
+ * including in a history record.
+ *
+ * @since COmanage Registry v0.7
+ * @param Array New data, in typical Cake format
+ * @param Array Old data, in typical Cake format
+ * @param Array Models to examine within new and old data
+ * @return String String describing changes
+ */
+
+ public function changesToString($newdata, $olddata, $models) {
+ // We assume $newdata and $olddate are intended to have the same
structure, however
+ // we require $models to be specified since different controllers may
pull different
+ // levels of containable or recursion data, and so we don't know how
many associated
+ // models will appear in $newdata and/or $olddata.
+
+ $changes = array();
+
+ foreach($models as $model) {
+ if($model == 'ExtendedAttribute') {
+ // Handle extended attributes differently, as usual
+
+ if(isset($this->cur_co['CoExtendedAttribute'])) {
+ // First, calculate the real model name
+ $eaModel = "Co" . $this->cur_co['Co']['id'] .
"PersonExtendedAttribute";
+
+ foreach($this->cur_co['CoExtendedAttribute'] as $extAttr) {
+ $oldval = null;
+ $newval = null;
+
+ // Grab the name of this attribute and lowercase it to match the
data model
+ $eaName = strtolower($extAttr['name']);
+ $eaDisplayName = $extAttr['display_name'];
+
+ // Try to find the attribute in the data
+
+ if(isset($newdata[$eaModel][$eaName]) &&
($newdata[$eaModel][$eaName] != "")) {
+ $newval = $newdata[$eaModel][$eaName];
+ }
+
+ if(isset($olddata[$eaModel][$eaName]) &&
($olddata[$eaModel][$eaName] != "")) {
+ $oldval = $olddata[$eaModel][$eaName];
+ }
+
+ if(isset($newval) && !isset($oldval)) {
+ $changes[] = $eaDisplayName . ": " . _txt('fd.null') . " > " .
$newval;
+ } elseif(!isset($newval) && isset($oldval)) {
+ $changes[] = $eaDisplayName . ": " . $oldval . " > " .
_txt('fd.null');
+ } elseif(isset($newval) && isset($oldval) && ($newval !=
$oldval)) {
+ $changes[] = $eaDisplayName . ": " . $oldval . " > " . $newval;
+ }
+ }
+ }
+ } else {
+ // Generate the union of keys among old and new
+
+ $attrs = array_unique(array_merge(array_keys($newdata[$model]),
array_keys($olddata[$model])));
+
+ foreach($attrs as $attr) {
+ // Skip some "housekeeping" keys
+ if($attr == 'id' || preg_match('/.*_id$/', $attr) || $attr ==
'created' || $attr == 'modified') {
+ continue;
+ }
+
+ // Skip nested arrays -- for now, we only deal with top level data
+ if((isset($newdata[$model][$attr]) &&
is_array($newdata[$model][$attr]))
+ || (isset($olddata[$model][$attr]) &&
is_array($olddata[$model][$attr]))) {
+ continue;
+ }
+
+ $oldval = (isset($olddata[$model][$attr]) &&
$olddata[$model][$attr] != "") ? $olddata[$model][$attr] : null;
+ $newval = (isset($newdata[$model][$attr]) &&
$newdata[$model][$attr] != "") ? $newdata[$model][$attr] : null;
+
+ // See if we're working with a type, and if so use the localized
string instead
+ // (if we can find it)
+
+ if(!isset($this->$model)) {
+ $this->loadModel($model);
+ }
+
+ if(isset($this->$model) &&
isset($this->$model->cm_enum_txt[$attr])) {
+ $oldval = _txt($this->$model->cm_enum_txt[$attr], null, $oldval)
. " (" . $oldval . ")";
+ $newval = _txt($this->$model->cm_enum_txt[$attr], null, $newval)
. " (" . $newval . ")";
+ }
+
+ // Find the localization of the field
+
+ $ftxt = "(?)";
+
+ if($model == 'Name' && $attr != 'type') {
+ // Treat name specially
+ $ftxt = _txt('fd.name.'.$attr);
+ } else {
+ $ftxt = _txt('fd.'.$attr);
+ }
+
+ if(isset($newval) && !isset($oldval)) {
+ $changes[] = $ftxt . ": " . _txt('fd.null') . " > " . $newval;
+ } elseif(!isset($newval) && isset($oldval)) {
+ $changes[] = $ftxt . ": " . $oldval . " > " . _txt('fd.null');
+ } elseif(isset($newval) && isset($oldval) && ($newval != $oldval))
{
+ $changes[] = $ftxt . ": " . $oldval . " > " . $newval;
+ }
+ }
+ }
+ }
+
+ return implode(';', $changes);
+ }
+
+ /**
* For Models that accept a CO Person ID, a CO Person Role ID, or an Org
* Identity ID, verify that a valid ID was specified. Also, generate an
* array suitable for redirecting back to the controller.

Modified: registry/trunk/app/Controller/CoOrgIdentityLinksController.php
===================================================================
--- registry/trunk/app/Controller/CoOrgIdentityLinksController.php
2012-08-12 00:48:24 UTC (rev 333)
+++ registry/trunk/app/Controller/CoOrgIdentityLinksController.php
2012-08-12 02:03:06 UTC (rev 334)
@@ -95,6 +95,44 @@
}

/**
+ * Generate history records for a transaction. This method is intended to
be
+ * overridden by model-specific controllers, and will be called from
within a
+ * try{} block so that HistoryRecord->record() may be called without
worrying
+ * about catching exceptions.
+ *
+ * @since COmanage Registry v0.7
+ * @param String Controller action causing the change
+ * @param Array Data provided as part of the action (for add/edit)
+ * @param Array Previous data (for delete/edit)
+ * @return boolean Whether the function completed successfully (which does
not necessarily imply history was recorded)
+ */
+
+ public function generateHistory($action, $newdata, $olddata) {
+ if($this->restful) {
+ switch($action) {
+ case 'add':
+ // We try to record an Org Identity ID, but this will only exist
for non-REST operations
+
$this->CoOrgIdentityLink->CoPerson->HistoryRecord->record($newdata['CoOrgIdentityLink']['co_person_id'],
+ null,
+
$newdata['CoOrgIdentityLink']['org_identity_id'],
+
$this->Session->read('Auth.User.co_person_id'),
+
ActionEnum::CoPersonOrgIdLinked);
+ break;
+ case 'delete':
+ // As of v0.7, unlinking preceeds deletion of an identity/person
that will therefore
+ // cause this history record to be deleted. As such, we don't
record anything. This is
+ // subject to being revisited in a future release.
+ break;
+ case 'edit':
+ // As of v0.7, we don't really have a use case for editing a link.
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Authorization for this Controller, called by Auth component
* - precondition: Session.Auth holds data used for authz decisions
* - postcondition: $permissions set with calculated permissions

Modified: registry/trunk/app/Controller/CoPeopleController.php
===================================================================
--- registry/trunk/app/Controller/CoPeopleController.php 2012-08-12
00:48:24 UTC (rev 333)
+++ registry/trunk/app/Controller/CoPeopleController.php 2012-08-12
02:03:06 UTC (rev 334)
@@ -269,6 +269,57 @@
}

/**
+ * Generate history records for a transaction. This method is intended to
be
+ * overridden by model-specific controllers, and will be called from
within a
+ * try{} block so that HistoryRecord->record() may be called without
worrying
+ * about catching exceptions.
+ *
+ * @since COmanage Registry v0.7
+ * @param String Controller action causing the change
+ * @param Array Data provided as part of the action (for add/edit)
+ * @param Array Previous data (for delete/edit)
+ * @return boolean Whether the function completed successfully (which does
not necessarily imply history was recorded)
+ */
+
+ public function generateHistory($action, $newdata, $olddata) {
+ switch($action) {
+ case 'add':
+ // We try to record an Org Identity ID, but this will only exist for
non-REST operations
+ $this->CoPerson->HistoryRecord->record($this->CoPerson->id,
+ null,
+
(isset($newdata['CoOrgIdentityLink'][0]['org_identity_id'])
+ ?
$newdata['CoOrgIdentityLink'][0]['org_identity_id'] : null),
+
$this->Session->read('Auth.User.co_person_id'),
+
ActionEnum::CoPersonAddedManual);
+
+ if(!$this->restful) {
+ // Add a record indicating the link took place
+ $this->CoPerson->HistoryRecord->record($this->CoPerson->id,
+ null,
+
$newdata['CoOrgIdentityLink'][0]['org_identity_id'],
+
$this->Session->read('Auth.User.co_person_id'),
+
ActionEnum::CoPersonOrgIdLinked);
+ }
+ break;
+ case 'delete':
+ // We don't handle delete since the CO person and its associated
history
+ // is about to be deleted
+ break;
+ case 'edit':
+ $this->CoPerson->HistoryRecord->record($this->CoPerson->id,
+ null,
+
$newdata['CoOrgIdentityLink'][0]['org_identity_id'],
+
$this->Session->read('Auth.User.co_person_id'),
+
ActionEnum::CoPersonEditedManual,
+ _txt('en.action', null,
ActionEnum::CoPersonEditedManual) . ": " .
+
$this->changesToString($newdata, $olddata, array('CoPerson', 'Name')));
+ break;
+ }
+
+ return true;
+ }
+
+ /**
* Obtain all CO People, or perform a match
*
* @since COmanage Registry v0.5

Modified: registry/trunk/app/Controller/CoPersonRolesController.php
===================================================================
--- registry/trunk/app/Controller/CoPersonRolesController.php 2012-08-12
00:48:24 UTC (rev 333)
+++ registry/trunk/app/Controller/CoPersonRolesController.php 2012-08-12
02:03:06 UTC (rev 334)
@@ -223,6 +223,46 @@
}

/**
+ * Generate history records for a transaction. This method is intended to
be
+ * overridden by model-specific controllers, and will be called from
within a
+ * try{} block so that HistoryRecord->record() may be called without
worrying
+ * about catching exceptions.
+ *
+ * @since COmanage Registry v0.7
+ * @param String Controller action causing the change
+ * @param Array Data provided as part of the action (for add/edit)
+ * @param Array Previous data (for delete/edit)
+ * @return boolean Whether the function completed successfully (which does
not necessarily imply history was recorded)
+ */
+
+ public function generateHistory($action, $newdata, $olddata) {
+ switch($action) {
+ case 'add':
+
$this->CoPersonRole->HistoryRecord->record($newdata['CoPersonRole']['co_person_id'],
+ $this->CoPersonRole->id,
+ null,
+
$this->Session->read('Auth.User.co_person_id'),
+
ActionEnum::CoPersonRoleAddedManual);
+ break;
+ case 'delete':
+ // We don't handle delete since the CO Person Role and its
associated history
+ // is about to be deleted
+ break;
+ case 'edit':
+
$this->CoPersonRole->HistoryRecord->record($newdata['CoPersonRole']['co_person_id'],
+ $this->CoPersonRole->id,
+ null,
+
$this->Session->read('Auth.User.co_person_id'),
+
ActionEnum::CoPersonRoleEditedManual,
+ _txt('en.action', null,
ActionEnum::CoPersonRoleEditedManual) . ": " .
+
$this->changesToString($newdata, $olddata, array('CoPersonRole',
'ExtendedAttribute')));
+ break;
+ }
+
+ return true;
+ }
+
+ /**
* Authorization for this Controller, called by Auth component
* - precondition: Session.Auth holds data used for authz decisions
* - postcondition: $permissions set with calculated permissions

Modified: registry/trunk/app/Controller/OrgIdentitiesController.php
===================================================================
--- registry/trunk/app/Controller/OrgIdentitiesController.php 2012-08-12
00:48:24 UTC (rev 333)
+++ registry/trunk/app/Controller/OrgIdentitiesController.php 2012-08-12
02:03:06 UTC (rev 334)
@@ -38,7 +38,7 @@
'Name.given' => 'asc'
)
);
-
+
function addvialdap()
{
// Add a new Organizational Person by querying LDAP.
@@ -217,6 +217,46 @@
}

/**
+ * Generate history records for a transaction. This method is intended to
be
+ * overridden by model-specific controllers, and will be called from
within a
+ * try{} block so that HistoryRecord->record() may be called without
worrying
+ * about catching exceptions.
+ *
+ * @since COmanage Registry v0.7
+ * @param String Controller action causing the change
+ * @param Array Data provided as part of the action (for add/edit)
+ * @param Array Previous data (for delete/edit)
+ * @return boolean Whether the function completed successfully (which does
not necessarily imply history was recorded)
+ */
+
+ public function generateHistory($action, $newdata, $olddata) {
+ switch($action) {
+ case 'add':
+ $this->OrgIdentity->HistoryRecord->record(null,
+ null,
+ $this->OrgIdentity->id,
+
$this->Session->read('Auth.User.co_person_id'),
+
ActionEnum::OrgIdAddedManual);
+ break;
+ case 'delete':
+ // We don't handle delete since the org identity and its associated
history
+ // is about to be deleted
+ break;
+ case 'edit':
+ $this->OrgIdentity->HistoryRecord->record(null,
+ null,
+ $this->OrgIdentity->id,
+
$this->Session->read('Auth.User.co_person_id'),
+
ActionEnum::OrgIdEditedManual,
+ _txt('en.action', null,
ActionEnum::OrgIdEditedManual) . ": " .
+
$this->changesToString($newdata, $olddata, array('OrgIdentity', 'Name')));
+ break;
+ }
+
+ return true;
+ }
+
+ /**
* Authorization for this Controller, called by Auth component
* - precondition: Session.Auth holds data used for authz decisions
* - postcondition: $permissions set with calculated permissions

Modified: registry/trunk/app/Controller/StandardController.php
===================================================================
--- registry/trunk/app/Controller/StandardController.php 2012-08-12
00:48:24 UTC (rev 333)
+++ registry/trunk/app/Controller/StandardController.php 2012-08-12
02:03:06 UTC (rev 334)
@@ -78,7 +78,8 @@
// Finally, try to save

if($model->saveAll($data)) {
- if(!$this->checkWriteFollowups($data)) {
+ if(!$this->recordHistory('add', $data)
+ || !$this->checkWriteFollowups($data)) {
if(!$this->restful) {
$this->performRedirect();
}
@@ -222,10 +223,12 @@

if($model->delete($id))
{
- if($this->restful)
- $this->restResultHeader(200, "Deleted");
- else
- $this->Session->setFlash(_txt('er.deleted-a',
array(Sanitize::html($name))), '', array(), 'success');
+ if($this->recordHistory('delete', null, $op)) {
+ if($this->restful)
+ $this->restResultHeader(200, "Deleted");
+ else
+ $this->Session->setFlash(_txt('er.deleted-a',
array(Sanitize::html($name))), '', array(), 'success');
+ }
}
else
{
@@ -359,8 +362,8 @@

if($model->saveAll($data))
{
- if(!$this->checkWriteFollowups($data, $curdata))
- {
+ if(!$this->recordHistory('edit', $data, $curdata)
+ || !$this->checkWriteFollowups($data, $curdata)) {
if(!$this->restful)
$this->performRedirect();

@@ -422,6 +425,23 @@
}

/**
+ * Generate history records for a transaction. This method is intended to
be
+ * overridden by model-specific controllers, and will be called from
within a
+ * try{} block so that HistoryRecord->record() may be called without
worrying
+ * about catching exceptions.
+ *
+ * @since COmanage Registry v0.7
+ * @param String Controller action causing the change
+ * @param Array Data provided as part of the action (for add/edit)
+ * @param Array Previous data (for delete/edit)
+ * @return boolean Whether the function completed successfully (which does
not necessarily imply history was recorded)
+ */
+
+ public function generateHistory($action, $newdata, $olddata) {
+ return true;
+ }
+
+ /**
* Obtain all Standard Objects (of the model's type).
* - postcondition: $<object>s set on success (REST or HTML), using
pagination (HTML only)
* - postcondition: HTTP status returned (REST)
@@ -591,21 +611,26 @@

if($this->requires_person)
{
- if(!empty($this->params['named']['copersonroleid']))
+ if(!empty($this->params['named']['copersonid']))
{
- $q = $req . ".co_person_role_id ='";
+ $q = $req . ".co_person_id = ";
+ $this->set($modelpl, $this->paginate($req, array($q =>
$this->params['named']['copersonid'])));
+ }
+ elseif(!empty($this->params['named']['copersonroleid']))
+ {
+ $q = $req . ".co_person_role_id = ";
$this->set($modelpl, $this->paginate($req, array($q =>
$this->params['named']['copersonroleid'])));
}
elseif(!empty($this->params['named']['orgidentityid']))
{
- $q = $req . ".org_identity_id ='";
+ $q = $req . ".org_identity_id = ";
$this->set($modelpl, $this->paginate($req, array($q =>
$this->params['named']['orgidentityid'])));
}
else
{
// Although requires_person is true, the UI sort of permits
// retrieval of all items of a given type
-
+
$this->set($modelpl, $this->paginate($req));
}
}
@@ -657,8 +682,39 @@
else
$this->redirect(array('action' => 'index'));
}
-
+
/**
+ * Record history associated with an action. Note that, for now, failure to
+ * record history DOES NOT roll back the original request. This may change
+ * in a future release.
+ *
+ * @since COmanage Registry v0.7
+ * @param String Controller action causing the change
+ * @param Array Data provided as part of the action (for add/edit)
+ * @param Array Previous data (for delete/edit)
+ * @return boolean Whether the function completed successfully (which does
not necessarily imply history was recorded)
+ */
+
+ function recordHistory($action, $newdata, $olddata=null) {
+ // This function handles the framework of recording history.
+
+ try {
+ $this->generateHistory($action, $newdata, $olddata);
+ }
+ catch(Exception $e) {
+ if($this->restful) {
+ $this->restResultHeader(500, "Other Error: " . $e->getMessage());
+ } else {
+ $this->Session->setFlash($e->getMessage(), '', array(), 'info');
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Regenerate a form after validation/save fails.
* This method is intended to be overridden.
* - postcondition: Redirect generated

Modified: registry/trunk/app/Lib/enum.php
===================================================================
--- registry/trunk/app/Lib/enum.php 2012-08-12 00:48:24 UTC (rev 333)
+++ registry/trunk/app/Lib/enum.php 2012-08-12 02:03:06 UTC (rev 334)
@@ -21,7 +21,26 @@
* @license Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0)
* @version $Id$
*/
-
+
+class ActionEnum
+{
+ // Codes beginning with 'X' (eg: 'XABC') are reserved for local use
+ const CoPersonAddedManual = 'ACPM';
+ const CoPersonAddedPetition = 'ACPP';
+ const CoPersonEditedManual = 'ECPM';
+ const CoPersonEditedPetition = 'ECPP';
+ const CoPersonRoleAddedManual = 'ACRM';
+ const CoPersonRoleAddedPetition = 'ACRP';
+ const CoPersonRoleDeletedManual = 'DCRM';
+ const CoPersonRoleEditedManual = 'ECRM';
+ const CoPersonRoleEditedPetition = 'ECRP';
+ const CoPersonOrgIdLinked = 'LOCP';
+ const IdentifierAutoAssigned = 'AIDA';
+ const OrgIdAddedManual = 'AOIM';
+ const OrgIdAddedPetition = 'AOIP';
+ const OrgIdEditedManual = 'EOIM';
+}
+
class AdministratorEnum
{
const NoAdmin = 'N';

Modified: registry/trunk/app/Lib/lang.php
===================================================================
--- registry/trunk/app/Lib/lang.php 2012-08-12 00:48:24 UTC (rev 333)
+++ registry/trunk/app/Lib/lang.php 2012-08-12 02:03:06 UTC (rev 334)
@@ -87,6 +87,8 @@
'ct.cous.pl' => 'COUs',
'ct.email_addresses.1' => 'Email Address',
'ct.email_addresses.pl' => 'Email Addresses',
+ 'ct.history_records.1' => 'History Record',
+ 'ct.history_records.pl' => 'History Records',
'ct.identifiers.1' => 'Identifier',
'ct.identifiers.pl' => 'Identifiers',
'ct.org_identities.1' => 'Organizational Identity',
@@ -103,6 +105,24 @@
'em.invite.footer' => 'This email was sent using %1$s.',

// Enumerations, corresponding to enum.php
+ // Default history comments
+ 'en.action' => array(
+ ActionEnum::CoPersonAddedManual => 'CO Person Created (Manual)',
+ ActionEnum::CoPersonAddedPetition => 'CO Person Role Created
(Petition)',
+ ActionEnum::CoPersonEditedManual => 'CO Person Edited',
+ ActionEnum::CoPersonEditedPetition => 'CO Person Edited (Petition)',
+ ActionEnum::CoPersonRoleAddedManual => 'CO Person Role Created
(Manual)',
+ ActionEnum::CoPersonRoleAddedPetition => 'CO Person Role Created
(Petition)',
+ ActionEnum::CoPersonRoleDeletedManual => 'CO Person Role Deleted
(Manual)',
+ ActionEnum::CoPersonRoleEditedManual => 'CO Person Role Edited',
+ ActionEnum::CoPersonRoleEditedPetition => 'CO Person Role Edited
(Petition)',
+ ActionEnum::CoPersonOrgIdLinked => 'CO Person and Org Identity
Linked',
+ ActionEnum::IdentifierAutoAssigned => 'Identifier Auto Assigned',
+ ActionEnum::OrgIdAddedManual => 'Org Identity Created
(Manual)',
+ ActionEnum::OrgIdAddedPetition => 'Org Identity Created
(Petition)',
+ ActionEnum::OrgIdEditedManual => 'Org Identity Edited'
+ ),
+
'en.admin' => array(AdministratorEnum::NoAdmin => 'None',
AdministratorEnum::CoAdmin => 'CO Admin',
AdministratorEnum::CoOrCouAdmin => 'CO or COU
Admin'),
@@ -271,7 +291,7 @@

'et.default' => 'There are no Extended Types currently defined for
this attribute. The default types are currently in use. When you create a new
Extended Type, the default types will automatically be added to this list.',

- // Fields
+ // Fields. Field names should match data model names to facilitate various
auto-rendering.
'fd.action' => 'Action',
'fd.actions' => 'Actions',
'fd.actor' => 'Actor',
@@ -397,9 +417,10 @@
'fd.name.family' => 'Family Name',
'fd.name.suffix' => 'Suffix',
'fd.no' => 'No',
+ 'fd.null' => 'Null',
'fd.o' => 'Organization',
'fd.open' => 'Open',
- 'fd.orgid' => 'Organization ID',
+ 'fd.organization_id' => 'Organization ID',
'fd.ou' => 'Department',
'fd.parent' => 'Parent COU',
'fd.perms' => 'Permissions',
@@ -419,10 +440,10 @@
'fd.type' => 'Type',
'fd.type.warn' => 'After an extended attribute is created, its type may
not be changed',
'fd.untitled' => 'Untitled',
- 'fd.valid.f' => 'Valid From',
- 'fd.valid.f.desc' => '(leave blank for immediate validity)',
- 'fd.valid.u' => 'Valid Through',
- 'fd.valid.u.desc' => '(leave blank for indefinite validity)',
+ 'fd.valid_from' => 'Valid From',
+ 'fd.valid_from.desc' => '(leave blank for immediate validity)',
+ 'fd.valid_through' => 'Valid Through',
+ 'fd.valid_through.desc' => '(leave blank for indefinite validity)',
'fd.yes' => 'Yes',

// Menu
@@ -456,6 +477,7 @@
'op.find.inv' => 'Find a Person to Invite to %1$s',
'op.gr.memadd' => 'Add Person %1$s to Group',
'op.grm.add' => 'Add Person to %1$s Group %2$s',
+ 'op.history' => 'View History',
'op.id.auto' => 'Autogenerate Identifiers',
'op.id.auto.confirm' => 'Are you sure you wish to autogenerate
identifiers?',
'op.inv' => 'Invite',

Modified: registry/trunk/app/Model/CoEnrollmentAttribute.php
===================================================================
--- registry/trunk/app/Model/CoEnrollmentAttribute.php 2012-08-12 00:48:24
UTC (rev 333)
+++ registry/trunk/app/Model/CoEnrollmentAttribute.php 2012-08-12 02:03:06
UTC (rev 334)
@@ -96,8 +96,8 @@
$ret['r:title'] = _txt('fd.title') . " (" . _txt('ct.co_person_roles.1')
. ")";
$ret['r:o'] = _txt('fd.o') . " (" . _txt('ct.co_person_roles.1') . ")";
$ret['r:ou'] = _txt('fd.ou') . " (" . _txt('ct.co_person_roles.1') . ")";
- $ret['r:valid_from'] = _txt('fd.valid.f') . " (" .
_txt('ct.co_person_roles.1') . ")";
- $ret['r:valid_through'] = _txt('fd.valid.u') . " (" .
_txt('ct.co_person_roles.1') . ")";
+ $ret['r:valid_from'] = _txt('fd.valid_from') . " (" .
_txt('ct.co_person_roles.1') . ")";
+ $ret['r:valid_through'] = _txt('fd.valid_through') . " (" .
_txt('ct.co_person_roles.1') . ")";

// (2) Multi valued CO Person attributes (code=p)


Modified: registry/trunk/app/Model/CoIdentifierAssignment.php
===================================================================
--- registry/trunk/app/Model/CoIdentifierAssignment.php 2012-08-12 00:48:24
UTC (rev 333)
+++ registry/trunk/app/Model/CoIdentifierAssignment.php 2012-08-12 02:03:06
UTC (rev 334)
@@ -202,6 +202,21 @@

if($this->Co->CoPerson->Identifier->save($identifierData)) {
$ret = $this->Co->CoPerson->Identifier->id;
+
+ // Create a history record
+ try {
+
$this->Co->CoPerson->HistoryRecord->record($coPerson['CoPerson']['id'],
+ null,
+ null,
+ null,
+
ActionEnum::IdentifierAutoAssigned,
+ _txt('en.action',
null, ActionEnum::IdentifierAutoAssigned) . ': '
+ . $candidate . ' ('
. $identifierData['Identifier']['type'] . ')');
+ }
+ catch(Exception $e) {
+ $dbc->rollback();
+ throw new RuntimeException(_txt('er.db.save'));
+ }
} else {
$dbc->rollback();
throw new RuntimeException(_txt('er.db.save'));

Modified: registry/trunk/app/Model/CoPerson.php
===================================================================
--- registry/trunk/app/Model/CoPerson.php 2012-08-12 00:48:24 UTC (rev
333)
+++ registry/trunk/app/Model/CoPerson.php 2012-08-12 02:03:06 UTC (rev
334)
@@ -72,11 +72,15 @@
),
// A person can be an actor on a petition and generate history
"CoPetitionHistoryRecord" => array(
- 'dependent' => true,
'foreignKey' => 'actor_co_person_id'
),
// A person can have one or more email address
"EmailAddress" => array('dependent' => true),
+ // We allow dependent=true for co_person_id but not for
actor_co_person_id (see CO-404).
+ "HistoryRecord" => array(
+ 'dependent' => true,
+ 'foreignKey' => 'co_person_id'
+ ),
// A person can have many identifiers within a CO
"Identifier" => array('dependent' => true)
);

Modified: registry/trunk/app/Model/CoPersonRole.php
===================================================================
--- registry/trunk/app/Model/CoPersonRole.php 2012-08-12 00:48:24 UTC (rev
333)
+++ registry/trunk/app/Model/CoPersonRole.php 2012-08-12 02:03:06 UTC (rev
334)
@@ -51,6 +51,8 @@
'dependent' => true,
'foreignKey' => 'enrollee_co_person_role_id'
),
+ // It's probably not right to delete history records, but generally CO
person roles shouldn't be deleted
+ "HistoryRecord" => array('dependent' => true),
// A person can have one or more telephone numbers
"TelephoneNumber" => array('dependent' => true)
);
@@ -58,8 +60,9 @@
// Default display field for cake generated views
public $displayField = "CoPersonRole.id";

+// XXX CO-296 Toss default order?
// Default ordering for find operations
- public $order = array("CoPersonRole.id");
+ // public $order = array("CoPersonRole.id");

// Validation rules for table elements
public $validate = array(

Modified: registry/trunk/app/Model/CoPetition.php
===================================================================
--- registry/trunk/app/Model/CoPetition.php 2012-08-12 00:48:24 UTC (rev
333)
+++ registry/trunk/app/Model/CoPetition.php 2012-08-12 02:03:06 UTC (rev
334)
@@ -277,6 +277,19 @@

if($this->EnrolleeOrgIdentity->saveAll($orgData)) {
$orgIdentityID = $this->EnrolleeOrgIdentity->id;
+
+ // Create a history record
+ try {
+ $this->EnrolleeOrgIdentity->HistoryRecord->record(null,
+ null,
+ $orgIdentityID,
+ $petitionerId,
+
ActionEnum::OrgIdAddedPetition);
+ }
+ catch(Exception $e) {
+ $dbc->rollback();
+ throw new RuntimeException($e->getMessage());
+ }
} else {
// We don't fail immediately on error because we want to run
validate on all
// the data we save in the various saveAll() calls so the
appropriate fields
@@ -305,6 +318,19 @@

if($this->EnrolleeCoPerson->saveAll($coData)) {
$coPersonID = $this->EnrolleeCoPerson->id;
+
+ // Create a history record
+ try {
+ $this->EnrolleeCoPerson->HistoryRecord->record($coPersonID,
+ null,
+ $orgIdentityID,
+ $petitionerId,
+
ActionEnum::CoPersonAddedPetition);
+ }
+ catch(Exception $e) {
+ $dbc->rollback();
+ throw new RuntimeException($e->getMessage());
+ }
} else {
// We don't fail immediately on error because we want to run validate
on all
// the data we save in the various saveAll() calls so the appropriate
fields
@@ -335,6 +361,19 @@

if($this->EnrolleeCoPersonRole->saveAll($coRoleData)) {
$coPersonRoleID = $this->EnrolleeCoPersonRole->id;
+
+ // Create a history record
+ try {
+ $this->EnrolleeCoPersonRole->HistoryRecord->record($coPersonID,
+ $coPersonRoleID,
+ $orgIdentityID,
+ $petitionerId,
+
ActionEnum::CoPersonRoleAddedPetition);
+ }
+ catch(Exception $e) {
+ $dbc->rollback();
+ throw new RuntimeException($e->getMessage());
+ }
} else {
// We need to fold any extended attribute validation errors into the
CO Person Role
// validation errors in order for FormHandler to be able to see them.
@@ -372,7 +411,20 @@
$coOrgLink['CoOrgIdentityLink']['org_identity_id'] = $orgIdentityID;
$coOrgLink['CoOrgIdentityLink']['co_person_id'] = $coPersonID;

- if(!$this->EnrolleeCoPerson->CoOrgIdentityLink->save($coOrgLink)) {
+ if($this->EnrolleeCoPerson->CoOrgIdentityLink->save($coOrgLink)) {
+ // Create a history record
+ try {
+ $this->EnrolleeCoPerson->HistoryRecord->record($coPersonID,
+ $coPersonRoleID,
+ $orgIdentityID,
+ $petitionerId,
+
ActionEnum::CoPersonOrgIdLinked);
+ }
+ catch(Exception $e) {
+ $dbc->rollback();
+ throw new RuntimeException($e->getMessage());
+ }
+ } else {
$dbc->rollback();
throw new RuntimeException(_txt('er.db.save'));
}
@@ -664,7 +716,7 @@
$newCoPersonStatus = $newStatus;
}

- // XXX This is temporary for CO-321 since there isn't currently a way
for an approved people
+ // XXX This is temporary for CO-321 since there isn't currently a way
for an approved person
// to become active. This should be dropped when a more
workflow-oriented mechanism is implemented.
if($newStatus == StatusEnum::Approved) {
$newCoPersonStatus = StatusEnum::Active;
@@ -720,7 +772,23 @@

if($coPersonRoleID) {
$this->EnrolleeCoPersonRole->id = $coPersonRoleID;
+ $curCoPersonRoleStatus =
$this->EnrolleeCoPersonRole->field('status');
$this->EnrolleeCoPersonRole->saveField('status',
$newCoPersonStatus);
+
+ // Create a history record
+ try {
+
$this->EnrolleeCoPersonRole->HistoryRecord->record($this->field('enrollee_co_person_id'),
+
$coPersonRoleID,
+ null,
+
$actorCoPersonID,
+
ActionEnum::CoPersonRoleEditedPetition,
+
_txt('en.action', null, ActionEnum::CoPersonRoleEditedPetition) . ": "
+ .
_txt('en.status', null, $curCoPersonRoleStatus) . " > "
+ .
_txt('en.status', null, $newCoPersonStatus));
+ }
+ catch(Exception $e) {
+ $fail = true;
+ }
} else {
$fail = true;
}
@@ -739,6 +807,26 @@
if(isset($curCoPersonStatus)
&& ($curCoPersonStatus == StatusEnum::PendingApproval)) {
$this->EnrolleeCoPerson->saveField('status', $newCoPersonStatus);
+
+ // Create a history record
+ try {
+ $newdata = array();
+ $olddata = array();
+ $newdata['CoPerson']['status'] = $newCoPersonStatus;
+ $olddata['CoPerson']['status'] = $curCoPersonStatus;
+
+ $this->EnrolleeCoPerson->HistoryRecord->record($coPersonID,
+ null,
+ null,
+
$actorCoPersonID,
+
ActionEnum::CoPersonEditedPetition,
+
_txt('en.action', null, ActionEnum::CoPersonEditedPetition) . ": "
+ .
_txt('en.status', null, $curCoPersonStatus) . " > "
+ .
_txt('en.status', null, $newCoPersonStatus));
+ }
+ catch(Exception $e) {
+ $fail = true;
+ }
}
// else not a fail
} else {

Modified: registry/trunk/app/Model/OrgIdentity.php
===================================================================
--- registry/trunk/app/Model/OrgIdentity.php 2012-08-12 00:48:24 UTC (rev
333)
+++ registry/trunk/app/Model/OrgIdentity.php 2012-08-12 02:03:06 UTC (rev
334)
@@ -52,6 +52,8 @@
),
// A person can have one or more email address
"EmailAddress" => array('dependent' => true),
+ // It's probably not right to delete history records, but generally org
identities shouldn't be deleted
+ "HistoryRecord" => array('dependent' => true),
// A person can have many identifiers within an organization
"Identifier" => array('dependent' => true),
// A person can have one or more telephone numbers

Modified: registry/trunk/app/View/CoPeople/fields.inc
===================================================================
--- registry/trunk/app/View/CoPeople/fields.inc 2012-08-12 00:48:24 UTC (rev
333)
+++ registry/trunk/app/View/CoPeople/fields.inc 2012-08-12 02:03:06 UTC (rev
334)
@@ -74,25 +74,31 @@
echo $this->Form->hidden('CoOrgIdentityLink.0.org_identity_id',
array('default' =>
$co_people[0]['CoOrgIdentityLink'][0]['org_identity_id'])). "\n";
// Default status is 'Pending'
echo $this->Form->hidden('status', array('default' => 'P')). "\n";
-
- echo '
- <br />
- <br />
- ';
}
else
{
echo $this->Html->link(_txt('op.back'),
array('controller' => 'co_people', 'action' =>
'index', 'co' => $cur_co['Co']['id']),
- array('class' => 'backbutton')) . '
- <br />
- <br />
- ';
+ array('class' => 'backbutton'));
}

+ if($this->action != "add" && $this->action != "invite") {
+ print $this->Html->link(
+ _txt('op.history'),
+ array(
+ 'controller' => 'history_records',
+ 'action' => 'index',
+ 'copersonid' => $co_people[0]['CoPerson']['id']
+ ),
+ array('class' => 'historybutton')
+ );
+ }
+
// Line number, for rendering
$l = 1;
?>
+<br />
+<br />
<script type="text/javascript">
<!-- JS specific to these fields -->

@@ -394,8 +400,8 @@
<?php endif; // compare ?>
<th><?php echo _txt('fd.title'); ?></th>
<th><?php echo _txt('fd.affiliation'); ?></th>
- <th><?php echo _txt('fd.valid.f'); ?></th>
- <th><?php echo _txt('fd.valid.u'); ?></th>
+ <th><?php echo _txt('fd.valid_from'); ?></th>
+ <th><?php echo _txt('fd.valid_through'); ?></th>
<?php if($this->action != "compare"): ?>
<th><?php echo _txt('fd.status'); ?></th>
<th><?php echo _txt('fd.actions'); ?></th>

Modified: registry/trunk/app/View/CoPersonRoles/fields.inc
===================================================================
--- registry/trunk/app/View/CoPersonRoles/fields.inc 2012-08-12 00:48:24
UTC (rev 333)
+++ registry/trunk/app/View/CoPersonRoles/fields.inc 2012-08-12 02:03:06
UTC (rev 334)
@@ -197,7 +197,7 @@
</tr>
<tr class="line<?php echo ($l % 2); $l++; ?>">
<td>
- <?php echo _txt('fd.valid.f'); if($e && !$es) echo " " .
_txt('fd.valid.f.desc'); ?>
+ <?php echo _txt('fd.valid_from'); if($e && !$es) echo " " .
_txt('fd.valid_from.desc'); ?>
</td>
<td>
<?php echo (($e && !$es)
@@ -207,7 +207,7 @@
</tr>
<tr class="line<?php echo ($l % 2); $l++; ?>">
<td>
- <?php echo _txt('fd.valid.u'); if($e && !$es) echo " " .
_txt('fd.valid.u.desc'); ?>
+ <?php echo _txt('fd.valid_through'); if($e && !$es) echo " " .
_txt('fd.valid_through.desc'); ?>
</td>
<td>
<?php echo (($e && !$es)

Modified: registry/trunk/app/View/CoPersonRoles/index.ctp
===================================================================
--- registry/trunk/app/View/CoPersonRoles/index.ctp 2012-08-12 00:48:24
UTC (rev 333)
+++ registry/trunk/app/View/CoPersonRoles/index.ctp 2012-08-12 02:03:06
UTC (rev 334)
@@ -47,8 +47,8 @@
<th><?php echo $this->Paginator->sort('Cou.ou', _txt('fd.cou'));
?></th>
<th><?php echo $this->Paginator->sort('title', _txt('fd.title'));
?></th>
<th><?php echo $this->Paginator->sort('affiliation',
_txt('fd.affiliation')); ?></th>
- <th><?php echo $this->Paginator->sort('valid_from',
_txt('fd.valid.f')); ?></th>
- <th><?php echo $this->Paginator->sort('valid_through',
_txt('fd.valid.u')); ?></th>
+ <th><?php echo $this->Paginator->sort('valid_from',
_txt('fd.valid_from')); ?></th>
+ <th><?php echo $this->Paginator->sort('valid_through',
_txt('fd.valid_through')); ?></th>
<th><?php echo $this->Paginator->sort('status', _txt('fd.status'));
?></th>
<th><?php echo _txt('fd.actions'); ?></th>
</tr>


Property changes on: registry/trunk/app/View/HistoryRecords/json/add.ctp
___________________________________________________________________
Added: svn:special
+ *


Property changes on: registry/trunk/app/View/HistoryRecords/json/index.ctp
___________________________________________________________________
Added: svn:special
+ *


Property changes on: registry/trunk/app/View/HistoryRecords/json/view.ctp
___________________________________________________________________
Added: svn:special
+ *


Property changes on: registry/trunk/app/View/HistoryRecords/xml/add.ctp
___________________________________________________________________
Added: svn:special
+ *


Property changes on: registry/trunk/app/View/HistoryRecords/xml/index.ctp
___________________________________________________________________
Added: svn:special
+ *


Property changes on: registry/trunk/app/View/HistoryRecords/xml/view.ctp
___________________________________________________________________
Added: svn:special
+ *

Modified: registry/trunk/app/View/Layouts/default.ctp
===================================================================
--- registry/trunk/app/View/Layouts/default.ctp 2012-08-12 00:48:24 UTC (rev
333)
+++ registry/trunk/app/View/Layouts/default.ctp 2012-08-12 02:03:06 UTC (rev
334)
@@ -184,6 +184,12 @@
}
});

+ $(".historybutton").button({
+ icons: {
+ primary: 'ui-icon-note'
+ }
+ });
+
$(".invitebutton").button({
icons: {
primary: 'ui-icon-mail-closed'

Modified: registry/trunk/app/View/OrgIdentities/fields.inc
===================================================================
--- registry/trunk/app/View/OrgIdentities/fields.inc 2012-08-12 00:48:24
UTC (rev 333)
+++ registry/trunk/app/View/OrgIdentities/fields.inc 2012-08-12 02:03:06
UTC (rev 334)
@@ -58,6 +58,18 @@
echo $this->Form->hidden('OrgIdentity.co_id',
array('default' => $cur_co['Co']['id'])).
"\n";
}
+
+ if($this->action != "add") {
+ print $this->Html->link(
+ _txt('op.history'),
+ array(
+ 'controller' => 'history_records',
+ 'action' => 'index',
+ 'orgidentityid' => $org_identities[0]['OrgIdentity']['id']
+ ),
+ array('class' => 'historybutton')
+ );
+ }
?>
<br />
<br />

Modified: registry/trunk/app/View/OrgIdentities/index.ctp
===================================================================
--- registry/trunk/app/View/OrgIdentities/index.ctp 2012-08-12 00:48:24
UTC (rev 333)
+++ registry/trunk/app/View/OrgIdentities/index.ctp 2012-08-12 02:03:06
UTC (rev 334)
@@ -47,7 +47,7 @@
<th><?php echo $this->Paginator->sort('ou', _txt('fd.ou')); ?></th>
<th><?php echo $this->Paginator->sort('title', _txt('fd.title'));
?></th>
<th><?php echo $this->Paginator->sort('affiliation',
_txt('fd.affiliation')); ?></th>
- <th><?php echo $this->Paginator->sort('organization_id',
_txt('fd.orgid')); ?></th>
+ <th><?php echo $this->Paginator->sort('organization_id',
_txt('fd.organization_id')); ?></th>
<th><?php echo _txt('fd.actions'); ?></th>
</tr>
</thead>



  • [comanage-dev] r334 - in registry/trunk/app: Config Config/Schema Controller Lib Model View View/CoPeople View/CoPersonRoles View/HistoryRecords View/HistoryRecords/json View/HistoryRecords/xml View/Layouts View/OrgIdentities, svnlog, 08/11/2012

Archive powered by MHonArc 2.6.16.

Top of Page