Program: CWE-89
Project: CVE-2021-39302
Buggy source:
<?php

App::uses('AppModel', 'Model');

class Log extends AppModel
{
    public $warningActions = array(
        'warning',
        'change_pw',
        'login_fail',
        'version_warning',
        'auth_fail'
    );
    public $errorActions = array(
        'error'
    );
    public $validate = array(
            'action' => array(
            'rule' => array(
                'inList',
                array( // ensure that the length of the rules is < 20 in length
                    'accept',
                    'accept_delegation',
                    'acceptRegistrations',
                    'add',
                    'admin_email',
                    'attachTags',
                    'auth',
                    'auth_fail',
                    'blocklisted',
                    'captureRelations',
                    'change_pw',
                    'delete',
                    'disable',
                    'discard',
                    'discardRegistrations',
                    'edit',
                    'email',
                    'enable',
                    'enrichment',
                    'error',
                    'export',
                    'fetchEvent',
                    'file_upload',
                    'galaxy',
                    'include_formula',
                    'login',
                    'login_fail',
                    'logout',
                    'merge',
                    'pruneUpdateLogs',
                    'publish',
                    'publish_sightings',
                    'publish alert',
                    'pull',
                    'purge_events',
                    'push',
                    'registration',
                    'registration_error',
                    'remove_dead_workers',
                    'request',
                    'request_delegation',
                    'reset_auth_key',
                    'send_mail',
                    'security',
                    'serverSettingsEdit',
                    'tag',
                    'undelete',
                    'update',
                    'update_database',
                    'update_db_worker',
                    'upgrade_24',
                    'upload_sample',
                    'version_warning',
                    'warning',
                    'wipe_default'
                )
            ),
            'message' => 'Options : ...'
        )
    );

    public $actionDefinitions = array(
        'login' => array('desc' => 'Login action', 'formdesc' => "Login action"),
        'logout' => array('desc' => 'Logout action', 'formdesc' => "Logout action"),
        'add' => array('desc' => 'Add action', 'formdesc' => "Add action"),
        'edit' => array('desc' => 'Edit action', 'formdesc' => "Edit action"),
        'change_pw' => array('desc' => 'Change_pw action', 'formdesc' => "Change_pw action"),
        'delete' => array('desc' => 'Delete action', 'formdesc' => "Delete action"),
        'publish' => array('desc' => "Publish action", 'formdesc' => "Publish action")
    );

    public $logMeta = array(
        'email' => array('values' => array('email'), 'name' => 'Emails'),
        'auth_issues' => array('values' => array('login_fail', 'auth_fail'), 'name' => 'Authentication issues')
    );

    public $logMetaAdmin = array(
        'update' => array('values' => array('update_database'), 'name' => 'MISP Update results'),
        'settings' => array('values' => array('serverSettingsEdit', 'remove_dead_workers'), 'name' => 'Setting changes'),
        'errors' => array('values' => array('warning', 'error', 'version_warning'), 'name' => 'Warnings and errors'),
        'email' => array('values' => array('admin_email'))
    );

    /**
     * Null when not defined, false when not enabled
     * @var Syslog|null|false
     */
    private $syslog;

    public function beforeSave($options = array())
    {
        if (!empty(Configure::read('MISP.log_skip_db_logs_completely'))) {
            return false;
        }
        if (Configure::read('MISP.log_client_ip')) {
            $ip_header = 'REMOTE_ADDR';
            if (Configure::read('MISP.log_client_ip_header')) {
                $ip_header = Configure::read('MISP.log_client_ip_header');
            }

            if (isset($_SERVER[$ip_header])) {
                $this->data['Log']['ip'] = $_SERVER[$ip_header];
            }
        }
        $setEmpty = array('title' => '', 'model' => '', 'model_id' => 0, 'action' => '', 'user_id' => 0, 'change' => '', 'email' => '', 'org' => '', 'description' => '', 'ip' => '');
        foreach ($setEmpty as $field => $empty) {
            if (!isset($this->data['Log'][$field]) || empty($this->data['Log'][$field])) {
                $this->data['Log'][$field] = $empty;
            }
        }
        if (!isset($this->data['Log']['created'])) {
            $this->data['Log']['created'] = date('Y-m-d H:i:s');
        }
        if (!isset($this->data['Log']['org']) || empty($this->data['Log']['org'])) {
            $this->data['Log']['org'] = 'SYSTEM';
        }
        $truncate_fields = array('title', 'change', 'description');
        foreach ($truncate_fields as $tf) {
            if (isset($this->data['Log'][$tf]) && strlen($this->data['Log'][$tf]) >= 65535) {
                $this->data['Log'][$tf] = substr($this->data['Log'][$tf], 0, 65532) . '...';
            }
        }
        $this->logData($this->data);
        if ($this->data['Log']['action'] === 'request' && !empty(Configure::read('MISP.log_paranoid_skip_db'))) {
            return false;
        }
        return true;
    }

