2016-06-04 01:10:45 +02:00
< ? php
2013-11-07 15:58:29 +01:00
App :: uses ( 'Folder' , 'Utility' );
App :: uses ( 'File' , 'Utility' );
2013-11-06 10:52:18 +01:00
require_once 'AppShell.php' ;
2020-08-18 17:49:29 +02:00
/**
* @ property User $User
* @ property Event $Event
2020-08-18 18:22:17 +02:00
* @ property Job $Job
2020-08-18 17:49:29 +02:00
*/
2013-11-06 10:52:18 +01:00
class EventShell extends AppShell
{
2020-09-01 15:59:38 +02:00
public $uses = array ( 'Event' , 'Post' , 'Attribute' , 'Job' , 'User' , 'Task' , 'Allowedlist' , 'Server' , 'Organisation' );
2020-06-05 07:46:46 +02:00
public $tasks = array ( 'ConfigLoad' );
2015-06-23 17:19:08 +02:00
2020-12-09 17:06:04 +01:00
public function getOptionParser ()
{
$parser = parent :: getOptionParser ();
$parser -> addSubcommand ( 'import' , array (
'help' => __ ( 'Import event from file into MISP.' ),
'parser' => array (
'arguments' => array (
'user_id' => [ 'help' => __ ( 'User ID that will owner of uploaded event.' ), 'required' => true ],
'file' => [ 'help' => __ ( 'Path to JSON MISP file, can be gzipped or bz2 compressed.' ), 'required' => true ],
),
'options' => [
'take-ownership' => [ 'boolean' => true ],
'publish' => [ 'boolean' => true ],
],
)
));
return $parser ;
}
public function import ()
{
list ( $userId , $path ) = $this -> args ;
$user = $this -> User -> getAuthUser ( $userId );
if ( empty ( $user )) {
$this -> error ( " User with ID $userId does not exists. " );
}
if ( ! file_exists ( $path )) {
$this -> error ( " File ' $path ' does not exists. " );
}
if ( ! is_readable ( $path )) {
$this -> error ( " File ' $path ' is not readable. " );
}
$pathInfo = pathinfo ( $path );
if ( $pathInfo [ 'extension' ] === 'gz' ) {
$content = file_get_contents ( " compress.zlib:// $path " );
$extension = pathinfo ( $pathInfo [ 'filename' ], PATHINFO_EXTENSION );
} else if ( $pathInfo [ 'extension' ] === 'bz2' ) {
$content = file_get_contents ( " compress.bzip2:// $path " );
$extension = pathinfo ( $pathInfo [ 'filename' ], PATHINFO_EXTENSION );
} else {
$content = file_get_contents ( $path );
$extension = $pathInfo [ 'extension' ];
}
if ( $content === false ) {
$this -> error ( " Could not read content from ' $path '. " );
}
$isXml = $extension === 'xml' ;
$takeOwnership = $this -> param ( 'take_ownership' );
$publish = $this -> param ( 'publish' );
$results = $this -> Event -> addMISPExportFile ( $user , $content , $isXml , $takeOwnership , $publish );
foreach ( $results as $result ) {
if ( is_numeric ( $result [ 'result' ])) {
$this -> out ( " Event # { $result [ 'id' ] } : { $result [ 'info' ] } imported. " );
} else {
$this -> out ( " Could not import event because of validation errors: " . json_encode ( $result [ 'validationIssues' ]));
}
}
}
2020-02-10 15:15:59 +01:00
public function doPublish ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$id = $this -> args [ 0 ];
$this -> Event -> id = $id ;
if ( ! $this -> Event -> exists ()) {
throw new NotFoundException ( __ ( 'Invalid event' ));
}
$this -> Job -> create ();
$data = array (
'worker' => 'default' ,
'job_type' => 'doPublish' ,
'job_input' => $id ,
'status' => 0 ,
'retries' => 0 ,
//'org' => $jobOrg,
'message' => 'Job created.' ,
);
$this -> Job -> save ( $data );
// update the event and set the from field to the current instance's organisation from the bootstrap. We also need to save id and info for the logs.
$this -> Event -> recursive = - 1 ;
$event = $this -> Event -> read ( null , $id );
$event [ 'Event' ][ 'published' ] = 1 ;
$fieldList = array ( 'published' , 'id' , 'info' );
$this -> Event -> save ( $event , array ( 'fieldList' => $fieldList ));
// only allow form submit CSRF protection.
$this -> Job -> saveField ( 'status' , 1 );
$this -> Job -> saveField ( 'message' , 'Job done.' );
}
2016-06-04 01:08:16 +02:00
2020-02-10 15:15:59 +01:00
public function cache ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$timeStart = time ();
$userId = $this -> args [ 0 ];
$id = $this -> args [ 1 ];
$user = $this -> User -> getAuthUser ( $userId );
$this -> Job -> id = $id ;
$export_type = $this -> args [ 2 ];
file_put_contents ( '/tmp/test' , $export_type );
$typeData = $this -> Event -> export_types [ $export_type ];
if ( ! in_array ( $export_type , array_keys ( $this -> Event -> export_types ))) {
$this -> Job -> saveField ( 'progress' , 100 );
$timeDelta = ( time () - $timeStart );
$this -> Job -> saveField ( 'message' , 'Job Failed due to invalid export format. (in ' . $timeDelta . 's)' );
2019-08-29 17:36:47 +02:00
$this -> Job -> saveField ( 'date_modified' , date ( " Y-m-d H:i:s " ));
2019-04-08 18:57:41 +02:00
return false ;
}
if ( $export_type == 'text' ) {
$types = array_keys ( $this -> Attribute -> typeDefinitions );
$typeCount = count ( $types );
foreach ( $types as $k => $type ) {
$typeData [ 'params' ][ 'type' ] = $type ;
$this -> __runCaching ( $user , $typeData , false , $export_type , '_' . $type );
$this -> Job -> saveField ( 'message' , 'Processing all attributes of type ' . $type . '.' );
$this -> Job -> saveField ( 'progress' , intval ( $k / $typeCount ));
}
} else {
$this -> __runCaching ( $user , $typeData , $id , $export_type );
}
$this -> Job -> saveField ( 'progress' , 100 );
$timeDelta = ( time () - $timeStart );
$this -> Job -> saveField ( 'message' , 'Job done. (in ' . $timeDelta . 's)' );
2019-08-29 17:36:47 +02:00
$this -> Job -> saveField ( 'date_modified' , date ( " Y-m-d H:i:s " ));
2019-04-08 18:57:41 +02:00
}
2018-10-05 14:48:02 +02:00
2019-04-08 18:57:41 +02:00
private function __runCaching ( $user , $typeData , $id , $export_type , $subType = '' )
{
2020-02-10 15:15:59 +01:00
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$export_type = strtolower ( $typeData [ 'type' ]);
$final = $this -> { $typeData [ 'scope' ]} -> restSearch ( $user , $typeData [ 'params' ][ 'returnFormat' ], $typeData [ 'params' ], false , $id );
$dir = new Folder ( APP . 'tmp/cached_exports/' . $export_type , true , 0750 );
//echo PHP_EOL . $dir->pwd() . DS . 'misp.' . $export_type . $subType . '.ADMIN' . $typeData['extension'] . PHP_EOL;
if ( $user [ 'Role' ][ 'perm_site_admin' ]) {
$file = new File ( $dir -> pwd () . DS . 'misp.' . $export_type . $subType . '.ADMIN' . $typeData [ 'extension' ]);
} else {
$file = new File ( $dir -> pwd () . DS . 'misp.' . $export_type . $subType . '.' . $user [ 'Organisation' ][ 'name' ] . $typeData [ 'extension' ]);
}
$file -> write ( $final );
$file -> close ();
return true ;
}
2018-10-05 14:48:02 +02:00
2019-04-08 18:57:41 +02:00
public function cachebro ()
{
2020-02-10 15:15:59 +01:00
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$timeStart = time ();
$userId = $this -> args [ 0 ];
$user = $this -> User -> getAuthUser ( $userId );
$id = $this -> args [ 1 ];
$this -> Job -> id = $id ;
$this -> Job -> saveField ( 'progress' , 1 );
App :: uses ( 'BroExport' , 'Export' );
$export = new BroExport ();
$types = array_keys ( $export -> mispTypes );
$typeCount = count ( $types );
$dir = new Folder ( APP . DS . '/tmp/cached_exports/bro' , true , 0750 );
if ( $user [ 'Role' ][ 'perm_site_admin' ]) {
$file = new File ( $dir -> pwd () . DS . 'misp.bro.ADMIN.intel' );
} else {
$file = new File ( $dir -> pwd () . DS . 'misp.bro.' . $user [ 'Organisation' ][ 'name' ] . '.intel' );
}
2016-12-07 16:33:17 +01:00
2019-04-08 18:57:41 +02:00
$file -> write ( '' );
2020-01-31 11:45:23 +01:00
$skipHeader = false ;
2019-04-08 18:57:41 +02:00
foreach ( $types as $k => $type ) {
2020-01-31 11:45:23 +01:00
$final = $this -> Attribute -> bro ( $user , $type , false , false , false , false , false , false , $skipHeader );
$skipHeader = true ;
2019-04-08 18:57:41 +02:00
foreach ( $final as $attribute ) {
$file -> append ( $attribute . PHP_EOL );
}
$this -> Job -> saveField ( 'progress' , $k / $typeCount * 100 );
}
$file -> close ();
$timeDelta = ( time () - $timeStart );
$this -> Job -> saveField ( 'progress' , 100 );
$this -> Job -> saveField ( 'message' , 'Job done. (in ' . $timeDelta . 's)' );
2019-08-29 17:36:47 +02:00
$this -> Job -> saveField ( 'date_modified' , date ( " Y-m-d H:i:s " ));
2019-04-08 18:57:41 +02:00
}
2016-08-23 14:06:06 +02:00
2020-02-10 15:15:59 +01:00
public function alertemail ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$userId = $this -> args [ 0 ];
2020-08-18 18:22:17 +02:00
$jobId = $this -> args [ 1 ];
2019-04-08 18:57:41 +02:00
$eventId = $this -> args [ 2 ];
$oldpublish = $this -> args [ 3 ];
2020-08-18 17:49:29 +02:00
$user = $this -> User -> getUserById ( $userId );
2020-08-12 18:10:57 +02:00
if ( empty ( $user )) {
die ( " Invalid user ID ' $userId ' provided. " );
}
2020-08-18 18:22:17 +02:00
$this -> Event -> sendAlertEmail ( $eventId , $user , $oldpublish , $jobId );
2019-04-08 18:57:41 +02:00
}
2016-06-04 01:08:16 +02:00
2020-02-10 15:15:59 +01:00
public function contactemail ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$id = $this -> args [ 0 ];
$message = $this -> args [ 1 ];
$all = $this -> args [ 2 ];
$userId = $this -> args [ 3 ];
2020-08-18 18:22:17 +02:00
$processId = $this -> args [ 4 ];
2020-08-18 17:49:29 +02:00
$user = $this -> User -> getUserById ( $userId );
2020-08-12 18:10:57 +02:00
if ( empty ( $user )) {
die ( " Invalid user ID ' $userId ' provided. " );
}
2020-08-18 18:22:17 +02:00
$result = $this -> Event -> sendContactEmail ( $id , $message , $all , $user );
$this -> Job -> saveStatus ( $processId , $result );
2019-04-08 18:57:41 +02:00
}
2015-01-27 17:56:50 +01:00
2020-02-10 15:15:59 +01:00
public function postsemail ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$userId = $this -> args [ 0 ];
$postId = $this -> args [ 1 ];
$eventId = $this -> args [ 2 ];
$title = $this -> args [ 3 ];
$message = $this -> args [ 4 ];
$processId = $this -> args [ 5 ];
$this -> Job -> id = $processId ;
$result = $this -> Post -> sendPostsEmail ( $userId , $postId , $eventId , $title , $message );
$job [ 'Job' ][ 'progress' ] = 100 ;
$job [ 'Job' ][ 'message' ] = 'Emails sent.' ;
2019-08-29 17:36:47 +02:00
$job [ 'Job' ][ 'date_modified' ] = date ( " Y-m-d H:i:s " );
2019-04-08 18:57:41 +02:00
$this -> Job -> save ( $job );
}
2016-06-04 01:08:16 +02:00
2020-02-10 15:15:59 +01:00
public function enqueueCaching ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$timestamp = $this -> args [ 0 ];
$task = $this -> Task -> findByType ( 'cache_exports' );
2016-06-04 01:08:16 +02:00
2019-04-08 18:57:41 +02:00
// If the next execution time and the timestamp don't match, it means that this task is no longer valid as the time for the execution has since being scheduled
// been updated.
if ( $task [ 'Task' ][ 'next_execution_time' ] != $timestamp ) return ;
2015-06-03 23:49:37 +02:00
2019-04-08 18:57:41 +02:00
$users = $this -> User -> find ( 'all' , array (
'recursive' => - 1 ,
'conditions' => array (
'Role.perm_site_admin' => 0 ,
'User.disabled' => 0 ,
),
'contain' => array (
'Organisation' => array ( 'fields' => array ( 'name' )),
'Role' => array ( 'fields' => array ( 'perm_site_admin' ))
),
'fields' => array ( 'User.org_id' , 'User.id' ),
'group' => array ( 'User.org_id' )
));
$site_admin = $this -> User -> find ( 'first' , array (
'recursive' => - 1 ,
'conditions' => array (
'Role.perm_site_admin' => 1 ,
'User.disabled' => 0
),
'contain' => array (
'Organisation' => array ( 'fields' => array ( 'name' )),
'Role' => array ( 'fields' => array ( 'perm_site_admin' ))
),
'fields' => array ( 'User.org_id' , 'User.id' )
));
$users [] = $site_admin ;
2016-06-04 01:08:16 +02:00
2019-04-08 18:57:41 +02:00
if ( $task [ 'Task' ][ 'timer' ] > 0 ) $this -> Task -> reQueue ( $task , 'cache' , 'EventShell' , 'enqueueCaching' , false , false );
2016-06-04 01:08:16 +02:00
2019-04-08 18:57:41 +02:00
// Queue a set of exports for admins. This "ADMIN" organisation. The organisation of the admin users doesn't actually matter, it is only used to indentify
// the special cache files containing all events
$i = 0 ;
foreach ( $users as $user ) {
foreach ( $this -> Event -> export_types as $k => $type ) {
if ( $k == 'stix' ) continue ;
$this -> Job -> cache ( $k , $user [ 'User' ]);
$i ++ ;
}
}
$this -> Task -> id = $task [ 'Task' ][ 'id' ];
$this -> Task -> saveField ( 'message' , $i . ' job(s) started at ' . date ( 'd/m/Y - H:i:s' ) . '.' );
}
2016-06-04 01:08:16 +02:00
2020-02-10 15:15:59 +01:00
public function publish ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$id = $this -> args [ 0 ];
$passAlong = $this -> args [ 1 ];
$jobId = $this -> args [ 2 ];
$userId = $this -> args [ 3 ];
$user = $this -> User -> getAuthUser ( $userId );
2020-08-12 18:10:57 +02:00
if ( empty ( $user )) {
die ( " Invalid user ID ' $userId ' provided. " );
}
2019-04-08 18:57:41 +02:00
$job = $this -> Job -> read ( null , $jobId );
$this -> Event -> Behaviors -> unload ( 'SysLogLogable.SysLogLogable' );
$result = $this -> Event -> publish ( $id , $passAlong );
$job [ 'Job' ][ 'progress' ] = 100 ;
2019-08-29 17:36:47 +02:00
$job [ 'Job' ][ 'date_modified' ] = date ( " Y-m-d H:i:s " );
2019-04-08 18:57:41 +02:00
if ( $result ) {
$job [ 'Job' ][ 'message' ] = 'Event published.' ;
} else {
$job [ 'Job' ][ 'message' ] = 'Event published, but the upload to other instances may have failed.' ;
}
$this -> Job -> save ( $job );
$log = ClassRegistry :: init ( 'Log' );
$log -> create ();
$log -> createLogEntry ( $user , 'publish' , 'Event' , $id , 'Event (' . $id . '): published.' , 'published () => (1)' );
}
2014-02-10 00:29:46 +01:00
2020-02-10 15:15:59 +01:00
public function publish_sightings ()
{
$this -> ConfigLoad -> execute ();
2019-11-22 21:53:51 +01:00
$id = $this -> args [ 0 ];
$passAlong = $this -> args [ 1 ];
$jobId = $this -> args [ 2 ];
$userId = $this -> args [ 3 ];
$user = $this -> User -> getAuthUser ( $userId );
2020-08-12 18:10:57 +02:00
if ( empty ( $user )) {
die ( " Invalid user ID ' $userId ' provided. " );
}
2019-11-22 21:53:51 +01:00
$job = $this -> Job -> read ( null , $jobId );
$this -> Event -> Behaviors -> unload ( 'SysLogLogable.SysLogLogable' );
$result = $this -> Event -> publish_sightings ( $id , $passAlong );
$job [ 'Job' ][ 'progress' ] = 100 ;
$job [ 'Job' ][ 'date_modified' ] = date ( " Y-m-d H:i:s " );
if ( $result ) {
$job [ 'Job' ][ 'message' ] = 'Sightings published.' ;
} else {
$job [ 'Job' ][ 'message' ] = 'Sightings published, but the upload to other instances may have failed.' ;
}
$this -> Job -> save ( $job );
$log = ClassRegistry :: init ( 'Log' );
$log -> create ();
$log -> createLogEntry ( $user , 'publish_sightings' , 'Event' , $id , 'Sightings for event (' . $id . '): published.' , 'publish_sightings updated' );
}
2020-06-26 15:48:50 +02:00
public function publish_galaxy_clusters ()
{
$this -> ConfigLoad -> execute ();
$clusterId = $this -> args [ 0 ];
$jobId = $this -> args [ 1 ];
$userId = $this -> args [ 2 ];
$passAlong = $this -> args [ 3 ];
$user = $this -> User -> getAuthUser ( $userId );
$job = $this -> Job -> read ( null , $jobId );
$this -> GalaxyCluster = ClassRegistry :: init ( 'GalaxyCluster' );
$result = $this -> GalaxyCluster -> publish ( $clusterId , $passAlong = $passAlong );
$job [ 'Job' ][ 'progress' ] = 100 ;
$job [ 'Job' ][ 'date_modified' ] = date ( " Y-m-d H:i:s " );
if ( $result ) {
$job [ 'Job' ][ 'message' ] = 'Galaxy cluster published.' ;
} else {
$job [ 'Job' ][ 'message' ] = 'Galaxy cluster published, but the upload to other instances may have failed.' ;
}
$this -> Job -> save ( $job );
$log = ClassRegistry :: init ( 'Log' );
$log -> create ();
$log -> createLogEntry ( $user , 'publish' , 'GalaxyCluster' , $clusterId , 'GalaxyCluster (' . $clusterId . '): published.' , 'published () => (1)' );
}
2020-02-10 15:15:59 +01:00
public function enrichment ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
if ( empty ( $this -> args [ 0 ]) || empty ( $this -> args [ 1 ]) || empty ( $this -> args [ 2 ])) {
die ( 'Usage: ' . $this -> Server -> command_line_functions [ 'enrichment' ] . PHP_EOL );
}
$userId = $this -> args [ 0 ];
$user = $this -> User -> getAuthUser ( $userId );
if ( empty ( $user )) die ( 'Invalid user.' );
$eventId = $this -> args [ 1 ];
2020-06-05 07:46:46 +02:00
$modulesRaw = $this -> args [ 2 ];
2019-04-08 18:57:41 +02:00
try {
2020-06-05 07:46:46 +02:00
$modules = json_decode ( $modulesRaw , true );
2019-04-08 18:57:41 +02:00
} catch ( Exception $e ) {
die ( 'Invalid module JSON' );
}
if ( ! empty ( $this -> args [ 3 ])) {
$jobId = $this -> args [ 3 ];
} else {
$this -> Job -> create ();
$data = array (
'worker' => 'default' ,
'job_type' => 'enrichment' ,
2020-06-05 07:46:46 +02:00
'job_input' => 'Event: ' . $eventId . ' modules: ' . $modulesRaw ,
2019-04-08 18:57:41 +02:00
'status' => 0 ,
'retries' => 0 ,
'org' => $user [ 'Organisation' ][ 'name' ],
'message' => 'Enriching event.' ,
);
$this -> Job -> save ( $data );
$jobId = $this -> Job -> id ;
}
$job = $this -> Job -> read ( null , $jobId );
$options = array (
'user' => $user ,
'event_id' => $eventId ,
'modules' => $modules
);
$result = $this -> Event -> enrichment ( $options );
$job [ 'Job' ][ 'progress' ] = 100 ;
2019-08-29 17:36:47 +02:00
$job [ 'Job' ][ 'date_modified' ] = date ( " Y-m-d H:i:s " );
2019-04-08 18:57:41 +02:00
if ( $result ) {
$job [ 'Job' ][ 'message' ] = 'Added ' . $result . ' attribute' . ( $result > 1 ? 's.' : '.' );
} else {
$job [ 'Job' ][ 'message' ] = 'Enrichment finished, but no attributes added.' ;
}
2020-08-17 17:57:30 +02:00
echo $job [ 'Job' ][ 'message' ] . PHP_EOL ;
2019-04-08 18:57:41 +02:00
$this -> Job -> save ( $job );
$log = ClassRegistry :: init ( 'Log' );
$log -> create ();
$log -> createLogEntry ( $user , 'enrichment' , 'Event' , $eventId , 'Event (' . $eventId . '): enriched.' , 'enriched () => (1)' );
}
2018-09-23 17:44:23 +02:00
2020-02-10 15:15:59 +01:00
public function processfreetext ()
{
$this -> ConfigLoad -> execute ();
2019-04-08 18:57:41 +02:00
$inputFile = $this -> args [ 0 ];
$tempdir = new Folder ( APP . 'tmp/cache/ingest' , true , 0750 );
$tempFile = new File ( APP . 'tmp/cache/ingest' . DS . $inputFile );
$inputData = $tempFile -> read ();
$inputData = json_decode ( $inputData , true );
$tempFile -> delete ();
$this -> Event -> processFreeTextData (
$inputData [ 'user' ],
$inputData [ 'attributes' ],
$inputData [ 'id' ],
$inputData [ 'default_comment' ],
2020-07-20 12:57:45 +02:00
$inputData [ 'proposals' ],
2019-04-08 18:57:41 +02:00
$inputData [ 'adhereToWarninglists' ],
$inputData [ 'jobId' ]
);
return true ;
}
2019-04-08 19:07:13 +02:00
public function processmoduleresult ()
{
2020-02-10 15:15:59 +01:00
$this -> ConfigLoad -> execute ();
2019-04-08 19:07:13 +02:00
$inputFile = $this -> args [ 0 ];
$tempDir = new Folder ( APP . 'tmp/cache/ingest' , true , 0750 );
$tempFile = new File ( APP . 'tmp/cache/ingest' . DS . $inputFile );
$inputData = json_decode ( $tempFile -> read (), true );
$tempFile -> delete ();
$this -> Event -> processModuleResultsData (
$inputData [ 'user' ],
$inputData [ 'misp_format' ],
$inputData [ 'id' ],
$inputData [ 'default_comment' ],
$inputData [ 'jobId' ]
);
return true ;
}
2020-09-16 10:33:05 +02:00
public function recoverEvent ()
{
$this -> ConfigLoad -> execute ();
$jobId = $this -> args [ 0 ];
$id = $this -> args [ 1 ];
$job = $this -> Job -> read ( null , $jobId );
$job [ 'Job' ][ 'progress' ] = 1 ;
$job [ 'Job' ][ 'date_modified' ] = date ( " Y-m-d H:i:s " );
$job [ 'Job' ][ 'message' ] = __ ( 'Recovering event %s' , $id );
$this -> Job -> save ( $job );
$result = $this -> Event -> recoverEvent ( $id );
$job [ 'Job' ][ 'progress' ] = 100 ;
$job [ 'Job' ][ 'date_modified' ] = date ( " Y-m-d H:i:s " );
$job [ 'Job' ][ 'message' ] = __ ( 'Recovery complete. Event #%s recovered, using %s log entries.' , $id , $result );
$this -> Job -> save ( $job );
}
2013-11-06 10:52:18 +01:00
}