chg: [sighting] Sighting statistics

pull/6578/head
Jakub Onderka 2020-11-11 09:00:50 +01:00
parent d23dcb5bc9
commit 1f258ebe66
9 changed files with 318 additions and 241 deletions

View File

@ -1653,7 +1653,6 @@ class AttributesController extends AppController
return [[], []];
}
$sightingsData = array();
$this->Feed = ClassRegistry::init('Feed');
$this->loadModel('Sighting');
@ -1684,11 +1683,6 @@ class AttributesController extends AppController
$attributes[$k]['Attribute']['AttributeTag'] = $attributes[$k]['AttributeTag'];
$attributes[$k]['Attribute'] = $this->Attribute->Event->massageTags($this->Auth->user(), $attributes[$k]['Attribute'], 'Attribute', $excludeGalaxy = false, $cullGalaxyTags = true);
unset($attributes[$k]['AttributeTag']);
$sightingsData = array_merge(
$sightingsData,
$this->Sighting->attachToEvent($attribute, $user, $attribute, $extraConditions = false)
);
}
// Fetch correlations in one query
@ -1708,8 +1702,7 @@ class AttributesController extends AppController
$attributes[$k]['Attribute']['RelatedAttribute'] = $correlations[$attribute['Attribute']['id']];
}
}
$sightingsData = $this->Attribute->Event->getSightingData(array('Sighting' => $sightingsData));
$sightingsData = $this->Sighting->attributesStatistics($attributes, $user);
return array($attributes, $sightingsData);
}

View File

@ -638,8 +638,6 @@ class DecayingModelController extends AppController
);
$attributes = $this->paginate($this->User->Event->Attribute);
// attach sightings and massage tags
$sightingsData = array();
if (!empty($options['overrideLimit'])) {
$overrideLimit = true;
} else {
@ -658,10 +656,6 @@ class DecayingModelController extends AppController
unset($attributes[$k]['Attribute']['AttributeTag'][$k2]);
}
}
$sightingsData = array_merge(
$sightingsData,
$this->Sighting->attachToEvent($attribute, $this->Auth->user(), $attributes[$k]['Attribute']['id'], $extraConditions = false)
);
if (!empty($params['includeEventTags'])) {
$tagConditions = array('EventTag.event_id' => $attribute['Event']['id']);
if (empty($params['includeAllTags'])) {
@ -694,8 +688,7 @@ class DecayingModelController extends AppController
}
}
}
$sightingsData = $this->User->Event->getSightingData(array('Sighting' => $sightingsData));
$this->set('sightingsData', $sightingsData);
$this->set('sightingsData', $this->Sighting->attributesStatistics($attributes, $this->Auth->user()));
$this->set('attributes', $attributes);
$this->set('attrDescriptions', $this->User->Event->Attribute->fieldDescriptions);
$this->set('typeDefinitions', $this->User->Event->Attribute->typeDefinitions);

View File