    public function returnDates($org = 'all')
    {
        $dataSourceConfig = ConnectionManager::getDataSource('default')->config;
        $dataSource = $dataSourceConfig['datasource'];
        $conditions = array();
        $this->Organisation = ClassRegistry::init('Organisation');
        if ($org !== 'all') {
            $org = $this->Organisation->fetchOrg($org);
            if (empty($org)) {
                throw new MethodNotAllowedException('Invalid organisation.');
            }
            $conditions['org'] = $org['name'];
        }
        $conditions['AND']['NOT'] = array('action' => array('login', 'logout', 'changepw'));
        if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
            $validDates = $this->find('all', array(
                    'fields' => array('DISTINCT UNIX_TIMESTAMP(DATE(created)) AS Date', 'count(id) AS count'),
                    'conditions' => $conditions,
                    'group' => array('Date'),
                    'order' => array('Date')
            ));
        } elseif ($dataSource == 'Database/Postgres') {
            // manually generate the query for Postgres
            // cakephp ORM would escape "DATE" datatype in CAST expression
            $condnotinaction = "'" . implode("', '", $conditions['AND']['NOT']['action']) . "'";
            if (!empty($conditions['org'])) {
                $condOrg = ' AND org = "' . $conditions['org'] . '"';
            } else {
                $condOrg = '';
            }
            $sql = 'SELECT DISTINCT EXTRACT(EPOCH FROM CAST(created AS DATE)) AS "Date",
                                    COUNT(id) AS count
                    FROM logs
                    WHERE action NOT IN (' . $condnotinaction . ')
                    ' . $condOrg . '
                    GROUP BY "Date" ORDER BY "Date"';
            $validDates = $this->query($sql);
        }
        $data = array();
        foreach ($validDates as $k => $date) {
            $data[$date[0]['Date']] = intval($date[0]['count']);
        }
        return $data;
    }

    /**
     * @param string|array $user
     * @param string $action
     * @param string $model
     * @param int $modelId
     * @param string $title
     * @param string|array $change
     * @return array|null
     * @throws Exception
     * @throws InvalidArgumentException
     */
    public function createLogEntry($user, $action, $model, $modelId = 0, $title = '', $change = '')
    {
        if (in_array($action, ['tag', 'galaxy', 'publish', 'publish_sightings'], true) && Configure::read('MISP.log_new_audit')) {
            return; // Do not store tag changes when new audit is enabled
        }
        if ($user === 'SYSTEM') {
            $user = array('Organisation' => array('name' => 'SYSTEM'), 'email' => 'SYSTEM', 'id' => 0);
        } else if (!is_array($user)) {
            throw new InvalidArgumentException("User must be array or 'SYSTEM' string.");
        }

        if (is_array($change)) {
            $output = array();
            foreach ($change as $field => $values) {
                $isSecret = strpos($field, 'password') !== false || ($field === 'authkey' && Configure::read('Security.do_not_log_authkeys'));
                if ($isSecret) {
                    $oldValue = $newValue = "*****";
                } else {
                    list($oldValue, $newValue) = $values;
                }
                $output[] = "$field ($oldValue) => ($newValue)";
            }
            $change = implode(", ", $output);
        }

        $this->create();
        $result = $this->save(array(
            'org' => $user['Organisation']['name'],
            'email' => $user['email'],
            'user_id' => $user['id'],
            'action' => $action,
            'title' => $title,
            'change' => $change,
            'model' => $model,
            'model_id' => $modelId,
        ));

        if (!$result) {
            if ($action === 'request' && !empty(Configure::read('MISP.log_paranoid_skip_db'))) {
                return null;
            }

            throw new Exception("Cannot save log because of validation errors: " . json_encode($this->validationErrors));
        }

        return $result;
    }

    // to combat a certain bug that causes the upgrade scripts to loop without being able to set the correct version
    // this function remedies a fixed upgrade bug instance by eliminating the massive number of erroneous upgrade log entries
    public function pruneUpdateLogs($jobId = false, $user)
    {
        $max = $this->find('first', array('fields' => array('MAX(id) AS lastid')));
        if (!empty($max)) {
            $max = $max[0]['lastid'];
        }
        if ($jobId) {
            $this->Job = ClassRegistry::init('Job');
            $this->Job->id = $jobId;
            if (!$this->Job->exists()) {
                $jobId = false;
            }
        }
        $iterations = ($max / 1000);
        for ($i = 0; $i < $iterations; $i++) {
            $this->deleteAll(array(
                'OR' => array(
                        'action' => 'update_database',
                        'AND' => array(
                            'action' => 'edit',
                            'model' => 'AdminSetting'
                        )
                ),
                'id >' => $i * 1000,
                'id <' => ($i+1) * 1000));
            if ($jobId) {
                $this->Job->saveField('progress', $i * 100 / $iterations);
            }
        }
        $this->create();
        $this->save(array(
                'org' => $user['Organisation']['name'],
                'email' =>$user['email'],
                'user_id' => $user['id'],
                'action' => 'pruneUpdateLogs',
                'title' => 'Pruning updates',
                'change' => 'Pruning completed in ' . $i . ' iteration(s).',
                'model' => 'Log',
                'model_id' => 0
        ));
    }


    public function pruneUpdateLogsRouter($user)
    {
        if (Configure::read('MISP.background_jobs')) {
            $job = ClassRegistry::init('Job');
            $job->create();
            $data = array(
                    'worker' => 'default',
                    'job_type' => 'prune_update_logs',
                    'job_input' => 'All update entries',
                    'status' => 0,
                    'retries' => 0,
                    'org_id' => $user['org_id'],
                    'org' => $user['Organisation']['name'],
                    'message' => 'Purging the heretic.',
            );
            $job->save($data);
            $jobId = $job->id;
            $process_id = CakeResque::enqueue(
                    'default',
                    'AdminShell',
                    array('prune_update_logs', $jobId, $user['id']),
                    true
            );
            $job->saveField('process_id', $process_id);
            return $process_id;
        } else {
            $result = $this->pruneUpdateLogs(false, $user);
            return $result;
        }
    }

    public function logData($data)
    {
        if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_audit_notifications_enable')) {
            $pubSubTool = $this->getPubSubTool();
            $pubSubTool->publish($data, 'audit', 'log');
        }

        $this->publishKafkaNotification('audit', $data, 'log');

        if (Configure::read('Plugin.ElasticSearch_logging_enable')) {
            // send off our logs to distributed /dev/null
            $logIndex = Configure::read("Plugin.ElasticSearch_log_index");
            $elasticSearchClient = $this->getElasticSearchTool();
            $elasticSearchClient->pushDocument($logIndex, "log", $data);
        }

        // Do not save request action logs to syslog, because they contain no information
        if ($data['Log']['action'] === 'request') {
            return true;
        }

        // write to syslogd as well if enabled
        if ($this->syslog === null) {
            if (Configure::read('Security.syslog')) {
                $options = [];
                $syslogToStdErr = Configure::read('Security.syslog_to_stderr');
                if ($syslogToStdErr !== null) {
                    $options['to_stderr'] = $syslogToStdErr;
                }
                $syslogIdent = Configure::read('Security.syslog_ident');
                if ($syslogIdent) {
                    $options['ident'] = $syslogIdent;
                }
                $this->syslog = new SysLog($options);
            } else {
                $this->syslog = false;
            }
        }
        if ($this->syslog) {
            $action = 'info';
            if (isset($data['Log']['action'])) {
                if (in_array($data['Log']['action'], $this->errorActions)) {
                    $action = 'err';
                }
                if (in_array($data['Log']['action'], $this->warningActions)) {
                    $action = 'warning';
                }
            }

            $entry = $data['Log']['action'];
            if (!empty($data['Log']['title'])) {
                $entry .= " -- {$data['Log']['title']}";
            }
            if (!empty($data['Log']['description'])) {
                $entry .= " -- {$data['Log']['description']}";
            }
            $this->syslog->write($action, $entry);
        }
        return true;
    }

    public function filterSiteAdminSensitiveLogs($list)
    {
        $this->User = ClassRegistry::init('User');
        $site_admin_roles = $this->User->Role->find('list', array(
            'recursive' => -1,
            'conditions' => array('Role.perm_site_admin' => 1),
            'fields' => array('Role.id', 'Role.id')
        ));
        $site_admins = $this->User->find('list', array(
            'recursive' => -1,
            'conditions' => array(
                'User.role_id' => array_values($site_admin_roles)
            ),
            'fields' => array('User.id', 'User.id')
        ));
        foreach ($list as $k => $v) {
            if (
                $v['Log']['model'] === 'User' &&
                in_array($v['Log']['model_id'], array_values($site_admins)) &&
                in_array($v['Log']['action'], array('add', 'edit', 'reset_auth_key'))
            ) {
                $list[$k]['Log']['change'] = __('Redacted');
            }
        }
        return $list;
    }

    public function changeParser($change)
    {
        $change = explode(',', $change);
        $data = [];
        foreach ($change as $entry) {
            $entry = trim($entry);
            $fieldName = explode(' ', $entry)[0];
            $entry = substr($entry, strlen($fieldName));
            $entry = trim($entry);
            if (strpos($entry, ') => (')) {
                list($before, $after) = explode(') => (', $entry);
                $before = substr($before, 1);
                $after = substr($after, 0, -1);
                $data[$fieldName] = $after;
            }
        }
        return $data;
    }

    public function findDeletedEvents($conditions)
    {
        $conditions['model'] = 'Event';
        $conditions['action'] = 'delete';
        $this->Event = ClassRegistry::init('Event');
        $deletions = $this->find('all', [
            'recursive' => -1,
            'conditions' => $conditions,
            'order' => ['Log.id']
        ]);
        $deleted_events = [];
        $users = [];
        $orgs = [];
        $deleted_event_ids = [];
        foreach ($deletions as $deletion_entry) {
            if (!empty($deleted_event_ids[$deletion_entry['Log']['model_id']])) {
                continue;
            } else {
                $deleted_event_ids[$deletion_entry['Log']['model_id']] = true;
            }
            $event = $this->Event->find('first', [
                'conditions' => ['Event.id' => $deletion_entry['Log']['model_id']],
                'recursive' => -1,
                'fields' => ['Event.id']
            ]);
            if (!empty($event)) {
                // event is already restored / not deleted
                continue;
            }
            $temp = [
                'event_id' => $deletion_entry['Log']['model_id'],
                'user_id' => $deletion_entry['Log']['user_id'],
                'created' => $deletion_entry['Log']['created']
            ];
            $event_creation_entry = $this->find('first', [
                'recursive' => -1,
                'conditions' => [
                    'model_id' => $temp['event_id'],
                    'model' => 'Event',
                    'action' => 'add'
                ]
            ]);
            $event = $this->changeParser($event_creation_entry['Log']['change']);
            $temp['event_info'] = $event['info'];
            $temp['event_org_id'] = $event['org_id'];
            $temp['event_orgc_id'] = $event['orgc_id'];
            $temp['event_user_id'] = $event['user_id'];
            $temp['event_info'] = $event['info'];
            $temp['event_created'] = $event_creation_entry['Log']['created'];
            foreach (['org', 'orgc'] as $scope) {
                if (empty($orgs[$temp['event_' . $scope . '_id']])) {
                    $orgs[$temp['event_' . $scope . '_id']] = array_values($this->Event->Orgc->find('list', [
                        'recursive' => -1,
                        'conditions' => array('id' => $temp['event_' . $scope . '_id']),
                        'fields' => array('id', 'name')
                    ]))[0];
                }
                $temp['event_' . $scope . '_name'] = $orgs[$temp['event_' . $scope . '_id']];
            }
            $users[$temp['user_id']] = array_values($this->Event->User->find('list', [
                'recursive' => -1,
                'conditions' => array('id' => $temp['user_id']),
                'fields' => array('id', 'email')
            ]))[0];
            $temp['user_name'] = $users[$temp['user_id']];
            $users[$temp['event_user_id']] = array_values($this->Event->User->find('list', [
                'recursive' => -1,
                'conditions' => array('id' => $temp['event_user_id']),
                'fields' => array('id', 'email')
            ]))[0];
            $temp['event_user_name'] = $users[$temp['event_user_id']];
            $deleted_events[] = $temp;
        }
        return $deleted_events;
    }

    public function recoverDeletedEvent($id, $mock = false)
    {
        if ($mock) {
            $this->mockRecovery = true;
            $this->mockLog = [];
        }
        $objectMap = [];
        $logEntries = [];
        $this->__recoverDeletedEventContainer($id, $objectMap, $logEntries);
        $this->__recoverDeletedObjects($id, $objectMap, $logEntries);
        $this->__recoverDeletedAttributes($id, $objectMap, $logEntries);
        $this->__recoverDeletedObjectReferences($id, $objectMap, $logEntries);
        $this->__recoverDeletedTagConnectors($id, $objectMap, $logEntries, 'Event');
        $this->__recoverDeletedTagConnectors($id, $objectMap, $logEntries, 'Attribute');
        $this->__recoverDeletedProposals($id, $objectMap, $logEntries);
        ksort($logEntries);
        foreach ($logEntries as $logEntry) {
            $this->{'__executeRecovery' . $logEntry['model']}($logEntry, $id);
        }
        return count($logEntries);
        // order: event -> object -> attribute -> object reference -> tag -> galaxy -> shadow_attribute -> sighting
    }

    private function __recoverDeletedEventContainer($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'Event',
                'model_id' => $id,
                'action' => ['add', 'edit', 'publish', 'alert']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
            $objectMap['Event'][$log['Log']['model_id']] = true;
        }
    }

    private function __recoverDeletedObjects($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'MispObject',
                'change LIKE ' => '%event_id () => (' . $id . ')%',
                'action' => ['add']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $objectMap['MispObject'][$log['Log']['model_id']] = true;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'MispObject',
                'model_id' => array_keys($objectMap['MispObject']),
                'action' => ['add', 'edit', 'delete']
            ]
        ]);
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
        }
    }

    private function __recoverDeletedObjectReferences($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'ObjectReference',
                'change LIKE ' => '%event_id () => (' . $id . ')%',
                'action' => ['add']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $objectMap['ObjectReference'][$log['Log']['model_id']] = true;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'ObjectReference',
                'model_id' => array_keys($objectMap['ObjectReference']),
                'action' => ['add', 'edit']
            ]
        ]);
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
        }
    }

    private function __recoverDeletedTagConnectors($id, &$objectMap, &$logEntries, $scope)
    {
        if (empty($objectMap[$scope])) {
            // example: we have no attributes, so we return
            return;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => $scope,
                'action' => ['tag', 'galaxy'],
                'model_id' => array_keys($objectMap[$scope])
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            if ($log['Log']['action'] === 'tag') {
                $temp = explode(' ', $log['Log']['title']);
                $local = ($temp[1] === 'local' ? true : false);
                $tag_id = ($local ? $temp[3] : $temp[2]);
                $tag_id = substr($tag_id, 1, -1);
            } else {
                $matches = [];
                preg_match('/\(([0-9]*)\)\s(from|to)/', $log['Log']['title'], $matches);
                if (!isset($matches[1])) {
                    continue;
                }
                $local = false;
                $tag_id = $matches[1];
            }
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => (strpos($log['Log']['title'], 'Attached')) === false ? 'remove_tag' : 'add_tag',
                'data' => [
                    'tag_id' => $tag_id,
                    'id' => $log['Log']['model_id'],
                    'target_type' => $log['Log']['model'],
                    'tag_type' => $log['Log']['action'],
                    'local' => $local
                ]
            ];
        }
    }

    private function __recoverDeletedAttributes($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'Attribute',
                'title LIKE ' => '%from Event (' . $id . ')%',
                'action' => ['add']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $objectMap['Attribute'][$log['Log']['model_id']] = true;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'Attribute',
                'model_id' => array_keys($objectMap['Attribute']),
                'action' => ['add', 'edit', 'delete']
            ]
        ]);
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
            $objectMap['Attribute'][$log['Log']['model_id']] = true;
        }
    }

    private function __recoverDeletedProposals($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'ShadowAttribute',
                'title LIKE ' => '%: to Event (' . $id . '): %',
                'action' => ['add']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $objectMap['ShadowAttribute'][$log['Log']['model_id']] = true;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'ShadowAttribute',
                'model_id' => array_keys($objectMap['ShadowAttribute']),
                'action' => ['add', 'accept', 'delete']
            ]
        ]);
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
            $objectMap['ShadowAttribute'][$log['Log']['model_id']] = true;
        }
    }


    private function __executeRecoveryEvent($logEntry, $id)
    {
        if (empty($this->Event)) {
            $this->Event = ClassRegistry::init('Event');
        }
        if (empty($this->GalaxyCluster)) {
            $this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
        }
        if (empty($this->EventBlocklist)) {
            $this->EventBlocklist = ClassRegistry::init('EventBlocklist');
        }
        switch($logEntry['action']) {
            case 'add':
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'Event', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->Event->create();
                    $this->Event->save($logEntry['data']);
                    $blockListEntry = $this->EventBlocklist->find('first', array(
                        'conditions' => array('event_uuid' => $logEntry['data']['uuid']),
                        'fields' => 'id'
                    ));
                    if (!empty($blockListEntry)) {
                        $this->EventBlocklist->delete($blockListEntry['EventBlocklist']['id']);
                    }
                }
                break;
            case 'edit':
            case 'publish':
                $event = $this->Event->find('first', [
                    'recursive' => -1,
                    'conditions' => ['Event.id' => $logEntry['model_id']]
                ]);
                if (!empty($event)) {
                    if ($logEntry['action'] === 'publish' || $logEntry['action'] === 'alert') {
                        $event['Event']['published'] = 1;
                        $event['Event']['publish_timestamp'] = strtotime($logEntry['data']['timestamp']);
                    } else {
                        foreach ($logEntry['data'] as $field => $value) {
                            $event['Event'][$field] = $value;
                        }
                    }
                    $this->Event->create();
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Event', 'action' => 'edit', 'data' => $event];
                    } else {
                        $this->Event->save($event);
                    }
                }
                break;
            case 'add_tag':
                $tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
                $this->Event->EventTag->create();
                $this->Event->create();
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'EventTag', 'action' => 'add', 'data' => [
                        'tag_id' => $tag_id,
                        'event_id' => $logEntry['data']['id'],
                        'local' => !empty($logEntry['data']['local'])
                    ]];
                } else {
                    $this->Event->EventTag->save([
                        'tag_id' => $tag_id,
                        'event_id' => $logEntry['data']['id'],
                        'local' => !empty($logEntry['data']['local'])
                    ]);
                }
                break;
            case 'remove_tag':
                $tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
                $et = $this->Event->EventTag->find('first', [
                    'recursive' => -1,
                    'conditions' => [
                        'tag_id' => $tag_id,
                        'event_id' => $logEntry['data']['id']
                    ]
                ]);
                if (!empty($et)) {
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'EventTag', 'action' => 'delete', 'data' => $et['EventTag']['id']];
                    } else {
                        $this->Event->EventTag->delete($et['EventTag']['id']);
                    }
                }
                break;
        }
    }

    private function __executeRecoveryAttribute($logEntry, $id)
    {
        if (empty($this->Attribute)) {
            $this->Attribute = ClassRegistry::init('Attribute');
        }
        if (empty($this->GalaxyCluster)) {
            $this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
        }
        if (!empty($logEntry['data']['value1'])) {
            $logEntry['data']['value'] = $logEntry['data']['value1'];
            if (!empty($logEntry['data']['value2'])) {
                $logEntry .= '|' . $logEntry['data']['value2'];
            }
        }
        switch($logEntry['action']) {
            case 'add':
                $logEntry['data'] = $this->Attribute->UTCToISODatetime(['Attribute' => $logEntry['data']], 'Attribute');
                $logEntry['data'] = $logEntry['data']['Attribute'];
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'Attribute', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->Attribute->create();
                    if (!isset($logEntry['data']['to_ids'])) {
                        $logEntry['data']['to_ids'] = 0;
                    }
                    $this->Attribute->save($logEntry['data']);
                }
                break;
            case 'edit':
                $attribute = $this->Attribute->find('first', [
                    'recursive' => -1,
                    'conditions' => ['Attribute.id' => $logEntry['model_id']]
                ]);
                if (!empty($attribute)) {
                    $logEntry['data'] = $this->Attribute->UTCToISODatetime(['Attribute' => $logEntry['data']], 'Attribute');
                    $logEntry['data'] = $logEntry['data']['Attribute'];
                    foreach ($logEntry['data'] as $field => $value) {
                        $attribute['Attribute'][$field] = $value;
                    }
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Attribute', 'action' => 'edit', 'data' => $attribute];
                    } else {
                        $this->Attribute->save($attribute);
                    }
                }
                break;
            case 'delete':
                $attribute = $this->Attribute->find('first', [
                    'recursive' => -1,
                    'conditions' => ['Attribute.id' => $logEntry['model_id']]
                ]);
                if (!empty($attribute)) {
                    $attribute['Attribute']['deleted'] = 1;
                    $attribute['Attribute']['timestamp'] = $logEntry['data']['timestamp'];
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Attribute', 'action' => 'delete', 'data' => $attribute];
                    } else {
                        $this->Attribute->save($attribute);
                    }
                }
                break;
            case 'add_tag':
                $tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'AttributeTag', 'action' => 'add', 'data' => [
                        'tag_id' => $tag_id,
                        'attribute_id' => $logEntry['data']['id'],
                        'event_id' => $id,
                        'local' => !empty($logEntry['data']['local'])
                    ]];
                } else {
                    $this->Attribute->AttributeTag->create();
                    $this->Attribute->AttributeTag->save([
                        'tag_id' => $tag_id,
                        'attribute_id' => $logEntry['data']['id'],
                        'event_id' => $id,
                        'local' => !empty($logEntry['data']['local'])
                    ]);
                }
                break;
            case 'remove_tag':
                $tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
                $at = $this->Attribute->AttributeTag->find('first', [
                    'recursive' => -1,
                    'conditions' => [
                        'tag_id' => $tag_id,
                        'attribute_id' => $logEntry['data']['id'],
                        'event_id' => $id
                    ]
                ]);
                if (!empty($at)) {
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'AttributeTag', 'action' => 'delete', 'data' => $at['AttributeTag']['id']];
                    } else {
                        $this->Attribute->AttributeTag->delete($at['AttributeTag']['id']);
                    }
                }
                break;
        }
    }

    private function __executeRecoveryShadowAttribute($logEntry, $id)
    {
        if (empty($this->Attribute)) {
            $this->Attribute = ClassRegistry::init('Attribute');
        }
        if (empty($this->ShadowAttribute)) {
            $this->ShadowAttribute = ClassRegistry::init('ShadowAttribute');
        }
        if (!empty($logEntry['data']['value1'])) {
            $logEntry['data']['value'] = $logEntry['data']['value1'];
            if (!empty($logEntry['data']['value2'])) {
                $logEntry .= '|' . $logEntry['data']['value2'];
            }
        }
        switch($logEntry['action']) {
            case 'add':
                $logEntry['data']['value'] = $logEntry['data']['value1'];
                if (!empty($logEntry['data']['value2'])) {
                    $logEntry['data']['value'] .= '|' . $logEntry['data']['value2'];
                }
                $logEntry['data'] = $this->Attribute->UTCToISODatetime(['ShadowAttribute' => $logEntry['data']], 'ShadowAttribute');
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->ShadowAttribute->create();
                    $this->ShadowAttribute->save($logEntry['data']);
                }
                break;
            case 'delete':
                $shadow_attribute = $this->ShadowAttribute->find('first', [
                    'recursive' => -1,
                    'conditions' => ['ShadowAttribute.id' => $logEntry['model_id']]
                ]);
                if (!empty($shadow_attribute)) {
                    $shadow_attribute['ShadowAttribute']['deleted'] = 1;
                    $shadow_attribute['ShadowAttribute']['timestamp'] = $logEntry['data']['timestamp'];
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'delete', 'data' => $attribute];
                    } else {
                        $this->ShadowAttribute->save($attribute);
                    }
                }
                break;
            case 'accept':
                $shadow_attribute = $this->ShadowAttribute->find('first', [
                    'recursive' => -1,
                    'conditions' => ['ShadowAttribute.id' => $logEntry['model_id']]
                ]);
                if (!empty($shadow_attribute['ShadowAttribute']['old_id'])) {
                    $attribute = $this->Attribute->find('first', [
                        'conditions' => ['Attribute.id' => $shadow_attribute['ShadowAttribute']['old_id']],
                        'recursive' => -1
                    ]);
                    if (!empty($shadow_attribute['ShadowAttribute']['proposal_to_delete'])) {
                        $attribute['Attribute']['deleted'] = 1;
                    } else {
                        foreach(['category', 'type', 'value', 'to_ids', 'comment', 'first_seen', 'last_seen'] as $field) {
                            if (isset($shadow_attribute['ShadowAttribute'][$field])) {
                                $attribute['Attribute'][$field] = $shadow_attribute['ShadowAttribute'][$field];
                            }
                        }
                    }
                    $attribute['Attribute']['timestamp'] = $logEntry['data']['timestamp'];
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Attribute', 'action' => 'edit', 'data' => $attribute];
                    } else {
                        $this->Attribute->save($attribute);
                    }
                } else {
                    $this->Attribute->create();
                    $attribute = $shadow_attribute['ShadowAttribute'];
                    if (isset($attribute['id'])) {
                        unset($attribute['id']);
                    }
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Attribute', 'action' => 'add', 'data' => $attribute];
                    } else {
                        $this->Attribute->save($attribute);
                    }
                }
                $shadow_attribute['ShadowAttribute']['deleted'] = 1;
                $shadow_attribute['ShadowAttribute']['timestamp'] = $logEntry['data']['timestamp'];
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'delete', 'data' => $shadow_attribute];
                } else {
                    $this->ShadowAttribute->save($shadow_attribute);
                }
                break;
        }
    }

    private function __executeRecoveryObjectReference($logEntry, $id)
    {
        if (empty($this->ObjectReference)) {
            $this->ObjectReference = ClassRegistry::init('ObjectReference');
        }
        switch($logEntry['action']) {
            case 'add':
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'ObjectReference', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->ObjectReference->create();
                    $this->ObjectReference->save($logEntry['data']);
                }
                break;
            case 'edit':
                $objectRef = $this->ObjectReference->find('first', [
                    'recursive' => -1,
                    'conditions' => ['ObjectReference.id' => $logEntry['model_id']]
                ]);
                if (!empty($object)) {
                    foreach ($logEntry['data'] as $field => $value) {
                        $object['ObjectReference'][$field] = $value;
                    }
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'ObjectReference', 'action' => 'edit', 'data' => $object];
                    } else {
                        $this->ObjectReference->save($object);
                    }
                }
                break;
        }
    }

    private function __executeRecoveryMispObject($logEntry)
    {
        if (empty($this->Attribute)) {
            $this->Attribute = ClassRegistry::init('Attribute');
        }
        if (empty($this->MispObject)) {
            $this->MispObject = ClassRegistry::init('MispObject');
        }
        switch($logEntry['action']) {
            case 'add':
                $logEntry['data'] = $this->MispObject->Attribute->UTCToISODatetime(['Object' => $logEntry['data']], 'Object');
                $logEntry['data'] = $logEntry['data']['Object'];
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'MispObject', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->MispObject->create();
                    $this->MispObject->save($logEntry['data']);
                }
                break;
            case 'edit':
                $logEntry['data'] = $this->MispObject->Attribute->UTCToISODatetime(['Object' => $logEntry['data']], 'Object');
                $logEntry['data'] = $logEntry['data']['Object'];
                $object = $this->MispObject->find('first', [
                    'recursive' => -1,
                    'conditions' => ['Object.id' => $logEntry['model_id']]
                ]);
                if (!empty($object)) {
                    foreach ($logEntry['data'] as $field => $value) {
                        $object['Object'][$field] = $value;
                    }
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'MispObject', 'action' => 'add', 'data' => $object];
                    } else {
                        $this->MispObject->save($object);
                    }
                }
                break;
        }
    }
}


