/** * Validates that a specific date string can be correctly parsed into a vague date. * * @param string SDate */ public static function vague_date($sDate) { if (vague_date::string_to_vague_date($sDate) != false) { return true; } return false; }
/** * Hook into the task scheduler. Runs a query to find all comments and verification status updates that need * to be notified back to the recorder of a record. */ function notify_verifications_and_comments_scheduled_task($last_run_date) { if (!$last_run_date) { // first run, so get all records changed in last day. Query will automatically gradually pick up the rest. $last_run_date = date('Y-m-d', time() - 60 * 60 * 24 * 50); } try { $db = new Database(); $notifications = postgreSQL::selectVerificationAndCommentNotifications($last_run_date, $db); foreach ($notifications as $notification) { $vd = array($notification->date_start, $notification->date_end, $notification->date_type); $date = vague_date::vague_date_to_string($vd); if (empty($notification->comment)) { switch ($notification->record_status) { case 'V': $action = 'verified'; break; case 'R': $action = 'rejected'; break; case 'D': $action = 'marked dubious'; break; case 'S': $action = 'emailed for checking'; break; } $comment = 'The record of ' . $notification->taxon . ' at ' . $notification->public_entered_sref . " on {$date} was {$action}."; } else { if ($notification->auto_generated === 't') { $comment = 'An automated check using the <a target="_blank" href="http://www.nbn.org.uk/Tools-Resources/Recording-Resources/NBN-Record-Cleaner.aspx" target="_blank">' . 'NBN Record Cleaner</a> rules has highlighted your record of ' . $notification->taxon . ' at ' . $notification->public_entered_sref . ' on ' . $date; $comment .= $notification->generated_by === 'data_cleaner_identification_difficulty' ? ' as being of a species for which identification is not always trivial. <br/><em>' : '. The following information was given: <br/><em>'; } elseif ($notification->verified_on > $last_run_date and $notification->record_status !== 'I' and $notification->record_status !== 'T' and $notification->record_status !== 'C') { $comment = 'Your record of ' . $notification->taxon . ' at ' . $notification->public_entered_sref . ' on ' . $date . ' was examined by an expert.<br/>"'; } elseif ($notification->record_owner === 't') { $comment = 'A comment was added to your record of ' . $notification->taxon . ' at ' . $notification->public_entered_sref . ' on ' . $date . '.<br/>"'; } else { $comment = 'A reply was added to the record of ' . $notification->taxon . ' at ' . $notification->public_entered_sref . ' on ' . $date . ' which you\'ve previously commented on.<br/>"'; } $comment .= $notification->comment; if ($notification->auto_generated === 't') { // a difficult ID record is not necessarily important... $thing = $notification->generated_by === 'data_cleaner_identification_difficulty' ? 'identification' : 'important record'; $comment .= "</em><br/>You may be contacted by an expert to confirm this {$thing} so if you can supply any more information or photographs it would be useful."; } else { $comment .= '"<br/>'; } } $theNotificationToInsert = array('source' => 'Verifications and comments', 'source_type' => $notification->source_type, 'data' => json_encode(array('username' => $notification->username, 'occurrence_id' => $notification->id, 'comment' => $comment, 'taxon' => $notification->taxon, 'date' => $date, 'entered_sref' => $notification->public_entered_sref, 'auto_generated' => $notification->auto_generated, 'record_status' => $notification->record_status, 'updated_on' => $notification->updated_on)), 'linked_id' => $notification->id, 'user_id' => $notification->notify_user_id, 'digest_mode' => 'N', 'source_detail' => $notification->source_detail); $db->insert('notifications', $theNotificationToInsert); } echo count($notifications) . ' notifications generated<br/>'; } catch (Exception $e) { echo $e->getMessage(); } }
public function Index() { $start = ''; $end = ''; $type = ''; $err = ''; $log = ''; if (array_key_exists('date', $_POST)) { $str = $_POST['date']; $log .= "Have string in postdata<br />"; $log .= "Parsing for string " . $str . "<br />"; $arr = vague_date::string_to_vague_date($str); if ($arr != false) { $log .= "Parsed correctly<br />"; $start = $arr['start']; $end = $arr['end']; $type = $arr['type']; } else { $err = 'Unable to parse date'; $log .= $err; } } else { $log .= "No postdata<br />"; } ?> <form method='post'><input name='date' id='date' value=''/><br /><input type='submit' value='Parse' /></form> <?php echo $err; ?> <ul> <li>Start: <?php echo $start; ?> <li> <li>End: <?php echo $end; ?> <li> <li>Type: <?php echo $type; ?> </li> </ul> <?php echo $log; ?> <?php }
public function Index() { $time = time(); echo '<ul>'; echo '<li>Today: ' . vague_date::vague_date_to_string(array(date_create('2008-11-01'), date_create('2008-11-01'), 'D')) . '</li>'; echo '<li>Day range for 1 week: ' . vague_date::vague_date_to_string(array(date_create('2008-11-01'), date_create('2008-11-08'), 'DD')) . '</li>'; echo '<li>Month in Year: ' . vague_date::vague_date_to_string(array(date_create('2008-11-01'), date_create('2008-11-30'), 'O')) . '</li>'; echo '<li>Month in Year range: ' . vague_date::vague_date_to_string(array(date_create('2008-10-01'), date_create('2008-11-30'), 'OO')) . '</li>'; echo '<li>Season in year: ' . vague_date::vague_date_to_string(array(date_create('2008-09-01'), date_create('2008-11-30'), 'P')) . '</li>'; echo '<li>Year: ' . vague_date::vague_date_to_string(array(date_create('2008-01-01'), date_create('2008-12-31'), 'Y')) . '</li>'; echo '<li>Years: ' . vague_date::vague_date_to_string(array(date_create('2006-01-01'), date_create('2008-12-31'), 'YY')) . '</li>'; echo '<li>From Year: ' . vague_date::vague_date_to_string(array(date_create('2006-01-01'), null, 'Y-')) . '</li>'; echo '<li>To Year: ' . vague_date::vague_date_to_string(array(null, date_create('2008-12-31'), '-Y')) . '</li>'; echo '<li>Month: ' . vague_date::vague_date_to_string(array(date_create('2006-01-01'), date_create('2006-01-31'), 'M')) . '</li>'; echo '<li>Season: ' . vague_date::vague_date_to_string(array(date_create('2008-03-01'), date_create('2008-05-31'), 'S')) . '</li>'; echo '<li>Unknown: ' . vague_date::vague_date_to_string(array(null, null, 'U')) . '</li>'; echo '<li>Century: ' . vague_date::vague_date_to_string(array(date_create('1801-01-01'), date_create('1900-12-31'), 'C')) . '</li>'; echo '<li>Centuries: ' . vague_date::vague_date_to_string(array(date_create('1701-01-01'), date_create('1900-12-31'), 'CC')) . '</li>'; echo '<li>Century From: ' . vague_date::vague_date_to_string(array(date_create('1701-01-01'), null, 'C-')) . '</li>'; echo '<li>Century To: ' . vague_date::vague_date_to_string(array(null, date_create('1800-12-31'), '-C')) . '</li>'; echo '</ul'; }
/** * Fills the temporary table called occdelta, which contains details of each proposed record to * verify. This is only done when calling verify directly, since occdelta is prepared by the * scheduled_tasks process when auto-checks are being run on the schedule. */ private function prepareOccdelta($db, $sample, $occurrences) { $website_id = $this->website_id; $srid = kohana::config('sref_notations.internal_srid'); $last_sref = ''; $last_sref_system = ''; foreach ($occurrences as $occurrence) { $record = array_merge($sample, $occurrence); $survey_id = $record['sample:survey_id']; $sref = $record['sample:entered_sref']; $sref_system = $record['sample:entered_sref_system']; $taxa_taxon_list_id = $record['occurrence:taxa_taxon_list_id']; if (isset($record['sample:geom'])) { $geom = $record['sample:geom']; } else { // avoid recalculating the geom if we don't have to as this is relatively expensive if ($sref !== $last_sref || $sref_system !== $last_sref_system) { $geom = spatial_ref::sref_to_internal_wkt($sref, $sref_system); } $last_sref = $sref; $last_sref_system = $sref_system; } $date = $record['sample:date']; $vd = vague_date::string_to_vague_date($date); $date_start = $vd[0]; $date_end = $vd[1]; $date_type = $vd[2]; $db->query("insert into occdelta (website_id, survey_id, date_start, date_end, date_type, public_entered_sref, entered_sref_system, public_geom, taxa_taxon_list_id)\n values ({$website_id}, {$survey_id}, '{$date_start}', '{$date_end}', '{$date_type}', '{$sref}', '{$sref_system}', st_geomfromtext('{$geom}', {$srid}), {$taxa_taxon_list_id});"); } // patch in some extra details about the taxon required for each cache entry $db->query("update occdelta o set taxon_meaning_id=ttl.taxon_meaning_id, taxon=ttl.taxon, taxa_taxon_list_external_key=ttl.external_key " . "from list_taxa_taxon_lists ttl where ttl.id=o.taxa_taxon_list_id"); }
/** * Create a virtual field called default_value from the relevant default value fields, depending on the data type. */ public function __get($column) { if ($column == 'default_value') { $attr = ORM::factory('survey_attribute', $this->survey_attribute_id); switch ($attr->data_type) { case 'T': return parent::__get('default_text_value'); case 'F': return parent::__get('default_float_value'); case 'I': case 'L': return parent::__get('default_int_value'); case 'D': case 'V': $vagueDate = array(parent::__get('default_date_start_value'), parent::__get('default_date_end_value'), parent::__get('default_date_type_value')); return vague_date::vague_date_to_string($vagueDate); } } else { return parent::__get($column); } }
protected function createAttributeRecord($attrId, $valueId, $value, $attrDef) { // There are particular circumstances when $value is actually an array: when a attribute is multi value, // AND has yet to be created, AND is passed in as multiple ***Attr:<n>[] POST variables. This should only happen when // the attribute has yet to be created, as after this point the $valueID is filled in and that specific attribute POST variable // is no longer multivalue - only one value is stored per attribute value record, though more than one record may exist // for a given attribute. There may be others with th same <n> without a $valueID. // If attrId = fk_* (e.g. when importing data) then the value is a term whose id needs to be looked up. if (is_array($value)) { if (is_null($valueId)) { $retVal = true; foreach ($value as $singlevalue) { // recurse over array. $retVal = $this->createAttributeRecord($attrId, $valueId, $singlevalue, $attrDef) && $retVal; } return $retVal; } else { $this->errors['general'] = 'INTERNAL ERROR: multiple values passed in for ' . $this->object_name . ' ' . $valueId . ' ' . print_r($value, true); return false; } } $fk = false; if (substr($attrId, 0, 3) == 'fk_') { // value is a term that needs looking up $fk = true; $attrId = substr($attrId, 3); $value = trim($value); } // Create a attribute value, loading the existing value id if it exists, or search for the existing record // if not multivalue but no id supplied and not a new record // @todo: Optimise attribute saving by using query builder rather than ORM if ($this->existing && !is_null($valueId) && !$attrDef->multi_value == 'f') { $attrValueModel = ORM::factory($this->object_name . '_attribute_value')->where(array($this->object_name . '_attribute_id' => $attrId, $this->object_name . '_id' => $this->id))->find(); } if (!isset($attrValueModel) || !$attrValueModel->loaded) { $attrValueModel = ORM::factory($this->object_name . '_attribute_value', $valueId); } $oldValues = array_merge($attrValueModel->as_array()); $dataType = $attrDef->data_type; $vf = null; $fieldPrefix = array_key_exists('field_prefix', $this->submission) ? $this->submission['field_prefix'] . ':' : ''; // For attribute value errors, we need to report e.g smpAttr:attrId[:attrValId] as the error key name, not // the table and field name as normal. $fieldId = $fieldPrefix . $this->attrs_field_prefix . ':' . $attrId; if ($attrValueModel->id) { $fieldId .= ':' . $attrValueModel->id; } switch ($dataType) { case 'T': $vf = 'text_value'; break; case 'F': $vf = 'float_value'; break; case 'D': case 'V': // Date if (!empty($value)) { $vd = vague_date::string_to_vague_date($value); if ($vd) { $attrValueModel->date_start_value = $vd[0]; $attrValueModel->date_end_value = $vd[1]; $attrValueModel->date_type_value = $vd[2]; kohana::log('debug', "Accepted value {$value} for attribute {$fieldId}"); kohana::log('debug', " date_start_value=" . $attrValueModel->date_start_value); kohana::log('debug', " date_end_value=" . $attrValueModel->date_end_value); kohana::log('debug', " date_type_value=" . $attrValueModel->date_type_value); } else { $this->errors[$fieldId] = "Invalid value {$value} for attribute " . $attrDef->caption; kohana::log('debug', "Could not accept value {$value} into date fields for attribute {$fieldId}."); return false; } } else { $attrValueModel->date_start_value = null; $attrValueModel->date_end_value = null; $attrValueModel->date_type_value = null; } break; case 'G': $vf = 'geom_value'; break; case 'B': // Boolean $vf = 'int_value'; if (!empty($value)) { $lower = strtolower($value); if ($lower == 'false' || $lower == 'f' || $lower == 'no' || $lower == 'n' || $lower == 'off') { $value = 0; } elseif ($lower == 'true' || $lower == 't' || $lower == 'yes' || $lower == 'y' || $lower == 'on') { $value = 1; } } break; case 'L': // Lookup list $vf = 'int_value'; if (!empty($value) && $fk) { // value must be looked up $r = $this->fkLookup(array('fkTable' => 'lookup_term', 'fkSearchField' => 'term', 'fkSearchValue' => $value, 'fkSearchFilterField' => 'termlist_id', 'fkSearchFilterValue' => $attrDef->termlist_id)); if ($r) { $value = $r; } else { $this->errors[$fieldId] = "Invalid value {$value} for attribute " . $attrDef->caption; kohana::log('debug', "Could not accept value {$value} into field {$vf} for attribute {$fieldId}."); return false; } } break; default: // Integer $vf = 'int_value'; break; } if ($vf != null) { $attrValueModel->{$vf} = $value; // Test that ORM accepted the new value - it will reject if the wrong data type for example. // Use a string compare to get a proper test but with type tolerance. // A wkt geometry gets translated to a proper geom so this will look different - just check it is not empty. // A float may loose precision or trailing 0 - just check for small percentage difference if (strcmp($attrValueModel->{$vf}, $value) === 0 || $dataType === 'G' && !empty($attrValueModel->{$vf})) { kohana::log('debug', "Accepted value {$value} into field {$vf} for attribute {$fieldId}."); } else { if ($dataType === 'F' && abs($attrValueModel->{$vf} - $value) < 1.0E-5 * $attrValueModel->{$vf}) { kohana::log('alert', "Lost precision accepting value {$value} into field {$vf} for attribute {$fieldId}. Value=" . $attrValueModel->{$vf}); } else { $this->errors[$fieldId] = "Invalid value {$value} for attribute " . $attrDef->caption; kohana::log('debug', "Could not accept value {$value} into field {$vf} for attribute {$fieldId}."); return false; } } } // set metadata $exactMatches = array_intersect_assoc($oldValues, $attrValueModel->as_array()); // which fields do we have in the submission? $fieldsWithValuesInSubmission = array_intersect_key($oldValues, $attrValueModel->as_array()); // Hook to the owning entity (the sample, location, taxa_taxon_list or occurrence) $thisFk = $this->object_name . '_id'; $attrValueModel->{$thisFk} = $this->id; // and hook to the attribute $attrFk = $this->object_name . '_attribute_id'; $attrValueModel->{$attrFk} = $attrId; // we'll update metadata only if at least one of the fields have changed $wantToUpdateAttrMetadata = count($exactMatches) !== count($fieldsWithValuesInSubmission); if (!$wantToUpdateAttrMetadata) { $attrValueModel->wantToUpdateMetadata = false; } try { $v = $attrValueModel->validate(new Validation($attrValueModel->as_array()), true); } catch (Exception $e) { $v = false; $this->errors[$fieldId] = $e->getMessage(); error::log_error('Exception during validation', $e); } if (!$v) { foreach ($attrValueModel->errors as $key => $value) { // concatenate the errors if more than one per field. $this->errors[$fieldId] = array_key_exists($fieldId, $this->errors) ? $this->errors[$fieldId] . ' ' . $value : $value; } return false; } $attrValueModel->save(); if ($wantToUpdateAttrMetadata && !$this->wantToUpdateMetadata) { // we didn't update the parent's metadata. But a custom attribute value has changed, so it makes sense to update it now. $this->wantToUpdateMetadata = true; $this->set_metadata(); $this->validate(new Validation($this->as_array()), true); } $this->nestedChildModelIds[] = $attrValueModel->get_submitted_ids(); return true; }
/** * Method to apply a parameterised test on a vague date conversion to a string */ protected function checkConvertVagueDate($from, $to, $type, $expected) { $fromDate = $from ? new DateTime($from) : null; $toDate = $to ? new DateTime($to) : null; $vd = array($fromDate, $toDate, $type); $s = vague_date::vague_date_to_string($vd); $this->assertEquals($expected, $s, 'Failed converting vague date (dates) to ' . $expected); // test using strings rather than date objects $fromStr = $from ? $from : ''; $toStr = $to ? $to : ''; $vd = array($fromStr, $toStr, $type); $s = vague_date::vague_date_to_string($vd); $this->assertEquals($expected, $s, 'Failed converting vague date (strings) to ' . $expected); }
private function getFilterClause($field, $datatype, &$operator, &$value) { if ($datatype == 'text') { // quote text values and replace * wildcards with SQL friendly ones. $value = str_replace('*', '%', $value); //by default add a wildcard to the end if (substr($value, -1) !== '%') { $value .= '%'; } $operator = 'ILIKE'; } else { $operator = '='; } if ($datatype !== 'text' && $datatype !== 'date') { //strip spaces so the user can be more flexible about spaces they enter //Note: Don't use this for text filter as spacing might be important for the search. //For dates, spacing will be handled by the vague date engine $value = str_replace(" ", "", $value); //apart from text and date values we handle the case where the user enters a range e.g. 1-3 // @todo: is there an i18n consideration here with the .? if (preg_match('/(?P<from>\\d+(\\.\\d+)?)(-|to)(?<to>\\d+(\\.\\d+)?)/', $value, $matches)) { return "{$field} BETWEEN " . $matches['from'] . " AND " . $matches['to']; } // support <, <=, >, >= operators if (preg_match('/(?P<op>(>|<|>=|<=))(?P<val>\\d+(\\.\\d+)?)/', $value, $matches)) { return "{$field} " . $matches['op'] . " " . $matches['val']; } } if ($datatype == 'text') { // ensure value is escaped for apostrophes $value = pg_escape_string($value); // quote text and date values $value = "'" . $value . "'"; } if ($datatype != 'date') { return "{$field} {$operator} {$value}"; } else { $vaguedate = vague_date::string_to_vague_date($value); return "({$field} >= '" . $vaguedate[0] . "' AND {$field} <= '" . $vaguedate[1] . "')"; } }
/** * Hook into the task scheduler. Runs a query to find all comments and verification status updates that need * to be notified back to the recorder of a record. * @param string $last_run_date Date & time that this module was last run. * @throws \Kohana_Database_Exception */ function notify_verifications_and_comments_scheduled_task($last_run_date) { if (!$last_run_date) { // first run, so get all records changed in last day. Query will automatically gradually pick up the rest. $last_run_date = date('Y-m-d', time() - 60 * 60 * 24 * 50); } $db = new Database(); $notifications = postgreSQL::selectVerificationAndCommentNotifications($last_run_date, $db); foreach ($notifications as $notification) { $vd = array($notification->date_start, $notification->date_end, $notification->date_type); $date = vague_date::vague_date_to_string($vd); if (empty($notification->comment)) { switch ($notification->record_status . (empty($notification->record_substatus) ? '' : $notification->record_substatus)) { case 'V': $action = 'accepted'; break; case 'V1': $action = 'accepted as correct'; break; case 'V2': $action = 'accepted as correct'; break; case 'C3': $action = 'plausible'; break; case 'D': $action = 'queried'; break; case 'R': $action = 'not accepted'; break; case 'R4': $action = 'not accepted as unable to verify'; break; case 'R5': $action = 'not accepted as incorrect'; break; default: $action = 'amended'; } $comment = "The record of {$notification->taxon} at {$notification->public_entered_sref} on {$date} was {$action}."; } else { if ($notification->auto_generated === 't' && substr($notification->generated_by, 0, 12) === 'data_cleaner' && $notification->record_owner === 't') { $comment = "The following message was attached to your record of {$notification->taxon} at {$notification->public_entered_sref} on {$date} " . "when it was checked using the <a target=\"_blank\" href=\"http://www.nbn.org.uk/Tools-Resources/Recording-Resources/NBN-Record-Cleaner.aspx\" target=\"_blank\">" . "NBN Record Cleaner</a>. This does not mean the record is incorrect or is being disputed; the information below is merely a flag against the record that " . "might provide useful information for recording and verification purposes."; } elseif ($notification->verified_on > $last_run_date && $notification->record_status !== 'I' && $notification->record_status !== 'T' && $notification->record_status !== 'C') { if ($notification->record_owner === 't') { $comment = "Your record of {$notification->taxon} at {$notification->public_entered_sref} on {$date} was examined by an expert."; } else { $comment = "A record of {$notification->taxon} at {$notification->public_entered_sref} on {$date} which you'd previously commented on was examined by an expert."; } } elseif ($notification->record_owner === 't') { $comment = "A comment was added to your record of {$notification->taxon} at {$notification->public_entered_sref} on {$date}."; } else { $comment = "A reply was added to the record of {$notification->taxon} at {$notification->public_entered_sref} on {$date} which you've previously commented on."; } $comment .= "<br/><em>{$notification->comment}</em>"; } $theNotificationToInsert = array('source' => 'Verifications and comments', 'source_type' => $notification->source_type, 'data' => json_encode(array('username' => $notification->username, 'occurrence_id' => $notification->id, 'comment' => $comment, 'taxon' => $notification->taxon, 'date' => $date, 'entered_sref' => $notification->public_entered_sref, 'auto_generated' => $notification->auto_generated, 'record_status' => $notification->record_status, 'record_substatus' => $notification->record_substatus, 'updated_on' => $notification->updated_on)), 'linked_id' => $notification->id, 'user_id' => $notification->notify_user_id, 'digest_mode' => 'N', 'source_detail' => $notification->source_detail); $db->insert('notifications', $theNotificationToInsert); } echo count($notifications) . ' notifications generated<br/>'; }
private function addArrayToEmailTable($occurrenceId, $array, &$emailContent) { $excludedFields = array('date_end', 'date_type', 'Email me a copy of the record', 'CMS Username', 'CMS User ID', 'Email', 'Happy for Contact?'); foreach ($array[$occurrenceId] as $field => $value) { if ($field == 'date_start') { $value = vague_date::vague_date_to_string(array($array[$occurrenceId]->date_start, $array[$occurrenceId]->date_end, $array[$occurrenceId]->date_type)); $field = 'date'; } if (!empty($value) && !in_array($field, $excludedFields)) { $emailContent .= "<tr><td>{$field}</td><td>{$value}</td></tr>"; } } }
/** * Before submission, map vague dates to their underlying database fields. */ protected function preSubmit() { if (array_key_exists('date', $this->submission['fields'])) { $vague_date = vague_date::string_to_vague_date($this->submission['fields']['date']['value']); $this->submission['fields']['date_start']['value'] = $vague_date[0]; $this->submission['fields']['date_end']['value'] = $vague_date[1]; $this->submission['fields']['date_type']['value'] = $vague_date[2]; } // Allow a sample to be submitted with a spatial ref and system but no Geom. If so we // can work out the Geom if (array_key_exists('entered_sref', $this->submission['fields']) && array_key_exists('entered_sref_system', $this->submission['fields']) && !(array_key_exists('geom', $this->submission['fields']) && $this->submission['fields']['geom']['value']) && $this->submission['fields']['entered_sref']['value'] && $this->submission['fields']['entered_sref_system']['value']) { try { $this->submission['fields']['geom']['value'] = spatial_ref::sref_to_internal_wkt($this->submission['fields']['entered_sref']['value'], $this->submission['fields']['entered_sref_system']['value']); } catch (Exception $e) { $this->errors['entered_sref'] = $e->getMessage(); } } return parent::presubmit(); }