@ -1093,6 +1093,7 @@ class EventsController extends AppController
$conditions['includeGranularCorrelations'] = 1;
$conditions['includeEventCorrelations'] = false;
$conditions['noEventReports'] = true; // event reports for view are loaded dynamically
$conditions['noSightings'] = true;
if (!empty($filters['includeRelatedTags'])) {
$this->set('includeRelatedTags', 1);
$conditions['includeRelatedTags'] = 1;
@ -1174,7 +1175,8 @@ class EventsController extends AppController
$filters['sort'] = 'timestamp';
$filters['direction'] = 'desc';
}
$sightingsData = $this->Event->getSightingData($event);
$this->loadModel('Sighting');
$sightingsData = $this->Sighting->eventsStatistic([$event], $this->Auth->user());
$this->set('sightingsData', $sightingsData);
$params = $this->Event->rearrangeEventForView($event, $filters, $all, $sightingsData);
if (!empty($filters['includeSightingdb']) && Configure::read('Plugin.Sightings_sighting_db_enable')) {
@ -1430,7 +1432,8 @@ class EventsController extends AppController
}
}
unset($modificationMap);
$sightingsData = $this->Event->getSightingData($event);
$this->loadModel('Sighting');
$sightingsData = $this->Sighting->eventsStatistic([$event], $this->Auth->user());
$this->set('sightingsData', $sightingsData);
$params = $this->Event->rearrangeEventForView($event, $filters, false, $sightingsData);
if (!empty($filters['includeSightingdb']) && Configure::read('Plugin.Sightings_sighting_db_enable')) {
@ -1539,6 +1542,7 @@ class EventsController extends AppController
} else {
$conditions['includeAllTags'] = true;
$conditions['noEventReports'] = true; // event reports for view are loaded dynamically
$conditions['noSightings'] = true;
}
$deleted = 0;
if (isset($this->params['named']['deleted'])) {

View File

@ -321,65 +321,21 @@ class SightingsController extends AppController
$this->loadModel('Event');
$id = $this->Sighting->explodeIdList($id);
if ($context === 'attribute') {
$attribute_id = $id;
$object = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0), 'flatten' => 1));
if (empty($object)) {
$objects = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0), 'flatten' => 1));
if (empty($objects)) {
throw new MethodNotAllowedException('Invalid object.');
}
$eventIds = array();
foreach ($object as $v) {
$eventIds[] = $v['Attribute']['event_id'];
}
$events = $this->Event->fetchSimpleEvents($this->Auth->user(), ['conditions' => ['id' => $eventIds]]);
} else {
$attribute_id = false;
$statistics = $this->Sighting->attributesStatistics($objects, $this->Auth->user(), true);
} elseif ($context === 'event') {
// let's set the context to event here, since we reuse the variable later on for some additional lookups.
// Passing $context = 'org' could have interesting results otherwise...
$events = $this->Event->fetchSimpleEvents($this->Auth->user(), ['conditions' => ['id' => $id]]);
$statistics = $this->Sighting->eventsStatistic($events, $this->Auth->user(), true);
} else {
throw new MethodNotAllowedException('Invalid context');
}
if (empty($events)) {
throw new MethodNotAllowedException('Invalid object.');
}
$raw = array();
foreach ($events as $event) {
$raw = array_merge($raw, $this->Sighting->attachToEvent($event, $this->Auth->user(), $attribute_id));
}
$results = array();
foreach ($raw as $sighting) {
$results[$sighting['type']][date('Ymd', $sighting['date_sighting'])][] = $sighting;
}
unset($raw);
$dataPoints = array();
$startDate = date('Ymd');
$range = date('Ymd', $this->Sighting->getMaximumRange());
foreach ($results as $type => $data) {
foreach ($data as $date => $sighting) {
if ($date < $startDate) {
if ($date >= $range) {
$startDate = $date;
}
}
$temp = array();
foreach ($sighting as $sightingInstance) {
if (!isset($sightingInstance['Organisation']['name'])) {
$org = 'Anonymised';
} else {
$org = $sightingInstance['Organisation']['name'];
}
$temp[$org] = isset($temp[$org]) ? $temp[$org] + 1 : 1;
}
$dataPoints[$date][$type] = array('count' => count($sighting), 'details' => $temp);
}
}
$startDate = date('Ymd', strtotime("-3 days", strtotime($startDate)));
$tsv = 'date\tSighting\tFalse-positive\n';
for ($i = $startDate; $i < date('Ymd') + 1; $i++) {
if (checkdate(substr($i, 4, 2), substr($i, 6, 2), substr($i, 0, 4))) {
$tsv .= $i . '\t' . (isset($dataPoints[$i][0]['count']) ? $dataPoints[$i][0]['count'] : 0) . '\t' . (isset($dataPoints[$i][1]['count']) ? $dataPoints[$i][1]['count'] : 0) . '\n';
}
}
$this->set('tsv', $tsv);
$this->set('results', $results);
$this->set('csv', $statistics['csv']['all']);
$this->layout = 'ajax';
$this->render('ajax/view_sightings');
}

View File