Fixed version (ground truth):
<?php

App::uses('AppModel', 'Model');

class Log extends AppModel
{
    public $warningActions = array(
        'warning',
        'change_pw',
        'login_fail',
        'version_warning',
        'auth_fail'
    );
    public $errorActions = array(
        'error'
    );
    public $validate = array(
            'action' => array(
            'rule' => array(
                'inList',
                array( // ensure that the length of the rules is < 20 in length
                    'accept',
                    'accept_delegation',
                    'acceptRegistrations',
                    'add',
                    'admin_email',
                    'attachTags',
                    'auth',
                    'auth_fail',
                    'blocklisted',
                    'captureRelations',
                    'change_pw',
                    'delete',
                    'disable',
                    'discard',
                    'discardRegistrations',
                    'edit',
                    'email',
                    'enable',
                    'enrichment',
                    'error',
                    'export',
                    'fetchEvent',
                    'file_upload',
                    'galaxy',
                    'include_formula',
                    'login',
                    'login_fail',
                    'logout',
                    'merge',
                    'pruneUpdateLogs',
                    'publish',
                    'publish_sightings',
                    'publish alert',
                    'pull',
                    'purge_events',
                    'push',
                    'registration',
                    'registration_error',
                    'remove_dead_workers',
                    'request',
                    'request_delegation',
                    'reset_auth_key',
                    'send_mail',
                    'security',
                    'serverSettingsEdit',
                    'tag',
                    'undelete',
                    'update',
                    'update_database',
                    'update_db_worker',
                    'upgrade_24',
                    'upload_sample',
                    'version_warning',
                    'warning',
                    'wipe_default'
                )
            ),
            'message' => 'Options : ...'
        )
    );

