2020-02-18 15:02:35 +01:00
|
|
|
<?php
|
|
|
|
App::uses('AppModel', 'Model');
|
|
|
|
|
|
|
|
class EventReport extends AppModel
|
|
|
|
{
|
2020-09-17 15:06:55 +02:00
|
|
|
public $actsAs = array(
|
|
|
|
'Containable',
|
|
|
|
'SysLogLogable.SysLogLogable' => array(
|
|
|
|
'userModel' => 'User',
|
|
|
|
'userKey' => 'user_id',
|
|
|
|
'change' => 'full'
|
|
|
|
),
|
2020-10-16 18:42:43 +02:00
|
|
|
'Regexp' => array('fields' => array('value')),
|
2020-09-17 15:06:55 +02:00
|
|
|
);
|
2020-02-18 15:02:35 +01:00
|
|
|
|
|
|
|
public $validate = array(
|
|
|
|
'event_id' => array(
|
|
|
|
'numeric' => array(
|
|
|
|
'rule' => array('numeric')
|
|
|
|
)
|
|
|
|
),
|
|
|
|
'uuid' => array(
|
|
|
|
'uuid' => array(
|
|
|
|
'rule' => array('custom', '/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/'),
|
|
|
|
'message' => 'Please provide a valid UUID'
|
|
|
|
),
|
|
|
|
'unique' => array(
|
|
|
|
'rule' => 'isUnique',
|
|
|
|
'message' => 'The UUID provided is not unique',
|
|
|
|
'required' => 'create'
|
|
|
|
)
|
|
|
|
),
|
|
|
|
'distribution' => array(
|
|
|
|
'rule' => array('inList', array('0', '1', '2', '3', '4', '5')),
|
|
|
|
'message' => 'Options: Your organisation only, This community only, Connected communities, All communities, Sharing group, Inherit event',
|
|
|
|
'required' => true
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2020-09-17 15:06:55 +02:00
|
|
|
public $captureFields = array('uuid', 'name', 'content', 'distribution', 'sharing_group_id', 'timestamp', 'deleted', 'event_id');
|
|
|
|
public $defaultContain = array(
|
|
|
|
'SharingGroup' => array('fields' => array('id', 'name', 'uuid')),
|
|
|
|
'Event' => array(
|
|
|
|
'fields' => array('Event.id', 'Event.orgc_id', 'Event.org_id', 'Event.info', 'Event.user_id', 'Event.date'),
|
|
|
|
'Orgc' => array('fields' => array('Orgc.id', 'Orgc.name')),
|
|
|
|
'Org' => array('fields' => array('Org.id', 'Org.name'))
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2020-02-18 15:02:35 +01:00
|
|
|
public $belongsTo = array(
|
|
|
|
'Event' => array(
|
|
|
|
'className' => 'Event',
|
|
|
|
'foreignKey' => 'event_id'
|
|
|
|
),
|
|
|
|
'SharingGroup' => array(
|
|
|
|
'className' => 'SharingGroup',
|
|
|
|
'foreignKey' => 'sharing_group_id'
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
2020-09-21 11:49:41 +02:00
|
|
|
public function beforeValidate($options = array())
|
2020-02-18 15:02:35 +01:00
|
|
|
{
|
|
|
|
parent::beforeValidate();
|
|
|
|
// generate UUID if it doesn't exist
|
|
|
|
if (empty($this->data['EventReport']['uuid'])) {
|
|
|
|
$this->data['EventReport']['uuid'] = CakeText::uuid();
|
|
|
|
}
|
|
|
|
// generate timestamp if it doesn't exist
|
|
|
|
if (empty($this->data['EventReport']['timestamp'])) {
|
|
|
|
$date = new DateTime();
|
|
|
|
$this->data['EventReport']['timestamp'] = $date->getTimestamp();
|
|
|
|
}
|
|
|
|
if ($this->data['EventReport']['distribution'] != 4) {
|
|
|
|
$this->data['EventReport']['sharing_group_id'] = 0;
|
|
|
|
}
|
|
|
|
// Set defaults for when some of the mandatory fields don't have defaults
|
|
|
|
// These fields all have sane defaults either based on another field, or due to server settings
|
|
|
|
if (!isset($this->data['EventReport']['distribution'])) {
|
|
|
|
$this->data['EventReport']['distribution'] = Configure::read('MISP.default_attribute_distribution');
|
2020-09-21 11:01:36 +02:00
|
|
|
if ($report['EventReport']['distribution'] == 'event') {
|
|
|
|
$report['EventReport']['distribution'] = 5;
|
|
|
|
}
|
2020-02-18 15:02:35 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
/**
|
|
|
|
* captureReport Gets a report then save it
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param array $report
|
|
|
|
* @param int|string $eventId
|
|
|
|
* @return array Any errors preventing the capture
|
|
|
|
*/
|
|
|
|
public function captureReport(array $user, array $report, $eventId)
|
2020-02-18 15:02:35 +01:00
|
|
|
{
|
|
|
|
$this->Log = ClassRegistry::init('Log');
|
2020-09-22 10:05:22 +02:00
|
|
|
if (!isset($report['EventReport'])) {
|
|
|
|
$report = ['EventReport' => $report];
|
|
|
|
}
|
2020-09-17 15:06:55 +02:00
|
|
|
$report['EventReport']['event_id'] = $eventId;
|
2020-02-18 15:02:35 +01:00
|
|
|
$report = $this->captureSG($user, $report);
|
|
|
|
$this->create();
|
2020-09-21 11:01:36 +02:00
|
|
|
$errors = $this->saveAndReturnErrors($report, ['fieldList' => $this->captureFields]);
|
|
|
|
if (!empty($errors)) {
|
|
|
|
$this->Log->createLogEntry($user, 'add', 'EventReport', 0,
|
|
|
|
__('Event Report dropped due to validation for Event report %s failed: %s', $report['EventReport']['uuid'], ' failed: ' . $report['EventReport']['name']),
|
|
|
|
__('Validation errors: %s.%sFull report: %s', json_encode($errors), PHP_EOL, json_encode($report['EventReport']))
|
|
|
|
);
|
2020-02-18 15:02:35 +01:00
|
|
|
}
|
|
|
|
return $errors;
|
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* addReport Add a report
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param array $report
|
|
|
|
* @param int|string $eventId
|
|
|
|
* @return array Any errors preventing the addition
|
|
|
|
*/
|
|
|
|
public function addReport(array $user, array $report, $eventId)
|
2020-09-21 11:14:43 +02:00
|
|
|
{
|
|
|
|
$errors = $this->captureReport($user, $report, $eventId);
|
|
|
|
if (empty($errors)) {
|
|
|
|
$this->Event->unpublishEvent($eventId);
|
|
|
|
}
|
|
|
|
return $errors;
|
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* editReport Edit a report
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param array $report
|
|
|
|
* @param int|string $eventId
|
|
|
|
* @param bool $fromPull
|
|
|
|
* @param bool $nothingToChange
|
|
|
|
* @return array Any errors preventing the edition
|
|
|
|
*/
|
|
|
|
public function editReport(array $user, array $report, $eventId, $fromPull = false, &$nothingToChange = false)
|
2020-02-18 15:02:35 +01:00
|
|
|
{
|
|
|
|
$errors = array();
|
2020-09-17 15:06:55 +02:00
|
|
|
if (!isset($report['EventReport']['uuid'])) {
|
|
|
|
$errors[] = __('Event Report doesn\'t have an UUID');
|
2020-08-04 11:45:54 +02:00
|
|
|
return $errors;
|
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
$report['EventReport']['event_id'] = $eventId;
|
2020-09-17 15:06:55 +02:00
|
|
|
$existingReport = $this->find('first', array(
|
|
|
|
'conditions' => array('EventReport.uuid' => $report['EventReport']['uuid']),
|
|
|
|
'recursive' => -1,
|
|
|
|
));
|
|
|
|
if (empty($existingReport)) {
|
|
|
|
if ($fromPull) {
|
|
|
|
return $this->captureReport($user, $report, $eventId);
|
|
|
|
} else {
|
|
|
|
$errors[] = __('Event Report not found.');
|
|
|
|
return $errors;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($fromPull) {
|
|
|
|
if (isset($report['EventReport']['timestamp'])) {
|
|
|
|
if ($report['EventReport']['timestamp'] <= $existingReport['EventReport']['timestamp']) {
|
|
|
|
$nothingToChange = true;
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2020-08-04 11:45:54 +02:00
|
|
|
unset($report['EventReport']['timestamp']);
|
2020-02-18 15:02:35 +01:00
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
$errors = $this->saveAndReturnErrors($report, ['fieldList' => $this->captureFields], $errors);
|
2020-09-21 11:14:43 +02:00
|
|
|
if (empty($errors)) {
|
|
|
|
$this->Event->unpublishEvent($eventId);
|
|
|
|
}
|
|
|
|
return $errors;
|
2020-02-18 15:02:35 +01:00
|
|
|
}
|
|
|
|
|
2020-09-15 17:12:49 +02:00
|
|
|
/**
|
2020-09-21 11:48:05 +02:00
|
|
|
* deleteReport ACL-aware method to delete the report.
|
2020-09-15 17:12:49 +02:00
|
|
|
*
|
2020-09-21 11:48:05 +02:00
|
|
|
* @param array $user
|
|
|
|
* @param int|string $id
|
2020-09-15 17:12:49 +02:00
|
|
|
* @param bool $hard
|
2020-09-21 11:48:05 +02:00
|
|
|
* @return array Any errors preventing the deletion
|
2020-09-15 17:12:49 +02:00
|
|
|
*/
|
2020-10-09 11:36:34 +02:00
|
|
|
public function deleteReport(array $user, $report, $hard=false)
|
2020-09-15 17:12:49 +02:00
|
|
|
{
|
2020-10-09 11:36:34 +02:00
|
|
|
$report = $this->fetchIfAuthorized($user, $report, 'delete', $throwErrors=true, $full=false);
|
2020-09-21 11:14:43 +02:00
|
|
|
$errors = [];
|
2020-09-15 17:12:49 +02:00
|
|
|
if ($hard) {
|
2020-10-09 11:36:34 +02:00
|
|
|
$deleted = $this->delete($report['EventReport']['id'], true);
|
2020-09-21 11:14:43 +02:00
|
|
|
if (!$deleted) {
|
|
|
|
$errors[] = __('Failed to delete report');
|
|
|
|
}
|
2020-09-15 17:12:49 +02:00
|
|
|
} else {
|
2020-09-17 10:08:10 +02:00
|
|
|
$report['EventReport']['deleted'] = true;
|
2020-09-21 11:14:43 +02:00
|
|
|
$errors = $this->saveAndReturnErrors($report, ['fieldList' => ['deleted']]);
|
|
|
|
}
|
|
|
|
if (empty($errors)) {
|
2020-09-22 08:31:14 +02:00
|
|
|
$this->Event->unpublishEvent($report['EventReport']['event_id']);
|
2020-09-15 17:12:49 +02:00
|
|
|
}
|
2020-09-21 11:14:43 +02:00
|
|
|
return $errors;
|
2020-09-15 17:12:49 +02:00
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* restoreReport ACL-aware method to restore a report.
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param int|string $id
|
|
|
|
* @return array Any errors preventing the restoration
|
|
|
|
*/
|
|
|
|
public function restoreReport(array $user, $id)
|
2020-09-15 17:12:49 +02:00
|
|
|
{
|
2020-09-17 10:08:10 +02:00
|
|
|
$report = $this->fetchIfAuthorized($user, $id, 'edit', $throwErrors=true, $full=false);
|
|
|
|
$report['EventReport']['deleted'] = false;
|
2020-09-21 11:14:43 +02:00
|
|
|
$errors = $this->saveAndReturnErrors($report, ['fieldList' => ['deleted']]);
|
|
|
|
if (empty($errors)) {
|
2020-09-22 08:31:14 +02:00
|
|
|
$this->Event->unpublishEvent($report['EventReport']['event_id']);
|
2020-09-21 11:14:43 +02:00
|
|
|
}
|
|
|
|
return $errors;
|
2020-09-15 17:12:49 +02:00
|
|
|
}
|
|
|
|
|
2020-09-21 11:48:05 +02:00
|
|
|
private function captureSG(array $user, array $report)
|
2020-08-04 11:45:54 +02:00
|
|
|
{
|
2020-09-17 15:06:55 +02:00
|
|
|
$this->Event = ClassRegistry::init('Event');
|
2020-08-04 11:45:54 +02:00
|
|
|
if (isset($report['EventReport']['distribution']) && $report['EventReport']['distribution'] == 4) {
|
|
|
|
$report['EventReport'] = $this->Event->__captureSGForElement($report['EventReport'], $user);
|
|
|
|
}
|
|
|
|
return $report;
|
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* buildACLConditions Generate ACL conditions for viewing the report
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function buildACLConditions(array $user)
|
2020-02-18 15:02:35 +01:00
|
|
|
{
|
|
|
|
$this->Event = ClassRegistry::init('Event');
|
|
|
|
$conditions = array();
|
|
|
|
if (!$user['Role']['perm_site_admin']) {
|
|
|
|
$sgids = $this->Event->cacheSgids($user, true);
|
|
|
|
$eventConditions = $this->Event->createEventConditions($user);
|
|
|
|
$conditions = array(
|
|
|
|
'AND' => array(
|
|
|
|
$eventConditions['AND'],
|
|
|
|
array(
|
|
|
|
'OR' => array(
|
|
|
|
'Event.org_id' => $user['org_id'],
|
|
|
|
'EventReport.distribution' => array('1', '2', '3', '5'),
|
|
|
|
'AND '=> array(
|
|
|
|
'EventReport.distribution' => 4,
|
|
|
|
'EventReport.sharing_group_id' => $sgids,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return $conditions;
|
|
|
|
}
|
|
|
|
|
2020-08-04 11:45:54 +02:00
|
|
|
/**
|
|
|
|
* fetchById Simple ACL-aware method to fetch a report by Id or UUID
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param int|string $reportId
|
2020-09-21 11:48:05 +02:00
|
|
|
* @param bool $throwErrors
|
2020-08-04 11:45:54 +02:00
|
|
|
* @param bool $full
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function simpleFetchById(array $user, $reportId, $throwErrors=true, $full=false)
|
2020-02-18 15:02:35 +01:00
|
|
|
{
|
2020-08-04 11:45:54 +02:00
|
|
|
if (Validation::uuid($reportId)) {
|
|
|
|
$temp = $this->find('first', array(
|
|
|
|
'recursive' => -1,
|
|
|
|
'fields' => array("EventReport.id", "EventReport.uuid"),
|
|
|
|
'conditions' => array("EventReport.uuid" => $reportId)
|
|
|
|
));
|
|
|
|
if (empty($temp)) {
|
|
|
|
if ($throwErrors) {
|
|
|
|
throw new NotFoundException(__('Invalid report'));
|
|
|
|
}
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
$reportId = $temp['EventReport']['id'];
|
|
|
|
} elseif (!is_numeric($reportId)) {
|
|
|
|
if ($throwErrors) {
|
|
|
|
throw new NotFoundException(__('Invalid report'));
|
|
|
|
}
|
|
|
|
return array();
|
2020-02-18 15:02:35 +01:00
|
|
|
}
|
2020-08-04 11:45:54 +02:00
|
|
|
$options = array('conditions' => array("EventReport.id" => $reportId));
|
|
|
|
$report = $this->fetchReports($user, $options, $full=$full);
|
2020-09-18 14:19:59 +02:00
|
|
|
if (!empty($report)) {
|
|
|
|
return $report[0];
|
|
|
|
}
|
|
|
|
if ($throwErrors) {
|
|
|
|
throw new NotFoundException(__('Invalid report'));
|
|
|
|
}
|
|
|
|
return array();
|
2020-02-18 15:02:35 +01:00
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* fetchReports ACL-aware method. Basically find with ACL
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param array $options
|
|
|
|
* @param bool $full
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function fetchReports(array $user, array $options = array(), $full=false)
|
2020-08-04 11:45:54 +02:00
|
|
|
{
|
|
|
|
$params = array(
|
2020-09-21 10:37:18 +02:00
|
|
|
'conditions' => $this->buildACLConditions($user),
|
2020-09-17 15:06:55 +02:00
|
|
|
'contain' => $this->defaultContain,
|
2020-08-04 11:45:54 +02:00
|
|
|
'recursive' => -1
|
|
|
|
);
|
|
|
|
if ($full) {
|
|
|
|
$params['recursive'] = 1;
|
|
|
|
}
|
|
|
|
if (isset($options['fields'])) {
|
|
|
|
$params['fields'] = $options['fields'];
|
|
|
|
}
|
|
|
|
if (isset($options['conditions'])) {
|
|
|
|
$params['conditions']['AND'][] = $options['conditions'];
|
|
|
|
}
|
|
|
|
if (isset($options['group'])) {
|
|
|
|
$params['group'] = empty($options['group']) ? $options['group'] : false;
|
|
|
|
}
|
|
|
|
$reports = $this->find('all', $params);
|
|
|
|
return $reports;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* fetchIfAuthorized Fetches a report and checks if the user has the authorization to perform the requested operation
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param int|string|array $report
|
|
|
|
* @param mixed $authorizations the requested actions to be performed on the report
|
|
|
|
* @param bool $throwErrors Should the function throws excpetion if users is not allowed to perform the action
|
|
|
|
* @param bool $full
|
|
|
|
* @return array The report or an error message
|
|
|
|
*/
|
|
|
|
public function fetchIfAuthorized(array $user, $report, $authorizations, $throwErrors=true, $full=false)
|
|
|
|
{
|
|
|
|
$authorizations = is_array($authorizations) ? $authorizations : array($authorizations);
|
|
|
|
$possibleAuthorizations = array('view', 'edit', 'delete');
|
|
|
|
if (!empty(array_diff($authorizations, $possibleAuthorizations))) {
|
|
|
|
throw new NotFoundException(__('Invalid authorization requested'));
|
|
|
|
}
|
|
|
|
if (isset($report['uuid'])) {
|
|
|
|
$report['EventReport'] = $report;
|
|
|
|
}
|
|
|
|
if (!isset($report['EventReport']['uuid'])) {
|
|
|
|
$report = $this->simpleFetchById($user, $report, $throwErrors=$throwErrors, $full=$full);
|
|
|
|
if (empty($report)) {
|
|
|
|
$message = __('Invalid report');
|
|
|
|
return array('authorized' => false, 'error' => $message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($user['Role']['perm_site_admin']) {
|
|
|
|
return $report;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_array('view', $authorizations) && count($authorizations) == 1) {
|
|
|
|
return $report;
|
|
|
|
} else {
|
|
|
|
if (in_array('edit', $authorizations) || in_array('delete', $authorizations)) {
|
|
|
|
$checkResult = $this->canEditReport($user, $report);
|
|
|
|
if ($checkResult !== true) {
|
|
|
|
if ($throwErrors) {
|
2020-09-15 10:12:58 +02:00
|
|
|
throw new UnauthorizedException($checkResult);
|
2020-08-04 11:45:54 +02:00
|
|
|
}
|
|
|
|
return array('authorized' => false, 'error' => $checkResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $report;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 11:48:05 +02:00
|
|
|
public function canEditReport(array $user, array $report)
|
2020-08-04 11:45:54 +02:00
|
|
|
{
|
2020-09-15 10:12:58 +02:00
|
|
|
if ($user['Role']['perm_site_admin']) {
|
|
|
|
return true;
|
2020-09-18 08:41:32 +02:00
|
|
|
}
|
|
|
|
if (empty($report['Event'])) {
|
|
|
|
return __('Could not find associated event');
|
|
|
|
}
|
|
|
|
if ($report['Event']['orgc_id'] != $user['org_id']) {
|
|
|
|
return __('Only the creator organisation of the event can modify the report');
|
2020-08-04 11:45:54 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2020-09-21 11:48:05 +02:00
|
|
|
|
2020-09-29 10:35:46 +02:00
|
|
|
public function reArrangeReport(array $report)
|
|
|
|
{
|
|
|
|
$rearrangeObjects = array('Event', 'SharingGroup');
|
|
|
|
if (isset($report['EventReport'])) {
|
|
|
|
foreach ($rearrangeObjects as $ro) {
|
|
|
|
if (isset($report[$ro]) && !is_null($report[$ro]['id'])) {
|
|
|
|
$report['EventReport'][$ro] = $report[$ro];
|
|
|
|
}
|
|
|
|
unset($report[$ro]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $report;
|
|
|
|
}
|
|
|
|
|
2020-09-21 11:48:05 +02:00
|
|
|
/**
|
2020-10-07 11:13:30 +02:00
|
|
|
* getProxyMISPElements Extract MISP Elements from an event and make them accessible by their UUID
|
2020-09-21 11:48:05 +02:00
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param int|string $eventid
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getProxyMISPElements(array $user, $eventid)
|
2020-09-10 11:26:23 +02:00
|
|
|
{
|
|
|
|
$event = $this->Event->fetchEvent($user, ['eventid' => $eventid]);
|
|
|
|
if (empty($event)) {
|
|
|
|
throw new NotFoundException(__('Invalid Event'));
|
|
|
|
}
|
|
|
|
$event = $event[0];
|
2020-10-08 09:05:44 +02:00
|
|
|
$parentEventId = $this->Event->fetchSimpleEventIds($user, ['conditions' => [
|
|
|
|
'uuid' => $event['Event']['extends_uuid']
|
|
|
|
]]);
|
2020-10-08 16:59:45 +02:00
|
|
|
$completeEvent = $event;
|
2020-10-08 09:05:44 +02:00
|
|
|
if (!empty($parentEventId)) {
|
|
|
|
$parentEvent = $this->Event->fetchEvent($user, ['eventid' => $parentEventId, 'extended' => true]);
|
|
|
|
if (!empty($parentEvent)) {
|
|
|
|
$parentEvent = $parentEvent[0];
|
2020-10-08 16:59:45 +02:00
|
|
|
$completeEvent = $parentEvent;
|
2020-10-08 09:05:44 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-08 16:59:45 +02:00
|
|
|
$attributes = Hash::combine($completeEvent, 'Attribute.{n}.uuid', 'Attribute.{n}');
|
2020-10-02 10:48:52 +02:00
|
|
|
$this->AttributeTag = ClassRegistry::init('AttributeTag');
|
|
|
|
$allTagNames = Hash::combine($event['EventTag'], '{n}.Tag.name', '{n}.Tag');
|
2020-10-08 16:59:45 +02:00
|
|
|
$attributeTags = Hash::combine($this->AttributeTag->getAttributesTags($completeEvent['Attribute'], true), '{n}.name', '{n}');
|
|
|
|
$parentEventTags = !empty($parentEvent) ? Hash::combine($parentEvent['EventTag'], '{n}.Tag.name', '{n}.Tag') : [];
|
2020-10-08 09:05:44 +02:00
|
|
|
$allTagNames = array_merge($allTagNames, $attributeTags, $parentEventTags);
|
2020-09-10 11:26:23 +02:00
|
|
|
$objects = [];
|
|
|
|
$templateConditions = [];
|
|
|
|
$recordedConditions = [];
|
2020-10-08 16:59:45 +02:00
|
|
|
foreach ($completeEvent['Object'] as $k => $object) {
|
2020-10-01 11:08:30 +02:00
|
|
|
$objects[$object['uuid']] = $object;
|
2020-10-05 14:07:51 +02:00
|
|
|
$objectAttributes = [];
|
|
|
|
foreach ($object['Attribute'] as $i => $objectAttribute) {
|
|
|
|
$objectAttributes[$objectAttribute['uuid']] = $object['Attribute'][$i];
|
|
|
|
$objectAttributes[$objectAttribute['uuid']]['object_uuid'] = $object['uuid'];
|
|
|
|
}
|
|
|
|
$attributes = array_merge($attributes, $objectAttributes);
|
2020-10-02 10:48:52 +02:00
|
|
|
$objectAttributeTags = Hash::combine($this->AttributeTag->getAttributesTags($object['Attribute'], true), '{n}.name', '{n}');
|
|
|
|
$allTagNames = array_merge($allTagNames, $objectAttributeTags);
|
2020-09-10 11:26:23 +02:00
|
|
|
$uniqueCondition = sprintf('%s.%s', $object['template_uuid'], $object['template_version']);
|
|
|
|
if (!isset($recordedConditions[$uniqueCondition])) {
|
|
|
|
$templateConditions['OR'][] = [
|
|
|
|
'ObjectTemplate.uuid' => $object['template_uuid'],
|
|
|
|
'ObjectTemplate.version' => $object['template_version']
|
|
|
|
];
|
|
|
|
$recordedConditions[$uniqueCondition] = true;
|
|
|
|
}
|
|
|
|
}
|
2020-10-08 09:05:44 +02:00
|
|
|
$templateConditions = empty($templateConditions) ? ['ObjectTemplate.id' => 0] : $templateConditions;
|
2020-09-10 11:26:23 +02:00
|
|
|
$this->ObjectTemplate = ClassRegistry::init('ObjectTemplate');
|
|
|
|
$templates = $this->ObjectTemplate->find('all', array(
|
|
|
|
'conditions' => $templateConditions,
|
|
|
|
'recursive' => -1,
|
|
|
|
'contain' => array(
|
|
|
|
'ObjectTemplateElement' => [
|
|
|
|
'order' => ['ui-priority' => 'DESC'],
|
|
|
|
'fields' => ['object_relation', 'type', 'ui-priority']
|
|
|
|
]
|
|
|
|
)
|
|
|
|
));
|
|
|
|
$objectTemplates = [];
|
|
|
|
foreach ($templates as $template) {
|
|
|
|
$objectTemplates[sprintf('%s.%s', $template['ObjectTemplate']['uuid'], $template['ObjectTemplate']['version'])] = $template;
|
|
|
|
}
|
2020-09-29 12:16:50 +02:00
|
|
|
$this->Galaxy = ClassRegistry::init('Galaxy');
|
|
|
|
$allowedGalaxies = $this->Galaxy->getAllowedMatrixGalaxies();
|
2020-10-01 11:08:30 +02:00
|
|
|
$allowedGalaxies = Hash::combine($allowedGalaxies, '{n}.Galaxy.uuid', '{n}.Galaxy');
|
2020-09-10 11:26:23 +02:00
|
|
|
$proxyMISPElements = [
|
2020-10-05 14:07:51 +02:00
|
|
|
'attribute' => $attributes,
|
2020-09-10 11:26:23 +02:00
|
|
|
'object' => $objects,
|
2020-09-29 12:16:50 +02:00
|
|
|
'objectTemplates' => $objectTemplates,
|
2020-10-02 10:48:52 +02:00
|
|
|
'galaxymatrix' => $allowedGalaxies,
|
|
|
|
'tagname' => $allTagNames
|
2020-09-10 11:26:23 +02:00
|
|
|
];
|
|
|
|
return $proxyMISPElements;
|
|
|
|
}
|
2020-09-21 11:01:36 +02:00
|
|
|
|
|
|
|
private function saveAndReturnErrors($data, $saveOptions = [], $errors = [])
|
|
|
|
{
|
|
|
|
$saveSuccess = $this->save($data, $saveOptions);
|
|
|
|
if (!$saveSuccess) {
|
|
|
|
foreach ($this->validationErrors as $validationError) {
|
|
|
|
$errors[] = $validationError[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $errors;
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
|
|
|
public function applySuggestions($user, $report, $contentWithSuggestions, $suggestionsMapping) {
|
|
|
|
$errors = [];
|
|
|
|
$replacedContent = $contentWithSuggestions;
|
|
|
|
$success = 0;
|
|
|
|
foreach ($suggestionsMapping as $value => $suggestedAttribute) {
|
|
|
|
$suggestedAttribute['value'] = $value;
|
|
|
|
$savedAttribute = $this->createAttributeFromSuggestion($user, $report, $suggestedAttribute);
|
|
|
|
if (empty($savedAttribute['errors'])) {
|
|
|
|
$success++;
|
2020-10-16 18:42:43 +02:00
|
|
|
$replacedContent = $this->applySuggestionsInText($replacedContent, $savedAttribute['attribute'], $value);
|
2020-10-15 11:56:21 +02:00
|
|
|
} else {
|
2020-10-16 18:42:43 +02:00
|
|
|
$replacedContent = $this->revertToOriginalInText($replacedContent, $value);
|
2020-10-15 11:56:21 +02:00
|
|
|
$errors[] = $savedAttribute['errors'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($success > 0) {
|
|
|
|
$report['EventReport']['content'] = $replacedContent;
|
|
|
|
$editErrors = $this->editReport($user, $report, $report['EventReport']['event_id']);
|
|
|
|
if (!empty($editErrors)) {
|
|
|
|
$errors[] = $editErrors;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function applySuggestionsInText($contentWithSuggestions, $attribute, $value)
|
|
|
|
{
|
|
|
|
$textToBeReplaced = sprintf('@[suggestion](%s)', $value);
|
|
|
|
$textToInject = sprintf('@[attribute](%s)', $attribute['Attribute']['uuid']);
|
|
|
|
$replacedContent = str_replace($textToBeReplaced, $textToInject, $contentWithSuggestions);
|
|
|
|
return $replacedContent;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function revertToOriginalInText($contentWithSuggestions, $value)
|
|
|
|
{
|
|
|
|
$textToBeReplaced = sprintf('@[suggestion](%s)', $value);
|
|
|
|
$textToInject = $value;
|
|
|
|
$replacedContent = str_replace($textToBeReplaced, $textToInject, $contentWithSuggestions);
|
|
|
|
return $replacedContent;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function createAttributeFromSuggestion($user, $report, $suggestedAttribute)
|
|
|
|
{
|
|
|
|
$errors = [];
|
|
|
|
$attribute = [
|
|
|
|
'event_id' => $report['EventReport']['event_id'],
|
|
|
|
'distribution' => 5,
|
|
|
|
'category' => $suggestedAttribute['category'],
|
|
|
|
'type' => $suggestedAttribute['type'],
|
|
|
|
'value' => $suggestedAttribute['value'],
|
|
|
|
'to_ids' => $suggestedAttribute['to_ids'],
|
|
|
|
];
|
|
|
|
$validationErrors = array();
|
|
|
|
$this->Event->Attribute->captureAttribute($attribute, $report['EventReport']['event_id'], $user, false, false, false, $validationErrors);
|
|
|
|
$savedAttribute = false;
|
|
|
|
if (!empty($validationErrors)) {
|
|
|
|
$errors = $validationErrors;
|
|
|
|
} else {
|
|
|
|
$savedAttribute = $this->Event->Attribute->find('first', array(
|
|
|
|
'recursive' => -1,
|
|
|
|
'conditions' => array('Attribute.id' => $this->Event->Attribute->id),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
return [
|
|
|
|
'errors' => $errors,
|
|
|
|
'attribute' => $savedAttribute
|
|
|
|
];
|
|
|
|
}
|
2020-10-19 12:26:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* transformFreeTextIntoReplacement
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param array $report
|
|
|
|
* @param array $complexTypeToolResult Uses the complex type tool output to support import regex replacements.
|
|
|
|
* Another solution would be to run the regex replacement on each token of the report which is too heavy
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function transformFreeTextIntoReplacement(array $user, array $report, array $complexTypeToolResult)
|
2020-10-16 18:42:43 +02:00
|
|
|
{
|
2020-10-19 12:26:13 +02:00
|
|
|
$complexTypeToolResultWithImportRegex = $this->injectImportRegexOnComplexTypeToolResult($complexTypeToolResult);
|
|
|
|
$valueToValueWithRegex = Hash::combine($complexTypeToolResultWithImportRegex, '{n}.valueWithImportRegex', '{n}.value');
|
2020-10-16 18:42:43 +02:00
|
|
|
$proxyElements = $this->getProxyMISPElements($user, $report['EventReport']['event_id']);
|
2020-10-19 12:26:13 +02:00
|
|
|
$originalContent = $report['EventReport']['content'];
|
|
|
|
$content = $originalContent;
|
2020-10-16 18:42:43 +02:00
|
|
|
$replacedValues = [];
|
|
|
|
foreach ($proxyElements['attribute'] as $uuid => $attribute) {
|
|
|
|
$count = 0;
|
|
|
|
$textToInject = sprintf('@[attribute](%s)', $uuid);
|
|
|
|
$content = str_replace($attribute['value'], $textToInject, $content, $count);
|
2020-10-19 12:26:13 +02:00
|
|
|
if ($count > 0 || strpos($originalContent, $attribute['value'])) { // Check if the value has been replaced by the first match
|
|
|
|
if (!isset($replacedValues[$attribute['value']])) {
|
|
|
|
$replacedValues[$attribute['value']] = [
|
|
|
|
'attributeUUIDs' => [$uuid],
|
|
|
|
'valueInReport' => $attribute['value'],
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
$replacedValues[$attribute['value']]['attributeUUIDs'][] = $uuid;
|
|
|
|
}
|
|
|
|
$count = 0;
|
|
|
|
}
|
|
|
|
if (isset($valueToValueWithRegex[$attribute['value']]) && $valueToValueWithRegex[$attribute['value']] != $attribute['value']) {
|
|
|
|
$content = str_replace($valueToValueWithRegex[$attribute['value']], $textToInject, $content, $count);
|
|
|
|
if ($count > 0 || strpos($originalContent, $valueToValueWithRegex[$attribute['value']])) {
|
|
|
|
if (!isset($replacedValues[$attribute['value']])) {
|
|
|
|
$replacedValues[$attribute['value']] = [
|
|
|
|
'attributeUUIDs' => [$uuid],
|
|
|
|
'valueInReport' => $valueToValueWithRegex[$attribute['value']],
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
$replacedValues[$attribute['value']]['attributeUUIDs'][] = $uuid;
|
|
|
|
}
|
|
|
|
}
|
2020-10-16 18:42:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return [
|
|
|
|
'contentWithReplacements' => $content,
|
|
|
|
'replacedValues' => $replacedValues
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function transformFreeTextIntoSuggestion($content, $complexTypeToolResult)
|
|
|
|
{
|
|
|
|
$replacedContent = $content;
|
|
|
|
$suggestionsMapping = [];
|
|
|
|
$typeToCategoryMapping = $this->Event->Attribute->typeToCategoryMapping();
|
|
|
|
foreach ($complexTypeToolResult as $i => $complexTypeToolEntry) {
|
|
|
|
$textToBeReplaced = $complexTypeToolEntry['value'];
|
|
|
|
$textToInject = sprintf('@[suggestion](%s)', $textToBeReplaced);
|
|
|
|
$suggestionsMapping[$textToBeReplaced] = [
|
|
|
|
'category' => $typeToCategoryMapping[$complexTypeToolEntry['default_type']][0],
|
|
|
|
'type' => $complexTypeToolEntry['default_type'],
|
|
|
|
'value' => $textToBeReplaced,
|
|
|
|
'to_ids' => $complexTypeToolEntry['to_ids'],
|
|
|
|
];
|
|
|
|
$replacedContent = str_replace($textToBeReplaced, $textToInject, $replacedContent);
|
|
|
|
}
|
|
|
|
return [
|
|
|
|
'contentWithSuggestions' => $replacedContent,
|
|
|
|
'suggestionsMapping' => $suggestionsMapping
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2020-10-19 12:26:13 +02:00
|
|
|
public function injectImportRegexOnComplexTypeToolResult($complexTypeToolResult) {
|
2020-10-16 18:42:43 +02:00
|
|
|
foreach ($complexTypeToolResult as $i => $complexTypeToolEntry) {
|
|
|
|
$transformedValue = $this->runRegexp($complexTypeToolEntry['default_type'], $complexTypeToolEntry['value']);
|
|
|
|
if ($transformedValue !== false) {
|
|
|
|
$complexTypeToolResult[$i]['valueWithImportRegex'] = $transformedValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $complexTypeToolResult;
|
|
|
|
}
|
2020-10-19 12:26:13 +02:00
|
|
|
|
|
|
|
public function getComplexTypeToolResultFromReport($content)
|
|
|
|
{
|
|
|
|
App::uses('ComplexTypeTool', 'Tools');
|
|
|
|
$complexTypeTool = new ComplexTypeTool();
|
|
|
|
$this->Warninglist = ClassRegistry::init('Warninglist');
|
|
|
|
$complexTypeTool->setTLDs($this->Warninglist->fetchTLDLists());
|
|
|
|
$complexTypeToolResult = $complexTypeTool->checkComplexRouter($content, 'freetext');
|
|
|
|
return $complexTypeToolResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getComplexTypeToolResultWithReplacementsFromReport($user, $report)
|
|
|
|
{
|
|
|
|
$complexTypeToolResult = $this->getComplexTypeToolResultFromReport($report['EventReport']['content']);
|
|
|
|
$replacementResult = $this->transformFreeTextIntoReplacement($user, $report, $complexTypeToolResult);
|
|
|
|
$complexTypeToolResult = $this->getComplexTypeToolResultFromReport($replacementResult['contentWithReplacements']);
|
|
|
|
return [
|
|
|
|
'complexTypeToolResult' => $complexTypeToolResult,
|
|
|
|
'replacementResult' => $replacementResult,
|
|
|
|
];
|
|
|
|
}
|
2020-10-19 18:12:41 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* extractWithReplacementsFromReport Extract context information from report with special care for ATT&CK
|
|
|
|
*
|
|
|
|
* @param array $user
|
|
|
|
* @param array $report
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function extractWithReplacementsFromReport(array $user, array $report, $options = [])
|
|
|
|
{
|
|
|
|
$baseOptions = [
|
|
|
|
'replace' => false,
|
|
|
|
'tags' => true,
|
|
|
|
'synonyms' => true,
|
|
|
|
'synonyms_min_characters' => 4,
|
|
|
|
'prune_deprecated' => true,
|
|
|
|
'attack' => true,
|
|
|
|
];
|
|
|
|
$options = array_merge($baseOptions, $options);
|
|
|
|
$originalContent = $report['EventReport']['content'];
|
|
|
|
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
|
|
|
|
$mitreAttackGalaxyId = $this->GalaxyCluster->Galaxy->getMitreAttackGalaxyId();
|
|
|
|
$clusterContain = ['Tag'];
|
|
|
|
|
|
|
|
if ($options['prune_deprecated']) {
|
|
|
|
$clusterContain['Galaxy'] = ['conditions' => ['Galaxy.namespace !=' => 'deprecated']];
|
|
|
|
}
|
|
|
|
if ($options['synonyms']) {
|
|
|
|
$clusterContain['GalaxyElement'] = ['conditions' => ['GalaxyElement.key' => 'synonyms']];
|
|
|
|
}
|
|
|
|
$clusterConditions = [];
|
|
|
|
if ($options['attack']) {
|
|
|
|
$clusterConditions = ['GalaxyCluster.galaxy_id !=' => $mitreAttackGalaxyId];
|
|
|
|
}
|
|
|
|
$clusters = $this->GalaxyCluster->find('all', [
|
|
|
|
'conditions' => $clusterConditions,
|
|
|
|
'contain' => $clusterContain
|
|
|
|
]);
|
|
|
|
|
|
|
|
if ($options['tags']) {
|
|
|
|
$this->Tag = ClassRegistry::init('Tag');
|
|
|
|
$tags = $this->Tag->fetchUsableTags($user);
|
|
|
|
foreach ($tags as $i => $tag) {
|
|
|
|
$tagName = $tag['Tag']['name'];
|
2020-10-20 08:28:50 +02:00
|
|
|
$found = $this->isValidReplacementTag($originalContent, $tagName);
|
|
|
|
if ($found) {
|
2020-10-19 18:12:41 +02:00
|
|
|
$replacedContext[$tagName][$tagName] = $tag['Tag'];
|
|
|
|
} else {
|
|
|
|
$tagNameUpper = strtoupper($tagName);
|
2020-10-20 08:28:50 +02:00
|
|
|
$found = $this->isValidReplacementTag($originalContent, $tagNameUpper);
|
|
|
|
if ($found) {
|
2020-10-19 18:12:41 +02:00
|
|
|
$replacedContext[$tagNameUpper][$tagName] = $tag['Tag'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($clusters as $i => $cluster) {
|
|
|
|
$cluster['GalaxyCluster']['colour'] = '#0088cc';
|
|
|
|
$tagName = $cluster['GalaxyCluster']['tag_name'];
|
2020-10-20 08:28:50 +02:00
|
|
|
$found = $this->isValidReplacementTag($originalContent, $tagName);
|
|
|
|
if ($found) {
|
2020-10-19 18:12:41 +02:00
|
|
|
$replacedContext[$tagName][$tagName] = $cluster['GalaxyCluster'];
|
|
|
|
}
|
2020-10-20 08:28:50 +02:00
|
|
|
$found = strpos($originalContent, $cluster['GalaxyCluster']['value']) !== false;
|
|
|
|
if ($found) {
|
2020-10-19 18:12:41 +02:00
|
|
|
$replacedContext[$cluster['GalaxyCluster']['value']][$tagName] = $cluster['GalaxyCluster'];
|
|
|
|
}
|
|
|
|
if ($options['synonyms']) {
|
|
|
|
foreach ($cluster['GalaxyElement'] as $j => $element) {
|
|
|
|
if (strlen($element['value']) >= $options['synonyms_min_characters']) {
|
2020-10-20 08:28:50 +02:00
|
|
|
$found = strpos($originalContent, $element['value']) !== false;
|
|
|
|
if ($found) {
|
2020-10-19 18:12:41 +02:00
|
|
|
$replacedContext[$element['value']][$tagName] = $cluster['GalaxyCluster'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($options['attack']) {
|
|
|
|
unset($clusterContain['Galaxy']);
|
|
|
|
$attackClusters = $this->GalaxyCluster->find('all', [
|
|
|
|
'conditions' => ['GalaxyCluster.galaxy_id' => $mitreAttackGalaxyId],
|
|
|
|
'contain' => $clusterContain
|
|
|
|
]);
|
|
|
|
foreach ($attackClusters as $i => $cluster) {
|
|
|
|
$cluster['GalaxyCluster']['colour'] = '#0088cc';
|
|
|
|
$tagName = $cluster['GalaxyCluster']['tag_name'];
|
2020-10-20 08:28:50 +02:00
|
|
|
$found = strpos($originalContent, $cluster['GalaxyCluster']['value']) !== false;
|
|
|
|
if ($found) {
|
2020-10-19 18:12:41 +02:00
|
|
|
$replacedContext[$cluster['GalaxyCluster']['value']][$tagName] = $cluster['GalaxyCluster'];
|
|
|
|
} else {
|
|
|
|
$clusterParts = explode(' - ', $cluster['GalaxyCluster']['value']);
|
2020-10-20 08:28:50 +02:00
|
|
|
$found = strpos($originalContent, $clusterParts[0]) !== false;
|
|
|
|
if ($found) {
|
2020-10-19 18:12:41 +02:00
|
|
|
$replacedContext[$clusterParts[0]][$tagName] = $cluster['GalaxyCluster'];
|
|
|
|
} else {
|
2020-10-20 08:28:50 +02:00
|
|
|
$found = strpos($originalContent, $clusterParts[1]) !== false;
|
|
|
|
if ($found) {
|
2020-10-19 18:12:41 +02:00
|
|
|
$replacedContext[$clusterParts[1]][$tagName] = $cluster['GalaxyCluster'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-20 08:28:50 +02:00
|
|
|
$toReturn = [
|
|
|
|
'replacedContext' => $replacedContext
|
2020-10-19 18:12:41 +02:00
|
|
|
];
|
2020-10-20 08:28:50 +02:00
|
|
|
if (!$options['replace']) {
|
|
|
|
$content = $originalContent;
|
|
|
|
foreach ($replacedContext as $rawText => $replacements) {
|
|
|
|
// Replace with first one until a better strategy is found
|
|
|
|
reset($replacements);
|
|
|
|
$replacement = key($replacements);
|
|
|
|
$textToInject = sprintf('@[tag](%s)', $replacement);
|
|
|
|
$content = str_replace($replacement, $textToInject, $content);
|
|
|
|
}
|
|
|
|
$toReturn['contentWithReplacements'] = $content;
|
|
|
|
}
|
|
|
|
return $toReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* findValidReplacementTag Search if tagName is in content and is not wrapped in a tag reference
|
|
|
|
*
|
|
|
|
* @param string $content
|
|
|
|
* @param string $tagName
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function isValidReplacementTag($content, $tagName)
|
|
|
|
{
|
|
|
|
$lastIndex = 0;
|
|
|
|
$allIndices = [];
|
|
|
|
while (($lastIndex = strpos($content, $tagName, $lastIndex)) !== false) {
|
|
|
|
$allIndices[] = $lastIndex;
|
|
|
|
$lastIndex = $lastIndex + strlen($tagName);
|
|
|
|
}
|
|
|
|
if (empty($allIndices)) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
$wrapper = '@[tag](';
|
|
|
|
foreach ($allIndices as $i => $index) {
|
|
|
|
$stringBeforeTag = substr($content, $index - strlen($wrapper), strlen($wrapper));
|
|
|
|
if ($stringBeforeTag != $wrapper) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2020-10-19 18:12:41 +02:00
|
|
|
}
|
2020-02-18 15:02:35 +01:00
|
|
|
}
|