@ -5772,88 +5772,6 @@ class Event extends AppModel
);
}
public function getSightingData(array $event)
{
if (empty($event['Sighting'])) {
return ['data' => [], 'csv' => []];
}
$this->Sighting = ClassRegistry::init('Sighting');
$sightingsData = array();
$sparklineData = array();
$startDates = array();
$range = $this->Sighting->getMaximumRange();
foreach ($event['Sighting'] as $sighting) {
$type = $this->Sighting->type[$sighting['type']];
if (!isset($sightingsData[$sighting['attribute_id']][$type])) {
$sightingsData[$sighting['attribute_id']][$type] = array('count' => 0);
}
$sightingsData[$sighting['attribute_id']][$type]['count']++;
$orgName = isset($sighting['Organisation']['name']) ? $sighting['Organisation']['name'] : 'Others';
if (!isset($sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName])) {
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName] = array('count' => 1, 'date' => $sighting['date_sighting']);
} else {
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['count']++;
if ($sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['date'] < $sighting['date_sighting']) {
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['date'] = $sighting['date_sighting'];
}
}
if ($sighting['type'] !== '0') {
continue;
}
if (!isset($startDates[$sighting['attribute_id']]) || $startDates[$sighting['attribute_id']] > $sighting['date_sighting']) {
if ($sighting['date_sighting'] >= $range) {
$startDates[$sighting['attribute_id']] = $sighting['date_sighting'];
}
}
if (!isset($startDates['event']) || $startDates['event'] > $sighting['date_sighting']) {
if ($sighting['date_sighting'] >= $range) {
$startDates['event'] = $sighting['date_sighting'];
}
}
$date = date("Y-m-d", $sighting['date_sighting']);
if (!isset($sparklineData[$sighting['attribute_id']][$date])) {
$sparklineData[$sighting['attribute_id']][$date] = 1;
} else {
$sparklineData[$sighting['attribute_id']][$date]++;
}
if (!isset($sparklineData['event'][$date])) {
$sparklineData['event'][$date] = 1;
} else {
$sparklineData['event'][$date]++;
}
}
$csv = array();
$today = strtotime(date('Y-m-d', time()));
foreach ($startDates as $k => $v) {
$startDates[$k] = date('Y-m-d', $v);
}
foreach ($sparklineData as $aid => $data) {
if (!isset($startDates[$aid])) {
continue;
}
$startDate = $startDates[$aid];
if (strtotime($startDate) < $range) {
$startDate = date('Y-m-d');
}
$startDate = date('Y-m-d', strtotime("-3 days", strtotime($startDate)));
$sighting = $data;
$csv[$aid] = 'Date,Close\n';
for ($date = $startDate; strtotime($date) <= $today; $date = date('Y-m-d', strtotime("+1 day", strtotime($date)))) {
if (isset($sighting[$date])) {
$csv[$aid] .= $date . ',' . $sighting[$date] . '\n';
} else {
$csv[$aid] .= $date . ',0\n';
}
}
}
return array(
'data' => $sightingsData,
'csv' => $csv
);
}
public function cacheSgids($user, $useCache = false)
{
if ($useCache && isset($this->assetCache['sgids'])) {

View File

@ -1,6 +1,5 @@
<?php
App::uses('AppModel', 'Model');
App::uses('RandomTool', 'Tools');
App::uses('TmpFileTool', 'Tools');
/**
@ -9,6 +8,15 @@ App::uses('TmpFileTool', 'Tools');
*/
class Sighting extends AppModel
{
const ONE_DAY = 86400; // in seconds
// Possible values of `Plugin.Sightings_policy` setting
const SIGHTING_POLICY_EVENT_OWNER = 0,
SIGHTING_POLICY_SIGHTING_REPORTER = 1,
SIGHTING_POLICY_EVERYONE = 2;
private $orgCache = [];
public $useTable = 'sightings';
public $recursive = -1;
@ -169,17 +177,7 @@ class Sighting extends AppModel
}
// if sighting policy == 1, the user can only see the sighting if they've sighted something in the event once
if (Configure::read('Plugin.Sightings_policy') == 1) {
$temp = $this->find(
'first',
array(
'recursive' => -1,
'conditions' => array(
'Sighting.event_id' => $sighting['Sighting']['event_id'],
'Sighting.org_id' => $user['org_id']
)
)
);
if (empty($temp)) {
if (!$this->isReporter($sighting['Sighting']['event_id'], $user['org_id'])) {
return array();
}
}
@ -207,6 +205,205 @@ class Sighting extends AppModel
return $result;
}
/**
* @param array $attributes Attribute must contain Event
* @param array $user
* @param bool $csvWithFalsePositive
* @return array[]
*/
public function attributesStatistics(array $attributes, array $user, $csvWithFalsePositive = false)
{
if (empty($attributes)) {
return ['data' => [], 'csv' => []];
}
$sightingsPolicy = $this->sightingsPolicy();
$conditions = [];
foreach ($attributes as $attribute) {
$attributeConditions = ['Sighting.attribute_id' => $attribute['Attribute']['id']];
$ownEvent = $user['Role']['perm_site_admin'] || $attribute['Event']['org_id'] == $user['org_id'];
if (!$ownEvent) {
if ($sightingsPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
$attributeConditions['Sighting.org_id'] = $user['org_id'];
} else if ($sightingsPolicy === self::SIGHTING_POLICY_SIGHTING_REPORTER) {
if (!$this->isReporter($attribute['Event']['id'], $user['org_id'])) {
continue; // skip attribute
}
}
}
$conditions['OR'][] = $attributeConditions;
}
$groupedSightings = $this->fetchGroupedSightings($conditions, $user);
return $this->generateStatistics($groupedSightings, $csvWithFalsePositive);
}
/**
* @param array $events
* @param array $user
* @param bool $csvWithFalsePositive
* @return array
*/
public function eventsStatistic(array $events, array $user, $csvWithFalsePositive = false)
{
if (empty($events)) {
return ['data' => [], 'csv' => []];
}
$sightingPolicy = $this->sightingsPolicy();
$conditions = [];
foreach ($events as $event) {
$eventCondition = ['Sighting.event_id' => $event['Event']['id']];
$ownEvent = $user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id'];
if (!$ownEvent) {
if ($sightingPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
$eventCondition['Sighting.org_id'] = $user['org_id'];
} else if ($sightingPolicy === self::SIGHTING_POLICY_SIGHTING_REPORTER) {
if (!$this->isReporter($event['Event']['id'], $user['org_id'])) {
continue;
}
}
}
$conditions['OR'][] = $eventCondition;
}
$groupedSightings = $this->fetchGroupedSightings($conditions, $user);
return $this->generateStatistics($groupedSightings, $csvWithFalsePositive);
}
/**
* @param array $conditions
* @param array $user
* @return array
*/
private function fetchGroupedSightings(array $conditions, array $user)
{
if (empty($conditions)) {
return [];
}
// Returns data in `Y-m-d` format
$this->virtualFields['date_sighting'] = 'DATE(FROM_UNIXTIME(date_sighting))'; // TODO: Probably just for MySQL
$this->virtualFields['sighting_count'] = 'COUNT(id)';
$groupedSightings = $this->find('all', array(
'conditions' => $conditions,
'fields' => ['org_id', 'attribute_id', 'type', 'date_sighting', 'date_sighting as last_timestamp', 'sighting_count'],
'recursive' => -1,
'group' => ['org_id', 'attribute_id', 'type', 'date_sighting'],
'order' => ['date_sighting'], // from oldest
));
unset($this->virtualFields['date_sighting'], $this->virtualFields['sighting_count']);
return $this->attachOrgToSightings($groupedSightings, $user, false);
}
/**
* @param array $groupedSightings
* @param bool $csvWithFalsePositive
* @return array[]
*/
private function generateStatistics(array $groupedSightings, $csvWithFalsePositive = false)
{
$sightingsData = [];
$sparklineData = [];
$range = $this->getMaximumRange();
foreach ($groupedSightings as $sighting) {
$type = $this->type[$sighting['type']];
$orgName = isset($sighting['Organisation']['name']) ? $sighting['Organisation']['name'] : __('Others');
$count = (int)$sighting['sighting_count'];
$inRange = strtotime($sighting['date_sighting']) >= $range;
foreach ([$sighting['attribute_id'], 'all'] as $needle) {
if (!isset($sightingsData[$needle][$type])) {
$sightingsData[$needle][$type] = ['count' => 0, 'orgs' => []];
}
$ref = &$sightingsData[$needle][$type];
$ref['count'] += $count;
if (!isset($ref['orgs'][$orgName])) {
$ref['orgs'][$orgName] = ['count' => $count, 'date' => $sighting['last_timestamp']];
} else {
$ref['orgs'][$orgName]['count'] += $count;
$ref['orgs'][$orgName]['date'] = $sighting['last_timestamp'];
}
if ($inRange) {
if (isset($sparklineData[$needle][$sighting['date_sighting']][$type])) {
$sparklineData[$needle][$sighting['date_sighting']][$type] += $count;
} else {
$sparklineData[$needle][$sighting['date_sighting']][$type] = $count;
}
}
}
}
$todayString = date('Y-m-d');
$today = strtotime($todayString);
// If nothing found, generate default "empty" CSV for 'all'
if (!isset($sparklineData['all'])) {
$sparklineData['all'][$todayString] = null;
}
$csv = [];
foreach ($sparklineData as $object => $data) {
$startDate = key($data); // oldest date for sparkline
$startDate = strtotime($startDate) - (self::ONE_DAY * 3);
$csvForObject = $csvWithFalsePositive ? 'Date,Sighting,False-positive\n' : 'Date,Close\n';
for ($date = $startDate; $date <= $today; $date += self::ONE_DAY) {
$dateAsString = date('Y-m-d', $date);
$csvForObject .= $dateAsString . ',' . (isset($data[$dateAsString]['sighting']) ? $data[$dateAsString]['sighting'] : '0');
if ($csvWithFalsePositive) {
$csvForObject .= ',' . (isset($data[$dateAsString]['false-positive']) ? $data[$dateAsString]['false-positive'] : '0');
}
$csvForObject .= '\n';
}
$csv[$object] = $csvForObject;
}
return ['data' => $sightingsData, 'csv' => $csv];
}
/**
* @param array $sightings
* @param array $user
* @param false $forSync
* @return array
*/
private function attachOrgToSightings(array $sightings, array $user, $forSync = false)
{
$showOrg = Configure::read('MISP.showorg');
$anonymise = Configure::read('Plugin.Sightings_anonymise');
$anonymiseAs = Configure::read('Plugin.Sightings_anonymise_as');
$anonOrg = null;
if ($forSync && !empty($anonymiseAs)) {
$anonOrg = $this->getOrganisationById($anonymiseAs);
}
foreach ($sightings as $k => $sighting) {
$sighting = $sighting['Sighting'];
if ($showOrg && $sighting['org_id']) {
$sighting['Organisation'] = $this->getOrganisationById($sighting['org_id']);
}
if ($sighting['org_id'] != $user['org_id'] && ($anonymise || !empty($anonOrg))) {
if (empty($anonOrg)) {
unset($sighting['org_id']);
unset($sighting['Organisation']);
} else {
$sighting['org_id'] = $anonOrg['Organisation']['id'];
$sighting['Organisation'] = $anonOrg['Organisation'];
}
}
$sightings[$k] = $sighting;
}
$this->orgCache = []; // clear org cache
return $sightings;
}
/**
* @param array $event
* @param array $user
@ -217,8 +414,6 @@ class Sighting extends AppModel
*/
public function attachToEvent(array $event, array $user, $attribute = null, $extraConditions = false, $forSync = false)
{
$ownEvent = $user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id'];
$contain = [];
$conditions = array('Sighting.event_id' => $event['Event']['id']);
if (isset($attribute['Attribute']['id'])) {
@ -234,32 +429,20 @@ class Sighting extends AppModel
$contain['Attribute'] = ['fields' => 'Attribute.uuid'];
}
if (!$ownEvent && (!Configure::read('Plugin.Sightings_policy') || Configure::read('Plugin.Sightings_policy') == 0)) {
$conditions['Sighting.org_id'] = $user['org_id'];
$ownEvent = $user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id'];
if (!$ownEvent) {
$sightingPolicy = $this->sightingsPolicy();
if ($sightingPolicy === self::SIGHTING_POLICY_EVENT_OWNER) {
$conditions['Sighting.org_id'] = $user['org_id'];
} elseif ($sightingPolicy === self::SIGHTING_POLICY_SIGHTING_REPORTER) {
if (!$this->isReporter($event['Event']['id'], $user['org_id'])) {
return array();
}
}
}
if ($extraConditions !== false) {
$conditions['AND'] = $extraConditions;
}
if (Configure::read('MISP.showorg')) {
$contain['Organisation'] = array('fields' => array('Organisation.id', 'Organisation.uuid', 'Organisation.name'));
}
// Sighting reporters setting
// If the event has any sightings for the user's org, then the user is a sighting reporter for the event too.
// This means that he /she has access to the sightings data contained within
if (!$ownEvent && Configure::read('Plugin.Sightings_policy') == 1) {
$temp = $this->find('first', array(
'recursive' => -1,
'conditions' => array(
'Sighting.event_id' => $event['Event']['id'],
'Sighting.org_id' => $user['org_id'],
)
));
if (empty($temp)) {
return array();
}
}
$sightings = $this->find('all', array(
'conditions' => $conditions,
'recursive' => -1,
@ -268,46 +451,15 @@ class Sighting extends AppModel
if (empty($sightings)) {
return array();
}
$anonymise = Configure::read('Plugin.Sightings_anonymise');
$anonymiseAs = Configure::read('Plugin.Sightings_anonymise_as');
$anonOrg = null;
if ($forSync && !empty($anonymiseAs)) {
$this->Organisation = ClassRegistry::init('Organisation');
$anonOrg = $this->Organisation->find('first', [
'recursive' => -1,
'conditions' => ['Organisation.id' => $anonymiseAs],
'fields' => ['Organisation.id', 'Organisation.uuid', 'Organisation.name']
]);
}
foreach ($sightings as $k => $sighting) {
if (
($sighting['Sighting']['org_id'] == 0 && !empty($sighting['Organisation'])) ||
$anonymise || !empty($anonOrg)
) {
if ($sighting['Sighting']['org_id'] != $user['org_id']) {
if (empty($anonOrg)) {
unset($sighting['Sighting']['org_id']);
unset($sighting['Organisation']);
} else {
$sighting['Sighting']['org_id'] = $anonOrg['Organisation']['id'];
$sighting['Organisation'] = $anonOrg['Organisation'];
}
}
}
// rearrange it to match the event format of fetchevent
if (isset($sighting['Organisation'])) {
$sighting['Sighting']['Organisation'] = $sighting['Organisation'];
}
// zeroq: add attribute UUID to sighting to make synchronization easier
if (isset($sighting['Attribute']['uuid'])) {
$sighting['Sighting']['attribute_uuid'] = $sighting['Attribute']['uuid'];
} else {
$sighting['Sighting']['attribute_uuid'] = $attribute['Attribute']['uuid'];
}
$sightings[$k] = $sighting['Sighting'] ;
$sightings[$k] = $sighting;
}
return $sightings;
return $this->attachOrgToSightings($sightings, $user, $forSync);
}
public function saveSightings($id, $values, $timestamp, $user, $type = false, $source = false, $sighting_uuid = false, $publish = false, $saveOnBehalfOf = false)
@ -825,4 +977,65 @@ class Sighting extends AppModel
$rangeInDays = (!empty($rangeInDays) && is_numeric($rangeInDays)) ? $rangeInDays : 365;
return strtotime("-$rangeInDays days");
}
/**
* Sighting reporters setting
* If the event has any sightings for the user's org, then the user is a sighting reporter for the event too.
* This means that he /she has access to the sightings data contained within.
*
* @param int $eventId
* @param int $orgId
* @return bool
*/
private function isReporter($eventId, $orgId)
{
return (bool)$this->find('first', array(
'recursive' => -1,
'callbacks' => false,
'fields' => ['Sighting.id'],
'conditions' => array(
'Sighting.event_id' => $eventId,
'Sighting.org_id' => $orgId,
)
));
}
/**
* Reduce memory usage by not fetching organisation object for every sighting but just once. Then organisation
* object will be deduplicated in memory.
*
* @param int $orgId
* @return array
*/
private function getOrganisationById($orgId)
{
if (isset($this->orgCache[$orgId])) {
return $this->orgCache[$orgId];
}
if (!isset($this->Organisation)) {
$this->Organisation = ClassRegistry::init('Organisation');
}
$org = $this->Organisation->find('first', [
'recursive' => -1,
'conditions' => ['Organisation.id' => $orgId],
'fields' => ['Organisation.id', 'Organisation.uuid', 'Organisation.name']
]);
if (!empty($org)) {
$org = $org['Organisation'];
}
$this->orgCache[$orgId] = $org;
return $this->orgCache[$orgId];
}
/**
* @return int
*/
private function sightingsPolicy()
{
$policy = Configure::read('Plugin.Sightings_policy');
if ($policy === null) { // default policy
return self::SIGHTING_POLICY_EVENT_OWNER;
}
return (int)$policy;
}
}

View File

@ -1,23 +1,22 @@
<?php
$ownOrgSightingsCount = 0;
if (isset($event['Sighting'])) {
$meOrgId = $this->get('me')['org_id'];
foreach ($event['Sighting'] as $sighting) {
if (isset($sighting['org_id']) && $sighting['org_id'] == $meOrgId) {
++$ownOrgSightingsCount;
}
}
$userOrgName = $this->get('me')['Organisation']['name'];
$totalCount = 0;
$ownCount = 0;
foreach ($sightingsData as $data) {
$totalCount += $data['count'];
$ownCount += isset($data['orgs'][$userOrgName]['count']) ? $data['orgs'][$userOrgName]['count'] : 0;
}
echo sprintf(
'%s (%s) %s %s',
sprintf(
'<span id="eventSightingCount" class="bold sightingsCounter">%s</span>',
count($event['Sighting'])
$totalCount
),
sprintf(
'<span id="eventOwnSightingCount" class="green bold sightingsCounter">%s</span>',
$ownOrgSightingsCount
$ownCount
),
(Configure::read('Plugin.Sightings_policy')) ? '' : __('- restricted to own organisation only.'),
sprintf(

View File

@ -225,13 +225,14 @@
'element' => '/Events/View/eventSightingValue',
'element_params' => array(
'event' => $event,
'sightingsData' => isset($sightingsData['data']['all']) ? $sightingsData['data']['all'] : [],
)
);
if (!empty($sightingsData['csv']['event'])) {
if (isset($sightingsData['data']['all'])) {
$table_data[] = array(
'key' => __('Activity'),
'element' => 'sparkline',
'element_params' => array('scope' => 'event', 'id' => $event['Event']['id'], 'csv' => $sightingsData['csv']['event'])
'element_params' => array('scope' => 'event', 'id' => $event['Event']['id'], 'csv' => $sightingsData['csv']['all'])
);
}
if (!empty($delegationRequest)) {

View File

@ -4,7 +4,7 @@
?>
<div id="graphContent" class="graphContent"></div>
<script>
var myData = "<?php echo $tsv; ?>";
var myData = "<?php echo $csv; ?>";
var colours = {
'Sighting': 'blue',
@ -20,7 +20,7 @@
width = 980 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var parseDate = d3.time.format("%Y-%m-%d").parse;
var x = d3.time.scale()
.range([0, width]);
@ -53,14 +53,14 @@
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = d3.tsv.parse(myData);
var data = d3.csv.parse(myData);
color.domain(d3.keys(data[0]).filter(function(key) {
return key !== "date";
return key !== "Date";
}));
data.forEach(function(d) {
d.date = parseDate(d.date);
d.Date = parseDate(d.Date);
});
var sightings = color.domain().map(function(name) {
@ -68,7 +68,7 @@
name: name,
values: data.map(function(d) {
return {
date: d.date,
date: d.Date,
count: +d[name]
};
})
@ -76,7 +76,7 @@
});
x.domain(d3.extent(data, function(d) {
return d.date;
return d.Date;
}));
y.domain([