    public $actionDefinitions = array(
        'login' => array('desc' => 'Login action', 'formdesc' => "Login action"),
        'logout' => array('desc' => 'Logout action', 'formdesc' => "Logout action"),
        'add' => array('desc' => 'Add action', 'formdesc' => "Add action"),
        'edit' => array('desc' => 'Edit action', 'formdesc' => "Edit action"),
        'change_pw' => array('desc' => 'Change_pw action', 'formdesc' => "Change_pw action"),
        'delete' => array('desc' => 'Delete action', 'formdesc' => "Delete action"),
        'publish' => array('desc' => "Publish action", 'formdesc' => "Publish action")
    );

    public $logMeta = array(
        'email' => array('values' => array('email'), 'name' => 'Emails'),
        'auth_issues' => array('values' => array('login_fail', 'auth_fail'), 'name' => 'Authentication issues')
    );

    public $logMetaAdmin = array(
        'update' => array('values' => array('update_database'), 'name' => 'MISP Update results'),
        'settings' => array('values' => array('serverSettingsEdit', 'remove_dead_workers'), 'name' => 'Setting changes'),
        'errors' => array('values' => array('warning', 'error', 'version_warning'), 'name' => 'Warnings and errors'),
        'email' => array('values' => array('admin_email'))
    );

