diff --git a/db_objects/abstract_note.class.php b/db_objects/abstract_note.class.php index c3dac209..bbe3d784 100644 --- a/db_objects/abstract_note.class.php +++ b/db_objects/abstract_note.class.php @@ -208,7 +208,7 @@ public function canEditOriginal() { function delete() { if (!$this->canBeDeleted()) { - trigger_error("This note can not be deleted"); + trigger_error("This note can not be deleted", E_USER_WARNING); return FALSE; } if (!parent::delete()) return FALSE; @@ -303,7 +303,7 @@ public static function getNotifications($minutes) COUNT(DISTINCT nn.id) as new_notes, GROUP_CONCAT(nn.id) as new_note_ids FROM person p - JOIN abstract_note nn ON nn.assignee = p.id + JOIN abstract_note nn ON nn.assignee = p.id AND nn.status = "pending" AND nn.action_date <= DATE(NOW()) AND (( @@ -320,4 +320,18 @@ public static function getNotifications($minutes) GROUP BY p.id'; return $GLOBALS['db']->queryAll($SQL); } + + /** + * Clean up any orphaned records that are not references by a person or family note + * @return boolean + */ + public static function cleanupInstances() + { + $SQL = 'DELETE FROM abstract_note WHERE id NOT IN ( + SELECT id FROM person_note + UNION + SELECT id from family_note + )'; + return $GLOBALS['db']->exec($SQL); + } } diff --git a/db_objects/attendance_record.class.php b/db_objects/attendance_record.class.php new file mode 100644 index 00000000..e8123796 --- /dev/null +++ b/db_objects/attendance_record.class.php @@ -0,0 +1,26 @@ + '`_person` (`id`) ON DELETE CASCADE'); + } +} +?> diff --git a/db_objects/attendance_record_set.class.php b/db_objects/attendance_record_set.class.php index aaa54563..01531e55 100644 --- a/db_objects/attendance_record_set.class.php +++ b/db_objects/attendance_record_set.class.php @@ -70,25 +70,6 @@ function create() { } - - function getInitSQL($table_name=NULL) - { - return " - CREATE TABLE `attendance_record` ( - `date` date NOT NULL default '0000-00-00', - `personid` int(11) NOT NULL default '0', - `groupid` int(11) NOT NULL default '0', - `present` tinyint(1) unsigned NOT NULL default '0', - PRIMARY KEY (`date`,`personid`,`groupid`) - ) ENGINE=InnoDB ; - "; - } - - public function getForeignKeys() - { - return Array(); - } - function load($date, $cohort, $age_brackets, $statuses) { $this->date = $date; @@ -346,7 +327,7 @@ function printHeadcountField() $headcountValue = Headcount::fetch('person_group', $this->date, $this->groupid); } ?> - + queryAll($sql); + $res = $db->queryAll($sql); foreach ($res as $row) { if (NULL !== $row[$groupingField]) { diff --git a/db_objects/custom_field.class.php b/db_objects/custom_field.class.php index d4da82d5..c7ac3570 100644 --- a/db_objects/custom_field.class.php +++ b/db_objects/custom_field.class.php @@ -502,6 +502,7 @@ public function printWidget($value, $extraParams=Array(), $prefix='') if (($this->getValue('type') == 'select') && strlen($value) && !empty($this->values['params']['allow_other'])) { if (!isset($widgetParams['options'][$value])) { $otherValue = $value; + if (0 === strpos($otherValue, '0 ')) $otherValue = substr($otherValue, 2); $value = 'other'; } } @@ -637,12 +638,13 @@ public function parseValue($val) /** * Get SQL expression to retrieve a value suitable for use by formatValue() above. - * @param string $tableAlias Alias of the custom_field_value table in the SQL statement + * @param string $valueTableAlias Alias of the custom_field_value table in the SQL statement + * @param string $fieldTableAlias Alias of the custom_field table in the SQL statement * @return string SQL */ - public static function getRawValueSQLExpr($tableAlias) + public static function getRawValueSQLExpr($valueTableAlias, $fieldTableAlias) { - return 'TRIM(CONCAT(COALESCE('.$tableAlias.'.value_optionid, CONCAT('.$tableAlias.'.value_date, " "), ""), COALESCE('.$tableAlias.'.value_text, "")))'; + return 'TRIM(CONCAT(COALESCE('.$valueTableAlias.'.value_optionid, CONCAT('.$valueTableAlias.'.value_date, " "), ""), COALESCE(CONCAT(IF('.$fieldTableAlias.'.type="select", "0 ", ""), '.$valueTableAlias.'.value_text, ""))))'; } /** diff --git a/db_objects/family.class.php b/db_objects/family.class.php index a9c19605..8e58ac3f 100644 --- a/db_objects/family.class.php +++ b/db_objects/family.class.php @@ -127,7 +127,7 @@ function getInitSQL($table_name=NULL) "CREATE TABLE family_photo ( familyid INT NOT NULL, photodata MEDIUMBLOB NOT NULL, - CONSTRAINT `famliyphotofamilyid` FOREIGN KEY (`familyid`) REFERENCES `family` (`id`), + CONSTRAINT `famliyphotofamilyid` FOREIGN KEY (`familyid`) REFERENCES `family` (`id`) ON DELETE CASCADE, PRIMARY KEY (familyid) ) ENGINE=InnoDB; " @@ -158,7 +158,7 @@ function printForm($prefix='', $fields=NULL) function processForm($prefix='', $fields=NULL) { $res = parent::processForm($prefix, $fields); - $this->_photo_data = Photo_Handler::getUploadedPhotoData('photo'); + $this->_photo_data = Photo_Handler::getUploadedPhotoData('photo', Photo_Handler::CROP_NONE); return $res; } @@ -482,6 +482,13 @@ private function savePhoto() { } } + private function clearPhoto() + { + $db =& $GLOBALS['db']; + $SQL = 'DELETE FROM family_photo WHERE familyid = '.(int)$this->id; + return $db->query($SQL); + } + /* Find a family that looks like a duplicate of this one - if it has the same family name and a member with the same name */ public function findSimilarFamilies() @@ -527,7 +534,7 @@ public static function getFamilyDataByMemberIDs($member_ids) ) allmembers ON allmembers.familyid = f.id LEFT JOIN ( select f.id as familyid, GROUP_CONCAT(p.first_name ORDER BY ab.rank ASC, p.gender DESC SEPARATOR ", ") as names - FROM person p + FROM person p JOIN family f on p.familyid = f.id JOIN age_bracket ab ON ab.id = p.age_bracketid WHERE ab.is_adult and p.status <> "archived" @@ -563,29 +570,48 @@ public static function printSingleFinder($name, $currentval=NULL) acquireLock()) return FALSE; foreach ($this->fields as $fieldname => $params) { switch ($fieldname) { case 'family_name': - $this->setValue($fieldname, '(Removed)'); + $this->setValue($fieldname, '['._('Removed').']'); break; case 'status_last_changed': case 'creator': case 'created': + case 'state': // leave these intact break; case 'status': $this->setValue($fieldname, 'archived'); break; + case 'history': + $this->setValue($fieldname, Array()); + break; default: $this->setValue($fieldname, ''); } } - if (!$this->save()) return FALSE; + $this->clearPhoto(); + if (!$this->save(FALSE)) return FALSE; + + $notes = $GLOBALS['system']->getDBObjectData('family_note', Array('familyid' => $this->id)); + foreach ($notes as $noteid => $data) { + $n = new Family_Note($noteid); + $n->delete(); + } + $this->releaseLock(); return TRUE; } + + public function delete() + { + parent::delete(); + Abstract_Note::cleanupInstances(); + return TRUE; + } } diff --git a/db_objects/family_note.class.php b/db_objects/family_note.class.php index 6456a932..d4c51c9d 100644 --- a/db_objects/family_note.class.php +++ b/db_objects/family_note.class.php @@ -22,7 +22,9 @@ function getInitSQL($table_name=NULL) CREATE TABLE `family_note` ( `familyid` int(11) NOT NULL default '0', `id` int(11) NOT NULL default '0', - PRIMARY KEY (`familyid`,`id`) + PRIMARY KEY (`familyid`,`id`), + CONSTRAINT `fn_familyid` FOREIGN KEY (familyid) REFERENCES family(id) ON DELETE CASCADE, + CONSTRAINT fn_id FOREIGN KEY (id) REFERENCES abstract_note(id) ON DELETE CASCADE ) ENGINE=InnoDB; "; } diff --git a/db_objects/person.class.php b/db_objects/person.class.php index ef6dadcf..cbbf6c16 100644 --- a/db_objects/person.class.php +++ b/db_objects/person.class.php @@ -218,8 +218,9 @@ public function load($id) { parent::load($id); // Load custom values - $SQL = 'SELECT v.fieldid, '.Custom_Field::getRawValueSQLExpr('v').' as value + $SQL = 'SELECT v.fieldid, '.Custom_Field::getRawValueSQLExpr('v', 'f').' as value FROM custom_field_value v + JOIN custom_field f ON v.fieldid = f.id WHERE personid = '.(int)$this->id; $res = $GLOBALS['db']->queryAll($SQL, NULL, NULL, true, FALSE, TRUE); $this->_custom_values = $res; @@ -367,6 +368,13 @@ function validateFields() return TRUE; } + function hasAttendance() + { + $SQL = 'SELECT count(*) FROM attendance_record + WHERE personid = '.(int)$this->id; + return $GLOBALS['db']->queryOne($SQL); + } + function getAttendance($from='1970-01-01', $to='2999-01-01', $groupid=-1) { @@ -475,7 +483,7 @@ public static function getPersonsBySearch($searchTerm, $includeArchived=true) } - function save($update_family=TRUE) + public function save($update_family=TRUE) { $GLOBALS['system']->doTransaction('BEGIN'); $msg = ''; @@ -553,7 +561,8 @@ function save($update_family=TRUE) return $res; } - function _savePhoto() { + private function _savePhoto() + { $db =& $GLOBALS['db']; if ($this->_photo_data) { $SQL = 'REPLACE INTO person_photo (personid, photodata) @@ -562,10 +571,25 @@ function _savePhoto() { } } - function _saveCustomValues() { + private function _clearPhoto() + { + $db =& $GLOBALS['db']; + $SQL = 'DELETE FROM person_photo WHERE personid = '.(int)$this->id; + return $db->query($SQL); + } + + private function _clearCustomValues() + { $db =& $GLOBALS['db']; $SQL = 'DELETE FROM custom_field_value WHERE personid = '.(int)$this->id; $res = $db->query($SQL); + } + + private function _saveCustomValues() + { + if (empty($this->_old_custom_values)) return; // Nothing to do. + $this->_clearCustomValues(); + $db =& $GLOBALS['db']; $SQL = 'INSERT INTO custom_field_value (personid, fieldid, value_text, value_date, value_optionid) VALUES '; @@ -702,7 +726,7 @@ static function getStatusStats() static function getCustomMergeData($personids) { $db = $GLOBALS['db']; - $SQL = 'SELECT '.Custom_Field::getRawValueSQLExpr('v').' AS value, f.name, v.personid, v.fieldid + $SQL = 'SELECT '.Custom_Field::getRawValueSQLExpr('v', 'f').' AS value, f.name, v.personid, v.fieldid FROM custom_field_value v JOIN custom_field f ON v.fieldid = f.id WHERE v.personid IN ('.implode(',', array_map(Array($db, 'quote'), $personids)).')'; @@ -725,12 +749,12 @@ static function getCustomMergeData($personids) } return $res; } - + function getInstancesQueryComps($params, $logic, $order) { $res = parent::getInstancesQueryComps($params, $logic, $order); $res['select'][] = 'f.family_name, f.address_street, f.address_suburb, f.address_state, f.address_postcode, f.home_tel, c.name as congregation, ab.label as age_bracket'; - $res['from'] = '(('.$res['from'].') + $res['from'] = '(('.$res['from'].') JOIN family f ON person.familyid = f.id) LEFT JOIN congregation c ON person.congregationid = c.id JOIN age_bracket ab on ab.id = person.age_bracketid '; @@ -816,11 +840,12 @@ function printForm($prefix='', $fields=NULL) function processForm($prefix='', $fields=NULL) { $res = parent::processForm($prefix, $fields); - foreach ($this->getCustomFields() as $fieldid => $fieldDetails) { - $field = $GLOBALS['system']->getDBObject('custom_field', $fieldid); - $this->setCustomValue($fieldid, $field->processWidget($prefix)); + if (empty($fields)) { + foreach ($this->getCustomFields() as $fieldid => $fieldDetails) { + $field = $GLOBALS['system']->getDBObject('custom_field', $fieldid); + $this->setCustomValue($fieldid, $field->processWidget($prefix)); + } } - $this->_photo_data = Photo_Handler::getUploadedPhotoData($prefix.'photo'); return $res; } @@ -894,7 +919,7 @@ public function getCustomValues() public function fromCsvRow($row) { $this->_custom_values = Array(); $this->_old_custom_values = Array(); - + static $customFields = NULL; if ($customFields === NULL) { $fields = $GLOBALS['system']->getDBObjectdata('custom_field'); @@ -934,7 +959,7 @@ public function populate($id, $values) parent::populate($id, $values); $this->_custom_values = Array(); $this->_old_custom_values = Array(); - + foreach ($values as $k => $v) { if (0 === strpos($k, 'CUSTOM_')) { $this->setCustomValue(substr($k, 7), $v); @@ -942,6 +967,7 @@ public function populate($id, $values) } } +<<<<<<< HEAD /* *
  • Change their name to "Removed"
  • @@ -995,6 +1021,70 @@ public function archiveAndClean() $GLOBALS['system']->doTransaction('COMMIT'); $this->releaseLock(); return TRUE; +======= + /** + * Archive and clean this record: + * Change their name to "Removed" + * Change their status to "archived" + * Blank out all their fields except congregation + * Clear their history and notes + * Preserve their (anonymous) roster assignments, group memberships and attendance records + */ + public function archiveAndClean() + { + $res = 1; + $GLOBALS['system']->doTransaction('BEGIN'); + $this->setValue('first_name', '['._('Removed').']'); + $this->setValue('last_name', '['._('Removed').']'); + $this->setValue('email', ''); + $this->setValue('mobile_tel', ''); + $this->setValue('work_tel', ''); + $this->setValue('remarks', ''); + $this->setValue('gender', ''); + $this->setValue('feed_uuid', ''); + $this->setValue('status', 'archived'); + $this->setValue('history', Array()); + $this->_clearCustomValues(); + $this->_clearPhoto(); + if (!$this->save(FALSE)) return FALSE; + + $notes = $GLOBALS['system']->getDBObjectData('person_note', Array('personid' => $this->id)); + foreach ($notes as $noteid => $data) { + $n = new Person_Note($noteid); + $n->delete(); + } + + $family = $GLOBALS['system']->getDBObject('family', $this->getValue('familyid')); + $members = $family->getMemberData(); + $found_live_member = false; + foreach ($members as $id => $details) { + if ($id == $this->id) continue; + if ($details['status'] != 'archived') { + $found_live_member = true; + break; + } + } + if (!$found_live_member) { + if ($family->archiveAndClean()) $res = 2; + } + + $GLOBALS['system']->doTransaction('COMMIT'); + return $res; + } + + public function delete() + { + $GLOBALS['system']->doTransaction('BEGIN'); + $family = $GLOBALS['system']->getDBObject('family', $this->getValue('familyid')); + $members = $family->getMemberData(); + unset($members[$this->id]); + parent::delete(); + if (empty($members)) { + $family->delete(); + } + Abstract_Note::cleanupInstances(); + $GLOBALS['system']->doTransaction('COMMIT'); +>>>>>>> 6622ab7ffc27fa88e336672820b7059284f0e340 } } diff --git a/db_objects/person_group.class.php b/db_objects/person_group.class.php index ed433f02..376a49e6 100644 --- a/db_objects/person_group.class.php +++ b/db_objects/person_group.class.php @@ -115,7 +115,8 @@ function getInitSQL($table_name=NULL) PRIMARY KEY (`personid`,`groupid`), INDEX personid (personid), INDEX groupid (groupid), - CONSTRAINT `membership_status_fk` FOREIGN KEY (membership_status) REFERENCES person_group_membership_status (id) ON DELETE RESTRICT + CONSTRAINT `membership_status_fk` FOREIGN KEY (membership_status) REFERENCES person_group_membership_status (id) ON DELETE RESTRICT, + CONSTRAINT `pgm_personid` FOREIGN KEY (personid) REFERENCES _person(id) ON DELETE CASCADE ) ENGINE=InnoDB", ); } @@ -308,7 +309,7 @@ function printFieldValue($fieldname, $value=NULL) case 'owner': echo _(($value === NULL) ? 'Everyone' : 'Only me'); break; - + case 'categoryid': if ($value == 0) { echo '(Uncategorised)'; diff --git a/db_objects/person_note.class.php b/db_objects/person_note.class.php index 5bd16d13..84aaf887 100644 --- a/db_objects/person_note.class.php +++ b/db_objects/person_note.class.php @@ -25,7 +25,9 @@ function getInitSQL($table_name=NULL) CREATE TABLE `person_note` ( `personid` int(11) NOT NULL default '0', `id` int(11) NOT NULL default '0', - PRIMARY KEY (`personid`,`id`) + PRIMARY KEY (`personid`,`id`), + CONSTRAINT `pn_personid` FOREIGN KEY (personid) REFERENCES _person(id) ON DELETE CASCADE, + CONSTRAINT pn_id FOREIGN KEY (id) REFERENCES abstract_note(id) ON DELETE CASCADE ) ENGINE=InnoDB ; "; } @@ -68,7 +70,7 @@ function printFieldInterface($name, $prefix = '') { id) $this->setValue('subject', $template->getValue('subject')); diff --git a/db_objects/person_query.class.php b/db_objects/person_query.class.php index 11c108c3..346743b3 100644 --- a/db_objects/person_query.class.php +++ b/db_objects/person_query.class.php @@ -70,12 +70,12 @@ function getInitSQL($table_name=NULL) `owner` int(11) DEFAULT NULL, `params` text NOT NULL, `mailchimp_list_id` varchar(255) NOT NULL default '', + `show_on_homepage` varchar(12) not null default '', PRIMARY KEY (`id`) ) ENGINE=InnoDB ; "; } - protected static function _getFields() { $default_params = Array( @@ -123,8 +123,17 @@ protected static function _getFields() 'default' => '', 'placeholder' => '('._('Optional').')', 'tooltip' => _('If you have a MailChimp list you would like to synchronise with the results of this report, enter the relevant List ID here and wait until the sync script runs.'), - ) - + ), + 'show_on_homepage' => Array( + 'type' => 'select', + 'editable'=> true, + 'default' => NULL, + 'options' => Array( + '' => 'No', + 'auth' => 'Show for users with access to this report', + 'all' => 'Show for all users'), + + ), ); } @@ -423,9 +432,9 @@ class="select-rule-toggle" 'class' => 'attendance-input', ); print_widget('attendance_operator', $operator_params, array_get($params, 'attendance_operator', '<')); ?> - % + % -
    over the last weeks +
    over the last weeks - havePerm(PERM_MANAGEREPORTS)) { $visibilityParams = Array( @@ -625,6 +633,14 @@ class="select-rule-toggle" ?> + + + Show on home page? + printFieldInterface('show_on_homepage'); + ?> + + havePerm(PERM_SYSADMIN)) { ?> @@ -636,7 +652,6 @@ class="select-rule-toggle" } ?> - processFieldInterface('mailchimp_list_id'); } $this->setValue('owner', $_POST['is_private'] ? $GLOBALS['user_system']->getCurrentUser('id') : NULL); + $this->processFieldInterface('show_on_homepage'); break; case 'replace': $this->processFieldInterface('name'); @@ -660,6 +676,7 @@ function processForm($prefix='', $fields=NULL) $this->processFieldInterface('mailchimp_list_id'); } $this->setValue('owner', $_POST['is_private'] ? $GLOBALS['user_system']->getCurrentUser('id') : NULL); + $this->processFieldInterface('show_on_homepage'); break; case 'temp': $this->id = 'TEMP'; @@ -669,6 +686,7 @@ function processForm($prefix='', $fields=NULL) $this->id = 'TEMP'; } + $params = $this->_convertParams($this->getValue('params')); // FIELD RULES @@ -1085,9 +1103,11 @@ function getSQL($select_fields=NULL) $query['from'] .= ' LEFT JOIN custom_field_option cfogroup ON cfogroup.id = cfvgroup.value_optionid '; + $query['from'] .= ' LEFT JOIN custom_field cfgroup ON cfgroup.id = cfvgroup.fieldid + '; $grouping_order = 'IF(cfvgroup.personid IS NULL, 1, 0), '.Custom_Field::getSortValueSQLExpr('cfvgroup', 'cfogroup').', '; - $grouping_field = Custom_Field::getRawValueSQLExpr('cfvgroup', 'cfogroup').', '; - $query['group_by'][] = Custom_Field::getRawValueSQLExpr('cfvgroup', 'cfogroup'); + $grouping_field = Custom_Field::getRawValueSQLExpr('cfvgroup', 'cfgroup').', '; + $query['group_by'][] = Custom_Field::getRawValueSQLExpr('cfvgroup', 'cfgroup'); } else { // by some core field $grouping_order = $grouping_field = $params['group_by'].', '; @@ -1157,7 +1177,7 @@ function getSQL($select_fields=NULL) $query['from'] .= ' JOIN ( SELECT familyid, IF ( - GROUP_CONCAT(DISTINCT last_name) = ff.family_name, + GROUP_CONCAT(DISTINCT last_name) = ff.family_name, GROUP_CONCAT(first_name ORDER BY ab.rank, gender DESC SEPARATOR ", "), GROUP_CONCAT(CONCAT(first_name, " ", last_name) ORDER BY ab.rank, gender DESC SEPARATOR ", ") ) AS `names` @@ -1182,7 +1202,7 @@ function getSQL($select_fields=NULL) )'); $r2 = $GLOBALS['db']->query('INSERT INTO _family_adults'.$this->id.' (familyid, names) SELECT familyid, IF ( - GROUP_CONCAT(DISTINCT last_name) = ff.family_name, + GROUP_CONCAT(DISTINCT last_name) = ff.family_name, GROUP_CONCAT(first_name ORDER BY ab.rank, gender DESC SEPARATOR ", "), GROUP_CONCAT(CONCAT(first_name, " ", last_name) ORDER BY ab.rank, gender DESC SEPARATOR ", ") ) @@ -1198,9 +1218,9 @@ function getSQL($select_fields=NULL) case 'attendance_percent': $groupid = $params['attendance_groupid'] == '__cong__' ? 0 : $params['attendance_groupid']; $min_date = date('Y-m-d', strtotime('-'.(int)$params['attendance_weeks'].' weeks')); - $query['select'][] = '(SELECT ROUND(SUM(present)/COUNT(*)*100) - FROM attendance_record - WHERE date >= '.$GLOBALS['db']->quote($min_date).' + $query['select'][] = '(SELECT ROUND(SUM(present)/COUNT(*)*100) + FROM attendance_record + WHERE date >= '.$GLOBALS['db']->quote($min_date).' AND groupid = '.(int)$groupid.' AND personid = p.id) AS `Attendance`'; break; @@ -1241,7 +1261,9 @@ function getSQL($select_fields=NULL) $field = new Custom_Field(); $field->populate($customFieldID, $this->_custom_fields[$customFieldID]); $query['from'] .= ' LEFT JOIN custom_field_value cfv'.$customFieldID.' ON cfv'.$customFieldID.'.personid = p.id AND cfv'.$customFieldID.'.fieldid = '.$db->quote($customFieldID)."\n"; - $query['select'][] = 'GROUP_CONCAT(DISTINCT '.Custom_Field::getRawValueSQLExpr('cfv'.$customFieldID).' ORDER BY '.Custom_Field::getRawValueSQLExpr('cfv'.$customFieldID).' SEPARATOR "'.self::CUSTOMFIELDVAL_SEP.'") as '.$db->quote(self::CUSTOMFIELD_PREFIX.$customFieldID)."\n"; + $query['from'] .= ' LEFT JOIN custom_field cf'.$customFieldID.' ON cfv'.$customFieldID.'.fieldid = cf'.$customFieldID.'.id '."\n"; + + $query['select'][] = 'GROUP_CONCAT(DISTINCT '.Custom_Field::getRawValueSQLExpr('cfv'.$customFieldID, 'cf'.$customFieldID).' ORDER BY '.Custom_Field::getRawValueSQLExpr('cfv'.$customFieldID, 'cf'.$customFieldID).' SEPARATOR "'.self::CUSTOMFIELDVAL_SEP.'") as '.$db->quote(self::CUSTOMFIELD_PREFIX.$customFieldID)."\n"; } } else { $query['select'][] = $this->_quoteAliasAndColumn($field).' AS '.$db->quote($field); @@ -1310,7 +1332,7 @@ function getSQL($select_fields=NULL) } $sql .= "\nGROUP BY ".implode(', ', $query['group_by']); $sql .= "\nORDER BY ".$query['order_by'].', p.last_name, p.first_name'; - + return $sql; } diff --git a/db_objects/roster_role_assignment.class.php b/db_objects/roster_role_assignment.class.php index 7d2b5996..198101f7 100644 --- a/db_objects/roster_role_assignment.class.php +++ b/db_objects/roster_role_assignment.class.php @@ -14,9 +14,9 @@ function getInitSql($table_name = NULL) assigner int(11) not null, assignedon timestamp, primary key (roster_role_id, assignment_date, personid), - constraint foreign key rra_assiger (assigner) references _person(id), - constraint foreign key rra_personid (personid) references _person(id), - constraint foreign key rra_roster_role_id (roster_role_id) references roster_role(id) + constraint `rra_assiger` foreign key (assigner) references _person(id), + constraint `rra_personid` foreign key (personid) references _person(id) ON DELETE CASCADE, + constraint `rra_roster_role_id` foreign key (roster_role_id) references roster_role(id) ) ENGINE=InnoDB ;'; } @@ -42,5 +42,12 @@ static function getUpcomingAssignments($personid, $timeframe='4 weeks') return $res; } + static function hasAssignments($personid) + { + $SQL = 'SELECT count(*) FROM roster_role_assignment + WHERE personid = '.(int)$personid; + $res = $GLOBALS['db']->queryOne($SQL); + } + } ?> diff --git a/db_objects/roster_view.class.php b/db_objects/roster_view.class.php index e37e62b2..1a214e3d 100644 --- a/db_objects/roster_view.class.php +++ b/db_objects/roster_view.class.php @@ -560,10 +560,17 @@ private function _printOutputValue($member, $service, $asn, $withLinks=TRUE) if ($member['role_id']) { if (empty($asn)) echo '--'; foreach ($asn as $rank => $asn) { - if ($withLinks) echo ''; + if ($withLinks) echo ''; echo ents($asn['name']); if ($withLinks) { echo ''; + if (('' === $asn['email'])) echo ' '; + if (('' === $asn['mobile']) && ifdef('SMS_HTTP_URL')) { + echo ' '; + } + echo ''; + + } else { echo ' '; } diff --git a/db_objects/staff_member.class.php b/db_objects/staff_member.class.php index c9a3ba72..5669eac6 100644 --- a/db_objects/staff_member.class.php +++ b/db_objects/staff_member.class.php @@ -201,8 +201,8 @@ function printFieldInterface($name, $prefix='') ?> - - + +
    Restrict to these congregations:  Restrict to these groups:Restrict to these congregations:  Restrict to these groups:
    @@ -221,7 +221,9 @@ function printFieldInterface($name, $prefix='')
    -

    If you select congregations or groups here, this user will only be able to see persons who belong to one of the selected congregations or groups. It will look like those are the only congregations, groups and persons in the system. Users with congregation or group restrictions cannot add new persons or families. Changes to restrictions take effect immediately.

    +

    If you select congregations or groups here, this user will only be able to see persons who belong to one of the selected congregations or groups. It will look like those are the only congregations, groups and persons in the system. + Users with group restrictions cannot add new persons or families. Changes to restrictions take effect immediately. +

    printFieldValue($name); diff --git a/include/db_object.class.php b/include/db_object.class.php index d88f9924..b01976cb 100644 --- a/include/db_object.class.php +++ b/include/db_object.class.php @@ -320,8 +320,8 @@ public function save() return FALSE; } - // Set the history - if (isset($this->fields['history'])) { + // Add to the history, unless it's been explicly set as a value (see Person::archiveAndClean()) + if (isset($this->fields['history']) && empty($this->_old_values['history'])) { $changes = $this->_getChanges(); if ($changes) { $user = $GLOBALS['user_system']->getCurrentPerson(); @@ -389,6 +389,11 @@ protected function _getChanges() foreach ($this->_old_values as $name => $old_val) { if ($name == 'history') continue; if ($name == 'password') continue; + if (!array_get($this->fields[$name], 'show_in_summary', TRUE) + && !array_get($this->fields[$name], 'editable', TRUE) + ) { + continue; + } $changes[] = $this->getFieldLabel($name).' changed from "'.ents($this->getFormattedValue($name, $old_val)).'" to "'.ents($this->getFormattedValue($name)).'"'; } return $changes; @@ -455,7 +460,7 @@ public function setValue($name, $value) $value = ucfirst($value); } if (array_get($this->fields[$name], 'trim')) { - $value = trim($value, ",;. \t\n\r\0\x0B"); + $value = hard_trim($value); } if ($this->fields[$name]['type'] == 'select') { if (!isset($this->fields[$name]['options'][$value]) && !(array_get($this->fields[$name], 'allow_empty', 1) && empty($value))) { @@ -594,7 +599,7 @@ public function printFieldValue($name, $value=NULL) return NULL; } if (is_null($value)) $value = $this->getValue($name); - if (($name == 'history') && !empty($value)) { + if (($name == 'history')) { ?> " " $width_exp = 'size="3" '; } ?> - /> + /> " } break; case 'date': - $year_classes = $day_year_classes = trim($classes.' int-box'); + $year_classes = $day_year_classes = $classes; if (array_get($params, 'allow_blank_year', false)) $year_classes .= ' optional-year'; if (FALSE === strpos($name, '[')) { $name_template = $name.'%s'; @@ -379,7 +383,7 @@ class="" list($year_val, $month_val, $day_val) = explode('-', substr($value, 0, 10)); ?> > - + self::MAX_PHOTO_WIDTH) || ($orig_height > self::MAX_PHOTO_HEIGHT)) { - if (self::MAX_PHOTO_WIDTH > self::MAX_PHOTO_HEIGHT) { + if ($crop == self::CROP_HEIGHT) { // resize to fit width then crop to fit height $new_width = self::MAX_PHOTO_WIDTH; $new_height = min(self::MAX_PHOTO_HEIGHT, $new_width / $orig_ratio); @@ -63,7 +68,7 @@ public static function getUploadedPhotoData($fieldName) $src_w = $orig_width; $src_h = $new_height * ($orig_width / $new_width); $src_y = (int)max(0, ($orig_height - $src_h) / 2); - } else { + } else if ($crop == self::CROP_WIDTH) { // resize to fit height then crop to fit width $new_height = self::MAX_PHOTO_HEIGHT; $new_width = min(self::MAX_PHOTO_WIDTH, $new_height * $orig_ratio); @@ -71,6 +76,13 @@ public static function getUploadedPhotoData($fieldName) $src_h = $orig_height; $src_w = $new_width * ($orig_height / $new_height); $src_x = (int)max(0, ($orig_width - $src_w) / 2); + } else { + // Just resize, no cropping + $new_width = self::MAX_PHOTO_WIDTH; + $new_height = min(self::MAX_PHOTO_HEIGHT, $new_width / $orig_ratio); + $src_x = $src_y = 0; + $src_w = $orig_width; + $src_h = $orig_height; } $output_img = imagecreatetruecolor($new_width, $new_height); imagecopyresampled($output_img, $input_img, 0, 0, $src_x, $src_y, $new_width, $new_height, $src_w, $src_h); @@ -102,11 +114,21 @@ public static function getPhotoData($type, $id) $SQL = $obj = NULL; if ($type == 'person') { $obj = $GLOBALS['system']->getDBObject('person', (int)$id); - if ($obj) $SQL = 'SELECT photodata FROM person_photo WHERE personid = '.$obj->id; + if ($obj) { + // for single-member families, show family photo as individual photo if individual photo is missing + $SQL = 'SELECT COALESCE(pp.photodata, IF(count(member.id) = 1, fp.photodata, NULL)) as photodata + FROM person p + JOIN family f ON p.familyid = f.id + JOIN person member ON member.familyid = f.id + LEFT JOIN person_photo pp ON pp.personid = p.id + LEFT JOIN family_photo fp ON fp.familyid = f.id + WHERE p.id = '.(int)$obj->id.' + GROUP BY p.id'; + } } else if ($type == 'family') { $obj = $GLOBALS['system']->getDBObject('family', (int)$id); if ($obj) { - // for single-member families, treat person photo as family photo + // for single-member families, treat person photo as family photo if family photo is missing $SQL = 'SELECT COALESCE(fp.photodata, IF(count(p.id) = 1, pp.photodata, NULL)) as photodata FROM family f LEFT JOIN family_photo fp ON fp.familyid = f.id diff --git a/members/views/view_0_edit_me.class.php b/members/views/view_0_edit_me.class.php index 7e2f460b..caacce4c 100644 --- a/members/views/view_0_edit_me.class.php +++ b/members/views/view_0_edit_me.class.php @@ -4,6 +4,7 @@ class View__Edit_Me extends View private $family = NULL; private $persons = Array(); private $hasAdult = FALSE; + private $person_fields = Array('gender', 'age_bracketid', 'email', 'mobile_tel', 'work_tel', 'photo'); function getTitle() { @@ -32,9 +33,9 @@ function processView() $this->family->processForm(); $this->family->save(); $this->family->releaseLock(); - + $fields = Array('gender', 'age_bracket', 'email', 'mobile_tel', 'work_tel'); foreach ($this->persons as $person) { - $person->processForm('person_'.$person->id); + $person->processForm('person_'.$person->id, $this->person_fields); $person->save(FALSE); $person->releaseLock(); } @@ -78,7 +79,7 @@ function printView() foreach ($this->persons as $person) { echo '

    '.$person->getValue('first_name').' '.$person->getValue('last_name').'

    '; - $person->printForm('person_'.$person->id, Array('gender', 'age_bracketid', 'email', 'mobile_tel', 'work_tel', 'photo')); + $person->printForm('person_'.$person->id, $this->person_fields); } ?> diff --git a/resources/js/tb_lib.js b/resources/js/tb_lib.js index 5dfd9d84..279a0f1e 100644 --- a/resources/js/tb_lib.js +++ b/resources/js/tb_lib.js @@ -36,8 +36,6 @@ $(document).ready(function() { } //// VALIDATION //// - $(document.body).on('focus', 'input.int-box', TBLib.handleIntBoxFocus) - .on('keydown', 'input.int-box', TBLib.handleIntBoxKeyPress); $('input.bible-ref').change(TBLib.handleBibleRefBlur); $('input.valid-email').change(TBLib.handleEmailBlur); $('input.day-box').change(TBLib.handleDayBoxBlur); @@ -822,28 +820,6 @@ TBLib.handleEmailBlur = function() return true; } -TBLib.handleIntBoxKeyPress = function(event) -{ - if (!event) event = window.event; - if (event.altKey || event.ctrlKey || event.metaKey) return true; - var keyCode = event.keyCode ? event.keyCode : event.which; - validKeys = new Array(8, 9, 13, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 97, 98, 99, 100, 101, 102, 103, 104, 105, 96, 46, 36, 35, 37, 39); - if (!validKeys.contains(keyCode)) { - return false; - } -} - -TBLib.handleIntBoxFocus = function() -{ - // This complexity is to work around a problem in chrome where the event somehow fired twice - // resulting in the box being selected then unselected. - if (TBLib.intBoxFocuser) { - clearTimeout(TBLib.intBoxFocuser); - } - var w = this; - TBLib.intBoxFocuser = setTimeout(function() { w.select() }, 100); -} - TBLib.handleSelectAllClick = function() { $(this).parents('table:first').find('input[type=checkbox]').attr('checked', this.checked); diff --git a/resources/less/jethro.less.php b/resources/less/jethro.less.php index c88450c6..fb0580a9 100644 --- a/resources/less/jethro.less.php +++ b/resources/less/jethro.less.php @@ -761,10 +761,10 @@ } .day-box { - width: 3ex !important; + width: 4.5ex !important; } .year-box { - width: 5ex !important; + width: 6.5ex !important; } @media (min-width: 1px) { /* support for media queries roughly equivalent for support for such things as the placeholder attr */ diff --git a/scripts/email_report.php b/scripts/email_report.php index 4ea74c3b..6ffaa003 100644 --- a/scripts/email_report.php +++ b/scripts/email_report.php @@ -235,4 +235,4 @@ } else { echo $reportname." Mail send ... ERROR!"; } - } \ No newline at end of file + } diff --git a/templates/view_person.template.php b/templates/view_person.template.php index 6437f2a4..4fb07e42 100644 --- a/templates/view_person.template.php +++ b/templates/view_person.template.php @@ -162,6 +162,11 @@ $person->printSummary(); + if ($GLOBALS['user_system']->havePerm(PERM_SYSADMIN)) { + ?> + + getValue('address_street')) { ?> diff --git a/upgrades/2017-upgrade-to-2.21.sql b/upgrades/2017-upgrade-to-2.21.sql index d9b956c5..d80faf3c 100644 --- a/upgrades/2017-upgrade-to-2.21.sql +++ b/upgrades/2017-upgrade-to-2.21.sql @@ -15,4 +15,4 @@ SET @rank = (SELECT rank FROM setting WHERE symbol = 'SMTP_SERVER'); INSERT INTO setting (rank, heading, symbol, note, type, value) VALUES -(@rank+1, NULL, 'SMTP_PORT', 'Port to connect to the SMTP server. Usually 25, 465 for SSL, or 587 for TLS.', 'int', '25'); \ No newline at end of file +(@rank+1, NULL, 'SMTP_PORT', 'Port to connect to the SMTP server. Usually 25, 465 for SSL, or 587 for TLS.', 'int', '25'); diff --git a/upgrades/2018-upgrade-to-2.24.sql b/upgrades/2018-upgrade-to-2.24.sql new file mode 100644 index 00000000..fc8afba1 --- /dev/null +++ b/upgrades/2018-upgrade-to-2.24.sql @@ -0,0 +1,68 @@ +/* Front page reports */ +ALTER table person_query +ADD COLUMN `show_on_homepage` varchar(16) not null default ''; + +/* Issue #457 - clean up zero dates just for tidyness */ +UPDATE _person SET status_last_changed = NULL where CAST(status_last_changed AS CHAR(20)) = '0000-00-00 00:00:00' ; + +/* issue #506 - is_adult col sometimes wrong definition */ +UPDATE age_bracket set is_adult = 0 where is_adult <> 1; +ALTER TABLE age_bracket MODIFY COLUMN is_adult TINYINT(1) UNSIGNED NOT NULL DEFAULT 0; + +/* issue #492 - correct the wording for the RESTRICTED_USERS_CAN_ADD setting */ +UPDATE setting SET note = 'Can users with congregation restrictions add new persons and families?' where symbol = 'RESTRICTED_USERS_CAN_ADD'; + +/* issue #30 - delete people altogether */ +/* To make sure FKs are right on roster_role_assignment we recreate the table +since the FK names are historically inconsistent */ + +ALTER TABLE roster_role_assignment +RENAME TO _disused_roster_role_assn; + +create table roster_role_assignment ( +assignment_date date not null, +roster_role_id int(11) not null, +personid int(11) not null, +rank int unsigned not null default 0, +assigner int(11) not null, +assignedon timestamp, +primary key (roster_role_id, assignment_date, personid), +constraint `rra_assiger` foreign key (assigner) references _person(id), +constraint `rra_personid` foreign key (personid) references _person(id) ON DELETE CASCADE, +constraint `rra_roster_role_id` foreign key (roster_role_id) references roster_role(id) +) ENGINE=InnoDB ; + +INSERT INTO roster_role_assignment +SELECT * FROM _disused_roster_role_assn; + +DELETE FROM attendance_record WHERE personid NOT IN (select id FROM _person); +ALTER TABLE attendance_record +ADD CONSTRAINT `ar_personid` FOREIGN KEY (personid) REFERENCES _person(id) ON DELETE CASCADE; + +DELETE FROM person_note WHERE personid NOT IN (select id FROM _person); +ALTER TABLE person_note +ADD CONSTRAINT `pn_personid` FOREIGN KEY (personid) REFERENCES _person(id) ON DELETE CASCADE; + +DELETE FROM family_note WHERE familyid NOT IN (select id from family); +ALTER TABLE family_note +ADD CONSTRAINT `fn_familyid` FOREIGN KEY (familyid) REFERENCES family(id) ON DELETE CASCADE; + +DELETE FROM person_group_membership WHERE personid NOT IN (select id FROM _person); +ALTER TABLE person_group_membership +ADD CONSTRAINT `pgm_personid` FOREIGN KEY (personid) REFERENCES _person(id) ON DELETE CASCADE; + +DELETE FROM person_note where id NOT IN (SELECT id from abstract_note); +ALTER TABLE person_note +ADD CONSTRAINT pn_id FOREIGN KEY (id) REFERENCES abstract_note(id) ON DELETE CASCADE; + +DELETE FROM family_note WHERE id NOT IN (SELECT id from abstract_note); +ALTER TABLE family_note +ADD CONSTRAINT fn_id FOREIGN KEY (id) REFERENCES abstract_note(id) ON DELETE CASCADE; + +DELETE FROM abstract_note WHERE id not IN (SELECT id from person_note UNION SELECT id from family_note); + +ALTER TABLE family_photo +DROP FOREIGN KEY `famliyphotofamilyid`; + +ALTER TABLE family_photo +ADD CONSTRAINT `famliyphotofamilyid` FOREIGN KEY (familyid) REFERENCES family(id) ON DELETE CASCADE; diff --git a/views/abstract_view_notes_list.class.php b/views/abstract_view_notes_list.class.php index 0db33576..ecfe849c 100644 --- a/views/abstract_view_notes_list.class.php +++ b/views/abstract_view_notes_list.class.php @@ -95,8 +95,7 @@ function printView() ?>

    - - +

    -
  • Change their name to "Removed"
  • +
  • Change their name to "[Removed]"
  • Change their status to "archived"
  • -
  • Blank out all their fields except congregation
  • -
  • Clear their history and notes
  • +
  • Blank out all their fields and custom fields, except congregation and age bracket
  • +
  • Clear their history, notes and photo
  • +
  • Do the same for their family, if the family has no remaining non-archived members
  • Preserve their (anonymous) roster assignments, group memberships and attendance records
  • '; static function getMenuPermissionLevel() { - return PERM_SYSADMIN; // TODO: check + return PERM_SYSADMIN; } function processView() @@ -25,20 +27,43 @@ function processView() } if (empty($this->_person)) trigger_error("Person not found", E_USER_ERROR); // exits $this->_staff_member = $GLOBALS['system']->getDBObject('staff_member', $this->_person->id); - + + $this->_notes = $GLOBALS['system']->getDBObjectData( + 'person_note', + Array('personid' => $this->_person->id, 'status' => 'pending'), + 'AND' + ); + $family = $GLOBALS['system']->getDBObject('family', $this->_person->getValue('familyid')); + $members = $family->getMemberData(); + if (count($members) == 1) { + $fnotes = $GLOBALS['system']->getDBObjectData( + 'family_note', + Array('familyid' => $family->id, 'status' => 'pending'), + 'AND' + ); + $this->_notes = array_merge($this->_notes, $fnotes); + } + if (empty($this->_staff_member) && !empty($_POST['confirm_delete'])) { // delete the person altogether $this->_person->delete(); + add_message($this->_person->toString().' has been deleted', 'success'); + redirect('home'); } else if (!empty($_POST['confirm_archiveclean'])) { // archive and anononmize the person - if (!$this->_person->aquireLock()) { + if (!$this->_person->acquireLock()) { add_message('This person cannot be deleted because somebody else holds the lock. Try again later.', 'error'); redirect('persons', Array('personid' => $this->_person->id)); // exits } - $this->_person->archiveAndClean(); - add_message($this->_person->toString().' has been archived and cleaned', 'success'); - redirect('persons', Array('personid' => $this->_person->id)); // exits + $message = $this->_person->toString().' has been archived and cleaned'; + $res = $this->_person->archiveAndClean(); + if ($res == 2) $message .= ' and so has their family'; + if ($res == 1) $message .= ' but their family has a remaining member and has been left untouched'; + if ($res) { + add_message($message, 'success'); + redirect('persons', Array('personid' => $this->_person->id)); // exits + } } } @@ -51,24 +76,50 @@ public function getTitle() public function printView() { $buttons = Array( + 'archive' => _('Archive only'), + 'archiveclean' => _('Archive and Cleanse'), 'delete' => _('Delete altogether'), - 'archiveclean' => _('Archive and Clean'), ); - - if ($this->_staff_member) { + if ($this->_notes) { + ?> +

    + +

    + + _staff_member) { ?> -

    -

    +

    +

    '; unset($buttons['delete']); - } else if ($this->_person->hasRosterAssignments() || $this->_person->hasAttendance()) { + } else if (Roster_Role_Assignment::hasAssignments($this->_person->id) || $this->_person->hasAttendance()) { ?>

    -

    +

    + +

    +

    + +

    +

    + @@ -76,10 +127,12 @@ public function printView() $label) { ?> - + + + - + havePerm(PERM_RUNREPORT)) { + $reportVals[] = 'auth'; + } + $frontpagereports = $GLOBALS['system']->getDBObjectData('person_query', Array('show_on_homepage' => $reportVals)); + foreach ($frontpagereports as $reportid => $reportparams) { + $report = $GLOBALS['system']->getDBObject('person_query', $reportid); + ?> +
    +

    + printResults(); ?> +
    + +} \ No newline at end of file diff --git a/views/view_2_families__4_contact_list.class.php b/views/view_2_families__4_contact_list.class.php index cf3b8133..5b002909 100644 --- a/views/view_2_families__4_contact_list.class.php +++ b/views/view_2_families__4_contact_list.class.php @@ -155,7 +155,7 @@ function printResults($dataURLs=FALSE) } ?>
    - +