    /**
     * Null when not defined, false when not enabled
     * @var Syslog|null|false
     */
    private $syslog;

    public function beforeSave($options = array())
    {
        if (!empty(Configure::read('MISP.log_skip_db_logs_completely'))) {
            return false;
        }
        if (Configure::read('MISP.log_client_ip')) {
            $ip_header = 'REMOTE_ADDR';
            if (Configure::read('MISP.log_client_ip_header')) {
                $ip_header = Configure::read('MISP.log_client_ip_header');
            }

            if (isset($_SERVER[$ip_header])) {
                $this->data['Log']['ip'] = $_SERVER[$ip_header];
            }
        }
        $setEmpty = array('title' => '', 'model' => '', 'model_id' => 0, 'action' => '', 'user_id' => 0, 'change' => '', 'email' => '', 'org' => '', 'description' => '', 'ip' => '');
        foreach ($setEmpty as $field => $empty) {
            if (!isset($this->data['Log'][$field]) || empty($this->data['Log'][$field])) {
                $this->data['Log'][$field] = $empty;
            }
        }
        if (!isset($this->data['Log']['created'])) {
            $this->data['Log']['created'] = date('Y-m-d H:i:s');
        }
        if (!isset($this->data['Log']['org']) || empty($this->data['Log']['org'])) {
            $this->data['Log']['org'] = 'SYSTEM';
        }
        $truncate_fields = array('title', 'change', 'description');
        foreach ($truncate_fields as $tf) {
            if (isset($this->data['Log'][$tf]) && strlen($this->data['Log'][$tf]) >= 65535) {
                $this->data['Log'][$tf] = substr($this->data['Log'][$tf], 0, 65532) . '...';
            }
        }
        $this->logData($this->data);
        if ($this->data['Log']['action'] === 'request' && !empty(Configure::read('MISP.log_paranoid_skip_db'))) {
            return false;
        }
        return true;
    }

    public function returnDates($org = 'all')
    {
        $dataSourceConfig = ConnectionManager::getDataSource('default')->config;
        $dataSource = $dataSourceConfig['datasource'];
        $conditions = array();
        $this->Organisation = ClassRegistry::init('Organisation');
        if ($org !== 'all') {
            $org = $this->Organisation->fetchOrg($org);
            if (empty($org)) {
                throw new MethodNotAllowedException('Invalid organisation.');
            }
            $conditions['org'] = $org['name'];
        }
        $conditions['AND']['NOT'] = array('action' => array('login', 'logout', 'changepw'));
        if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
            $validDates = $this->find('all', array(
                    'fields' => array('DISTINCT UNIX_TIMESTAMP(DATE(created)) AS Date', 'count(id) AS count'),
                    'conditions' => $conditions,
                    'group' => array('Date'),
                    'order' => array('Date')
            ));
        } elseif ($dataSource == 'Database/Postgres') {
            // manually generate the query for Postgres
            // cakephp ORM would escape "DATE" datatype in CAST expression
            $condnotinaction = "'" . implode("', '", $conditions['AND']['NOT']['action']) . "'";
            if (!empty($conditions['org'])) {
                $condOrg = sprintf('AND org = %s', $this->getDataSource()->value($conditions['org']));
            } else {
                $condOrg = '';
            }
            $sql = 'SELECT DISTINCT EXTRACT(EPOCH FROM CAST(created AS DATE)) AS "Date",
                                    COUNT(id) AS count
                    FROM logs
                    WHERE action NOT IN (' . $condnotinaction . ')
                    ' . $condOrg . '
                    GROUP BY "Date" ORDER BY "Date"';
            $validDates = $this->query($sql);
        }
        $data = array();
        foreach ($validDates as $k => $date) {
            $data[$date[0]['Date']] = intval($date[0]['count']);
        }
        return $data;
    }

    /**
     * @param string|array $user
     * @param string $action
     * @param string $model
     * @param int $modelId
     * @param string $title
     * @param string|array $change
     * @return array|null
     * @throws Exception
     * @throws InvalidArgumentException
     */
    public function createLogEntry($user, $action, $model, $modelId = 0, $title = '', $change = '')
    {
        if (in_array($action, ['tag', 'galaxy', 'publish', 'publish_sightings'], true) && Configure::read('MISP.log_new_audit')) {
            return; // Do not store tag changes when new audit is enabled
        }
        if ($user === 'SYSTEM') {
            $user = array('Organisation' => array('name' => 'SYSTEM'), 'email' => 'SYSTEM', 'id' => 0);
        } else if (!is_array($user)) {
            throw new InvalidArgumentException("User must be array or 'SYSTEM' string.");
        }

        if (is_array($change)) {
            $output = array();
            foreach ($change as $field => $values) {
                $isSecret = strpos($field, 'password') !== false || ($field === 'authkey' && Configure::read('Security.do_not_log_authkeys'));
                if ($isSecret) {
                    $oldValue = $newValue = "*****";
                } else {
                    list($oldValue, $newValue) = $values;
                }
                $output[] = "$field ($oldValue) => ($newValue)";
            }
            $change = implode(", ", $output);
        }

        $this->create();
        $result = $this->save(array(
            'org' => $user['Organisation']['name'],
            'email' => $user['email'],
            'user_id' => $user['id'],
            'action' => $action,
            'title' => $title,
            'change' => $change,
            'model' => $model,
            'model_id' => $modelId,
        ));

        if (!$result) {
            if ($action === 'request' && !empty(Configure::read('MISP.log_paranoid_skip_db'))) {
                return null;
            }

            throw new Exception("Cannot save log because of validation errors: " . json_encode($this->validationErrors));
        }

        return $result;
    }

    // to combat a certain bug that causes the upgrade scripts to loop without being able to set the correct version
    // this function remedies a fixed upgrade bug instance by eliminating the massive number of erroneous upgrade log entries
    public function pruneUpdateLogs($jobId = false, $user)
    {
        $max = $this->find('first', array('fields' => array('MAX(id) AS lastid')));
        if (!empty($max)) {
            $max = $max[0]['lastid'];
        }
        if ($jobId) {
            $this->Job = ClassRegistry::init('Job');
            $this->Job->id = $jobId;
            if (!$this->Job->exists()) {
                $jobId = false;
            }
        }
        $iterations = ($max / 1000);
        for ($i = 0; $i < $iterations; $i++) {
            $this->deleteAll(array(
                'OR' => array(
                        'action' => 'update_database',
                        'AND' => array(
                            'action' => 'edit',
                            'model' => 'AdminSetting'
                        )
                ),
                'id >' => $i * 1000,
                'id <' => ($i+1) * 1000));
            if ($jobId) {
                $this->Job->saveField('progress', $i * 100 / $iterations);
            }
        }
        $this->create();
        $this->save(array(
                'org' => $user['Organisation']['name'],
                'email' =>$user['email'],
                'user_id' => $user['id'],
                'action' => 'pruneUpdateLogs',
                'title' => 'Pruning updates',
                'change' => 'Pruning completed in ' . $i . ' iteration(s).',
                'model' => 'Log',
                'model_id' => 0
        ));
    }


    public function pruneUpdateLogsRouter($user)
    {
        if (Configure::read('MISP.background_jobs')) {
            $job = ClassRegistry::init('Job');
            $job->create();
            $data = array(
                    'worker' => 'default',
                    'job_type' => 'prune_update_logs',
                    'job_input' => 'All update entries',
                    'status' => 0,
                    'retries' => 0,
                    'org_id' => $user['org_id'],
                    'org' => $user['Organisation']['name'],
                    'message' => 'Purging the heretic.',
            );
            $job->save($data);
            $jobId = $job->id;
            $process_id = CakeResque::enqueue(
                    'default',
                    'AdminShell',
                    array('prune_update_logs', $jobId, $user['id']),
                    true
            );
            $job->saveField('process_id', $process_id);
            return $process_id;
        } else {
            $result = $this->pruneUpdateLogs(false, $user);
            return $result;
        }
    }

    public function logData($data)
    {
        if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_audit_notifications_enable')) {
            $pubSubTool = $this->getPubSubTool();
            $pubSubTool->publish($data, 'audit', 'log');
        }

        $this->publishKafkaNotification('audit', $data, 'log');

        if (Configure::read('Plugin.ElasticSearch_logging_enable')) {
            // send off our logs to distributed /dev/null
            $logIndex = Configure::read("Plugin.ElasticSearch_log_index");
            $elasticSearchClient = $this->getElasticSearchTool();
            $elasticSearchClient->pushDocument($logIndex, "log", $data);
        }

        // Do not save request action logs to syslog, because they contain no information
        if ($data['Log']['action'] === 'request') {
            return true;
        }

        // write to syslogd as well if enabled
        if ($this->syslog === null) {
            if (Configure::read('Security.syslog')) {
                $options = [];
                $syslogToStdErr = Configure::read('Security.syslog_to_stderr');
                if ($syslogToStdErr !== null) {
                    $options['to_stderr'] = $syslogToStdErr;
                }
                $syslogIdent = Configure::read('Security.syslog_ident');
                if ($syslogIdent) {
                    $options['ident'] = $syslogIdent;
                }
                $this->syslog = new SysLog($options);
            } else {
                $this->syslog = false;
            }
        }
        if ($this->syslog) {
            $action = 'info';
            if (isset($data['Log']['action'])) {
                if (in_array($data['Log']['action'], $this->errorActions)) {
                    $action = 'err';
                }
                if (in_array($data['Log']['action'], $this->warningActions)) {
                    $action = 'warning';
                }
            }

            $entry = $data['Log']['action'];
            if (!empty($data['Log']['title'])) {
                $entry .= " -- {$data['Log']['title']}";
            }
            if (!empty($data['Log']['description'])) {
                $entry .= " -- {$data['Log']['description']}";
            }
            $this->syslog->write($action, $entry);
        }
        return true;
    }

    public function filterSiteAdminSensitiveLogs($list)
    {
        $this->User = ClassRegistry::init('User');
        $site_admin_roles = $this->User->Role->find('list', array(
            'recursive' => -1,
            'conditions' => array('Role.perm_site_admin' => 1),
            'fields' => array('Role.id', 'Role.id')
        ));
        $site_admins = $this->User->find('list', array(
            'recursive' => -1,
            'conditions' => array(
                'User.role_id' => array_values($site_admin_roles)
            ),
            'fields' => array('User.id', 'User.id')
        ));
        foreach ($list as $k => $v) {
            if (
                $v['Log']['model'] === 'User' &&
                in_array($v['Log']['model_id'], array_values($site_admins)) &&
                in_array($v['Log']['action'], array('add', 'edit', 'reset_auth_key'))
            ) {
                $list[$k]['Log']['change'] = __('Redacted');
            }
        }
        return $list;
    }

    public function changeParser($change)
    {
        $change = explode(',', $change);
        $data = [];
        foreach ($change as $entry) {
            $entry = trim($entry);
            $fieldName = explode(' ', $entry)[0];
            $entry = substr($entry, strlen($fieldName));
            $entry = trim($entry);
            if (strpos($entry, ') => (')) {
                list($before, $after) = explode(') => (', $entry);
                $before = substr($before, 1);
                $after = substr($after, 0, -1);
                $data[$fieldName] = $after;
            }
        }
        return $data;
    }

    public function findDeletedEvents($conditions)
    {
        $conditions['model'] = 'Event';
        $conditions['action'] = 'delete';
        $this->Event = ClassRegistry::init('Event');
        $deletions = $this->find('all', [
            'recursive' => -1,
            'conditions' => $conditions,
            'order' => ['Log.id']
        ]);
        $deleted_events = [];
        $users = [];
        $orgs = [];
        $deleted_event_ids = [];
        foreach ($deletions as $deletion_entry) {
            if (!empty($deleted_event_ids[$deletion_entry['Log']['model_id']])) {
                continue;
            } else {
                $deleted_event_ids[$deletion_entry['Log']['model_id']] = true;
            }
            $event = $this->Event->find('first', [
                'conditions' => ['Event.id' => $deletion_entry['Log']['model_id']],
                'recursive' => -1,
                'fields' => ['Event.id']
            ]);
            if (!empty($event)) {
                // event is already restored / not deleted
                continue;
            }
            $temp = [
                'event_id' => $deletion_entry['Log']['model_id'],
                'user_id' => $deletion_entry['Log']['user_id'],
                'created' => $deletion_entry['Log']['created']
            ];
            $event_creation_entry = $this->find('first', [
                'recursive' => -1,
                'conditions' => [
                    'model_id' => $temp['event_id'],
                    'model' => 'Event',
                    'action' => 'add'
                ]
            ]);
            $event = $this->changeParser($event_creation_entry['Log']['change']);
            $temp['event_info'] = $event['info'];
            $temp['event_org_id'] = $event['org_id'];
            $temp['event_orgc_id'] = $event['orgc_id'];
            $temp['event_user_id'] = $event['user_id'];
            $temp['event_info'] = $event['info'];
            $temp['event_created'] = $event_creation_entry['Log']['created'];
            foreach (['org', 'orgc'] as $scope) {
                if (empty($orgs[$temp['event_' . $scope . '_id']])) {
                    $orgs[$temp['event_' . $scope . '_id']] = array_values($this->Event->Orgc->find('list', [
                        'recursive' => -1,
                        'conditions' => array('id' => $temp['event_' . $scope . '_id']),
                        'fields' => array('id', 'name')
                    ]))[0];
                }
                $temp['event_' . $scope . '_name'] = $orgs[$temp['event_' . $scope . '_id']];
            }
            $users[$temp['user_id']] = array_values($this->Event->User->find('list', [
                'recursive' => -1,
                'conditions' => array('id' => $temp['user_id']),
                'fields' => array('id', 'email')
            ]))[0];
            $temp['user_name'] = $users[$temp['user_id']];
            $users[$temp['event_user_id']] = array_values($this->Event->User->find('list', [
                'recursive' => -1,
                'conditions' => array('id' => $temp['event_user_id']),
                'fields' => array('id', 'email')
            ]))[0];
            $temp['event_user_name'] = $users[$temp['event_user_id']];
            $deleted_events[] = $temp;
        }
        return $deleted_events;
    }

    public function recoverDeletedEvent($id, $mock = false)
    {
        if ($mock) {
            $this->mockRecovery = true;
            $this->mockLog = [];
        }
        $objectMap = [];
        $logEntries = [];
        $this->__recoverDeletedEventContainer($id, $objectMap, $logEntries);
        $this->__recoverDeletedObjects($id, $objectMap, $logEntries);
        $this->__recoverDeletedAttributes($id, $objectMap, $logEntries);
        $this->__recoverDeletedObjectReferences($id, $objectMap, $logEntries);
        $this->__recoverDeletedTagConnectors($id, $objectMap, $logEntries, 'Event');
        $this->__recoverDeletedTagConnectors($id, $objectMap, $logEntries, 'Attribute');
        $this->__recoverDeletedProposals($id, $objectMap, $logEntries);
        ksort($logEntries);
        foreach ($logEntries as $logEntry) {
            $this->{'__executeRecovery' . $logEntry['model']}($logEntry, $id);
        }
        return count($logEntries);
        // order: event -> object -> attribute -> object reference -> tag -> galaxy -> shadow_attribute -> sighting
    }

    private function __recoverDeletedEventContainer($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'Event',
                'model_id' => $id,
                'action' => ['add', 'edit', 'publish', 'alert']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
            $objectMap['Event'][$log['Log']['model_id']] = true;
        }
    }

    private function __recoverDeletedObjects($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'MispObject',
                'change LIKE ' => '%event_id () => (' . $id . ')%',
                'action' => ['add']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $objectMap['MispObject'][$log['Log']['model_id']] = true;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'MispObject',
                'model_id' => array_keys($objectMap['MispObject']),
                'action' => ['add', 'edit', 'delete']
            ]
        ]);
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
        }
    }

    private function __recoverDeletedObjectReferences($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'ObjectReference',
                'change LIKE ' => '%event_id () => (' . $id . ')%',
                'action' => ['add']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $objectMap['ObjectReference'][$log['Log']['model_id']] = true;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'ObjectReference',
                'model_id' => array_keys($objectMap['ObjectReference']),
                'action' => ['add', 'edit']
            ]
        ]);
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
        }
    }

    private function __recoverDeletedTagConnectors($id, &$objectMap, &$logEntries, $scope)
    {
        if (empty($objectMap[$scope])) {
            // example: we have no attributes, so we return
            return;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => $scope,
                'action' => ['tag', 'galaxy'],
                'model_id' => array_keys($objectMap[$scope])
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            if ($log['Log']['action'] === 'tag') {
                $temp = explode(' ', $log['Log']['title']);
                $local = ($temp[1] === 'local' ? true : false);
                $tag_id = ($local ? $temp[3] : $temp[2]);
                $tag_id = substr($tag_id, 1, -1);
            } else {
                $matches = [];
                preg_match('/\(([0-9]*)\)\s(from|to)/', $log['Log']['title'], $matches);
                if (!isset($matches[1])) {
                    continue;
                }
                $local = false;
                $tag_id = $matches[1];
            }
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => (strpos($log['Log']['title'], 'Attached')) === false ? 'remove_tag' : 'add_tag',
                'data' => [
                    'tag_id' => $tag_id,
                    'id' => $log['Log']['model_id'],
                    'target_type' => $log['Log']['model'],
                    'tag_type' => $log['Log']['action'],
                    'local' => $local
                ]
            ];
        }
    }

    private function __recoverDeletedAttributes($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'Attribute',
                'title LIKE ' => '%from Event (' . $id . ')%',
                'action' => ['add']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $objectMap['Attribute'][$log['Log']['model_id']] = true;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'Attribute',
                'model_id' => array_keys($objectMap['Attribute']),
                'action' => ['add', 'edit', 'delete']
            ]
        ]);
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
            $objectMap['Attribute'][$log['Log']['model_id']] = true;
        }
    }

    private function __recoverDeletedProposals($id, &$objectMap, &$logEntries)
    {
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'ShadowAttribute',
                'title LIKE ' => '%: to Event (' . $id . '): %',
                'action' => ['add']
            ]
        ]);
        if (empty($logs)) {
            return;
        }
        foreach ($logs as $log) {
            $objectMap['ShadowAttribute'][$log['Log']['model_id']] = true;
        }
        $logs = $this->find('all', [
            'recursive' => -1,
            'conditions' => [
                'model' => 'ShadowAttribute',
                'model_id' => array_keys($objectMap['ShadowAttribute']),
                'action' => ['add', 'accept', 'delete']
            ]
        ]);
        foreach ($logs as $log) {
            $logEntries[$log['Log']['id']] = [
                'model_id' => $log['Log']['model_id'],
                'model' => $log['Log']['model'],
                'action' => $log['Log']['action'],
                'data' => array_merge(
                    $this->changeParser($log['Log']['change']),
                    [
                        'timestamp' => strtotime($log['Log']['created']),
                        'id' => $log['Log']['model_id']
                    ]
                )
            ];
            $objectMap['ShadowAttribute'][$log['Log']['model_id']] = true;
        }
    }


    private function __executeRecoveryEvent($logEntry, $id)
    {
        if (empty($this->Event)) {
            $this->Event = ClassRegistry::init('Event');
        }
        if (empty($this->GalaxyCluster)) {
            $this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
        }
        if (empty($this->EventBlocklist)) {
            $this->EventBlocklist = ClassRegistry::init('EventBlocklist');
        }
        switch($logEntry['action']) {
            case 'add':
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'Event', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->Event->create();
                    $this->Event->save($logEntry['data']);
                    $blockListEntry = $this->EventBlocklist->find('first', array(
                        'conditions' => array('event_uuid' => $logEntry['data']['uuid']),
                        'fields' => 'id'
                    ));
                    if (!empty($blockListEntry)) {
                        $this->EventBlocklist->delete($blockListEntry['EventBlocklist']['id']);
                    }
                }
                break;
            case 'edit':
            case 'publish':
                $event = $this->Event->find('first', [
                    'recursive' => -1,
                    'conditions' => ['Event.id' => $logEntry['model_id']]
                ]);
                if (!empty($event)) {
                    if ($logEntry['action'] === 'publish' || $logEntry['action'] === 'alert') {
                        $event['Event']['published'] = 1;
                        $event['Event']['publish_timestamp'] = strtotime($logEntry['data']['timestamp']);
                    } else {
                        foreach ($logEntry['data'] as $field => $value) {
                            $event['Event'][$field] = $value;
                        }
                    }
                    $this->Event->create();
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Event', 'action' => 'edit', 'data' => $event];
                    } else {
                        $this->Event->save($event);
                    }
                }
                break;
            case 'add_tag':
                $tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
                $this->Event->EventTag->create();
                $this->Event->create();
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'EventTag', 'action' => 'add', 'data' => [
                        'tag_id' => $tag_id,
                        'event_id' => $logEntry['data']['id'],
                        'local' => !empty($logEntry['data']['local'])
                    ]];
                } else {
                    $this->Event->EventTag->save([
                        'tag_id' => $tag_id,
                        'event_id' => $logEntry['data']['id'],
                        'local' => !empty($logEntry['data']['local'])
                    ]);
                }
                break;
            case 'remove_tag':
                $tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
                $et = $this->Event->EventTag->find('first', [
                    'recursive' => -1,
                    'conditions' => [
                        'tag_id' => $tag_id,
                        'event_id' => $logEntry['data']['id']
                    ]
                ]);
                if (!empty($et)) {
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'EventTag', 'action' => 'delete', 'data' => $et['EventTag']['id']];
                    } else {
                        $this->Event->EventTag->delete($et['EventTag']['id']);
                    }
                }
                break;
        }
    }

    private function __executeRecoveryAttribute($logEntry, $id)
    {
        if (empty($this->Attribute)) {
            $this->Attribute = ClassRegistry::init('Attribute');
        }
        if (empty($this->GalaxyCluster)) {
            $this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
        }
        if (!empty($logEntry['data']['value1'])) {
            $logEntry['data']['value'] = $logEntry['data']['value1'];
            if (!empty($logEntry['data']['value2'])) {
                $logEntry .= '|' . $logEntry['data']['value2'];
            }
        }
        switch($logEntry['action']) {
            case 'add':
                $logEntry['data'] = $this->Attribute->UTCToISODatetime(['Attribute' => $logEntry['data']], 'Attribute');
                $logEntry['data'] = $logEntry['data']['Attribute'];
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'Attribute', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->Attribute->create();
                    if (!isset($logEntry['data']['to_ids'])) {
                        $logEntry['data']['to_ids'] = 0;
                    }
                    $this->Attribute->save($logEntry['data']);
                }
                break;
            case 'edit':
                $attribute = $this->Attribute->find('first', [
                    'recursive' => -1,
                    'conditions' => ['Attribute.id' => $logEntry['model_id']]
                ]);
                if (!empty($attribute)) {
                    $logEntry['data'] = $this->Attribute->UTCToISODatetime(['Attribute' => $logEntry['data']], 'Attribute');
                    $logEntry['data'] = $logEntry['data']['Attribute'];
                    foreach ($logEntry['data'] as $field => $value) {
                        $attribute['Attribute'][$field] = $value;
                    }
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Attribute', 'action' => 'edit', 'data' => $attribute];
                    } else {
                        $this->Attribute->save($attribute);
                    }
                }
                break;
            case 'delete':
                $attribute = $this->Attribute->find('first', [
                    'recursive' => -1,
                    'conditions' => ['Attribute.id' => $logEntry['model_id']]
                ]);
                if (!empty($attribute)) {
                    $attribute['Attribute']['deleted'] = 1;
                    $attribute['Attribute']['timestamp'] = $logEntry['data']['timestamp'];
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Attribute', 'action' => 'delete', 'data' => $attribute];
                    } else {
                        $this->Attribute->save($attribute);
                    }
                }
                break;
            case 'add_tag':
                $tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'AttributeTag', 'action' => 'add', 'data' => [
                        'tag_id' => $tag_id,
                        'attribute_id' => $logEntry['data']['id'],
                        'event_id' => $id,
                        'local' => !empty($logEntry['data']['local'])
                    ]];
                } else {
                    $this->Attribute->AttributeTag->create();
                    $this->Attribute->AttributeTag->save([
                        'tag_id' => $tag_id,
                        'attribute_id' => $logEntry['data']['id'],
                        'event_id' => $id,
                        'local' => !empty($logEntry['data']['local'])
                    ]);
                }
                break;
            case 'remove_tag':
                $tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
                $at = $this->Attribute->AttributeTag->find('first', [
                    'recursive' => -1,
                    'conditions' => [
                        'tag_id' => $tag_id,
                        'attribute_id' => $logEntry['data']['id'],
                        'event_id' => $id
                    ]
                ]);
                if (!empty($at)) {
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'AttributeTag', 'action' => 'delete', 'data' => $at['AttributeTag']['id']];
                    } else {
                        $this->Attribute->AttributeTag->delete($at['AttributeTag']['id']);
                    }
                }
                break;
        }
    }

    private function __executeRecoveryShadowAttribute($logEntry, $id)
    {
        if (empty($this->Attribute)) {
            $this->Attribute = ClassRegistry::init('Attribute');
        }
        if (empty($this->ShadowAttribute)) {
            $this->ShadowAttribute = ClassRegistry::init('ShadowAttribute');
        }
        if (!empty($logEntry['data']['value1'])) {
            $logEntry['data']['value'] = $logEntry['data']['value1'];
            if (!empty($logEntry['data']['value2'])) {
                $logEntry .= '|' . $logEntry['data']['value2'];
            }
        }
        switch($logEntry['action']) {
            case 'add':
                $logEntry['data']['value'] = $logEntry['data']['value1'];
                if (!empty($logEntry['data']['value2'])) {
                    $logEntry['data']['value'] .= '|' . $logEntry['data']['value2'];
                }
                $logEntry['data'] = $this->Attribute->UTCToISODatetime(['ShadowAttribute' => $logEntry['data']], 'ShadowAttribute');
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->ShadowAttribute->create();
                    $this->ShadowAttribute->save($logEntry['data']);
                }
                break;
            case 'delete':
                $shadow_attribute = $this->ShadowAttribute->find('first', [
                    'recursive' => -1,
                    'conditions' => ['ShadowAttribute.id' => $logEntry['model_id']]
                ]);
                if (!empty($shadow_attribute)) {
                    $shadow_attribute['ShadowAttribute']['deleted'] = 1;
                    $shadow_attribute['ShadowAttribute']['timestamp'] = $logEntry['data']['timestamp'];
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'delete', 'data' => $attribute];
                    } else {
                        $this->ShadowAttribute->save($attribute);
                    }
                }
                break;
            case 'accept':
                $shadow_attribute = $this->ShadowAttribute->find('first', [
                    'recursive' => -1,
                    'conditions' => ['ShadowAttribute.id' => $logEntry['model_id']]
                ]);
                if (!empty($shadow_attribute['ShadowAttribute']['old_id'])) {
                    $attribute = $this->Attribute->find('first', [
                        'conditions' => ['Attribute.id' => $shadow_attribute['ShadowAttribute']['old_id']],
                        'recursive' => -1
                    ]);
                    if (!empty($shadow_attribute['ShadowAttribute']['proposal_to_delete'])) {
                        $attribute['Attribute']['deleted'] = 1;
                    } else {
                        foreach(['category', 'type', 'value', 'to_ids', 'comment', 'first_seen', 'last_seen'] as $field) {
                            if (isset($shadow_attribute['ShadowAttribute'][$field])) {
                                $attribute['Attribute'][$field] = $shadow_attribute['ShadowAttribute'][$field];
                            }
                        }
                    }
                    $attribute['Attribute']['timestamp'] = $logEntry['data']['timestamp'];
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Attribute', 'action' => 'edit', 'data' => $attribute];
                    } else {
                        $this->Attribute->save($attribute);
                    }
                } else {
                    $this->Attribute->create();
                    $attribute = $shadow_attribute['ShadowAttribute'];
                    if (isset($attribute['id'])) {
                        unset($attribute['id']);
                    }
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'Attribute', 'action' => 'add', 'data' => $attribute];
                    } else {
                        $this->Attribute->save($attribute);
                    }
                }
                $shadow_attribute['ShadowAttribute']['deleted'] = 1;
                $shadow_attribute['ShadowAttribute']['timestamp'] = $logEntry['data']['timestamp'];
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'delete', 'data' => $shadow_attribute];
                } else {
                    $this->ShadowAttribute->save($shadow_attribute);
                }
                break;
        }
    }

    private function __executeRecoveryObjectReference($logEntry, $id)
    {
        if (empty($this->ObjectReference)) {
            $this->ObjectReference = ClassRegistry::init('ObjectReference');
        }
        switch($logEntry['action']) {
            case 'add':
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'ObjectReference', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->ObjectReference->create();
                    $this->ObjectReference->save($logEntry['data']);
                }
                break;
            case 'edit':
                $objectRef = $this->ObjectReference->find('first', [
                    'recursive' => -1,
                    'conditions' => ['ObjectReference.id' => $logEntry['model_id']]
                ]);
                if (!empty($object)) {
                    foreach ($logEntry['data'] as $field => $value) {
                        $object['ObjectReference'][$field] = $value;
                    }
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'ObjectReference', 'action' => 'edit', 'data' => $object];
                    } else {
                        $this->ObjectReference->save($object);
                    }
                }
                break;
        }
    }

    private function __executeRecoveryMispObject($logEntry)
    {
        if (empty($this->Attribute)) {
            $this->Attribute = ClassRegistry::init('Attribute');
        }
        if (empty($this->MispObject)) {
            $this->MispObject = ClassRegistry::init('MispObject');
        }
        switch($logEntry['action']) {
            case 'add':
                $logEntry['data'] = $this->MispObject->Attribute->UTCToISODatetime(['Object' => $logEntry['data']], 'Object');
                $logEntry['data'] = $logEntry['data']['Object'];
                if (!empty($this->mockRecovery)) {
                    $this->mockLog[] = ['model' => 'MispObject', 'action' => 'add', 'data' => $logEntry['data']];
                } else {
                    $this->MispObject->create();
                    $this->MispObject->save($logEntry['data']);
                }
                break;
            case 'edit':
                $logEntry['data'] = $this->MispObject->Attribute->UTCToISODatetime(['Object' => $logEntry['data']], 'Object');
                $logEntry['data'] = $logEntry['data']['Object'];
                $object = $this->MispObject->find('first', [
                    'recursive' => -1,
                    'conditions' => ['Object.id' => $logEntry['model_id']]
                ]);
                if (!empty($object)) {
                    foreach ($logEntry['data'] as $field => $value) {
                        $object['Object'][$field] = $value;
                    }
                    if (!empty($this->mockRecovery)) {
                        $this->mockLog[] = ['model' => 'MispObject', 'action' => 'add', 'data' => $object];
                    } else {
                        $this->MispObject->save($object);
                    }
                }
                break;
        }
    }
}