Files
IPB/applications/core/modules/admin/developer/tasks.php
T
2025-12-19 16:27:35 -08:00

340 lines
9.4 KiB
PHP

<?php
/**
* @brief tasks
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 31 Jan 2024
*/
namespace IPS\core\modules\admin\developer;
use DateInterval;
use DomainException;
use Exception;
use InvalidArgumentException;
use IPS\DateTime;
use IPS\Db;
use IPS\Developer;
use IPS\Developer\Controller;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Text;
use IPS\Helpers\Table\Custom;
use IPS\Http\Url;
use IPS\Member;
use IPS\Output;
use IPS\Request;
use IPS\Session;
use IPS\Task;
use IPS\Theme;
use OutOfRangeException;
use UnderflowException;
use function defined;
use const IPS\IPS_FOLDER_PERMISSION;
use const IPS\ROOT_PATH;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* tasks
*/
class tasks extends Controller
{
/**
* @var bool
*/
public static bool $csrfProtected = true;
/**
* ...
*
* @return void
*/
protected function manage() : void
{
$json = ROOT_PATH . "/applications/{$this->application->directory}/data/tasks.json";
$data = array();
foreach ( json_decode( file_get_contents( $json ), TRUE ) as $k => $f )
{
$data[ $k ] = array(
'dev_task_key' => $k,
'dev_task_frequency' => $f
);
}
$table = new Custom( $data, $this->url );
$table->quickSearch = 'dev_task_key';
$table->rootButtons = array(
'add' => array(
'icon' => 'plus',
'title' => 'add',
'link' => $this->url->setQueryString( 'do', 'taskForm' ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('add') )
)
);
$url = $this->url;
$table->rowButtons = function( $row ) use ( $url )
{
$task = null;
try
{
$task = Task::load( $row['dev_task_key'], 'key' );
}
catch( OutOfRangeException ){}
$buttons = array();
$fileLink = ROOT_PATH . "/applications/{$this->application->directory}/tasks/{$row['dev_task_key']}.php";
if( $ideLink = Developer::getIdeHref($fileLink ) )
{
$buttons['ide'] = [
'icon' => 'fa-file-code',
'title' => 'open_in_ide',
'link' => $ideLink
];
};
$buttons['edit'] = array(
'icon' => 'pencil',
'title' => 'edit',
'link' => $url->setQueryString( array( 'do' => 'taskForm', 'key' => $row['dev_task_key'] ) ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('edit') )
);
$buttons['delete'] = array(
'icon' => 'times-circle',
'title' => 'delete',
'link' => $url->setQueryString( array( 'do' => 'taskDelete', 'key' => $row['dev_task_key'] ) )->csrf(),
'data' => array( 'delete' => '' )
);
if( $task !== null )
{
if( $task->running )
{
$buttons['unlock'] = [
'icon' => 'unlock',
'title' => 'task_manager_unlock',
'link' => Url::internal( "app=core&module=settings&controller=advanced&do=unlockTask&id={$task->id}" )->csrf()
];
}
else
{
$buttons['run'] = [
'icon' => 'play-circle',
'title' => 'task_manager_run',
'link' => Url::internal( "app=core&module=settings&controller=advanced&do=runTask&id={$task->id}" )->csrf()
];
}
$buttons['logs'] = array(
'icon' => 'search',
'title' => 'task_manager_logs',
'link' => Url::internal( "app=core&module=settings&controller=advanced&do=taskLogs&id={$task->id}" )
);
}
return $buttons;
};
$table->parsers = array(
'dev_task_frequency' => function( $v )
{
$interval = new DateInterval( $v );
$return = array();
foreach ( array( 'y' => 'years', 'm' => 'months', 'd' => 'days', 'h' => 'hours', 'i' => 'minutes', 's' => 'seconds' ) as $k => $v )
{
if ( $interval->$k )
{
$return[] = Member::loggedIn()->language()->addToStack( 'every_x_' . $v, FALSE, array( 'pluralize' => array( $interval->format( '%' . $k ) ) ) );
}
}
return Member::loggedIn()->language()->formatList( $return );
}
);
Output::i()->output = (string) $table;
}
/**
* @return void
*/
protected function taskForm() : void
{
$json = ROOT_PATH . "/applications/{$this->application->directory}/data/tasks.json";
$form = new Form;
$current = NULL;
if ( isset( Request::i()->key ) )
{
$tasks = json_decode( file_get_contents( $json ), TRUE );
if ( array_key_exists( Request::i()->key, $tasks ) )
{
$current = array(
'dev_task_key' => Request::i()->key,
'dev_task_frequency' => new DateInterval( $tasks[ Request::i()->key ] )
);
try
{
$current['id'] = Db::i()->select( 'id', 'core_tasks', array( '`key`=?', Request::i()->key ) )->first();
}
catch( UnderflowException $e ){}
$form->hiddenValues['old'] = $current['dev_task_key'];
}
unset( $tasks );
}
$form->add( new Text( 'dev_task_key', $current ? $current['dev_task_key'] : NULL, TRUE, array( 'maxLength' => 255, 'regex' => '/^[a-z0-9_]*$/i' ), function( $val ) use ( $current )
{
$where = array( array( '`key`=?', $val ) );
if ( isset( $current['id'] ) )
{
$where[] = array( 'id<>?', $current['id'] );
}
if ( Db::i()->select( 'count(*)', 'core_tasks', $where )->first() )
{
throw new DomainException( 'dev_task_key_err' );
}
} ) );
$form->add( new Form\Custom( 'dev_task_frequency', $current ? $current['dev_task_frequency'] : NULL, TRUE, array(
'getHtml' => function( $element )
{
return Theme::i()->getTemplate( 'forms', 'core' )->dateinterval( $element->name, $element->value ?: new DateInterval( 'P0D' ) );
},
'formatValue' => function ( $element )
{
if ( !( $element->value instanceof DateInterval ) )
{
if( !empty($element->value) )
{
try
{
$interval = new DateInterval( "P{$element->value['y']}Y{$element->value['m']}M{$element->value['d']}DT{$element->value['h']}H{$element->value['i']}M{$element->value['s']}S" );
}
catch( Exception $e )
{
$interval = DateInterval::createFromDateString('1 day');
}
}
else
{
$interval = DateInterval::createFromDateString('1 day');
}
return $interval;
}
return $element->value;
}
), function ( $val )
{
foreach ( $val as $k => $v )
{
if ( $v )
{
return;
}
}
throw new InvalidArgumentException( 'form_required' );
} ) );
if ( $values = $form->values() )
{
/* Write PHP file */
$taskDirectory = ROOT_PATH . "/applications/{$this->application->directory}/tasks";
$taskFile = $taskDirectory . "/{$values['dev_task_key']}.php";
if ( isset( $values['old'] ) and $values['old'] !== $values['dev_task_key'] and file_exists( $taskDirectory . "/{$values['old']}.php" ) )
{
@rename( $taskDirectory . "/{$values['old']}.php", $taskFile );
Db::i()->delete( 'core_tasks', array( '`key`=?', $values['old'] ) );
}
if ( !file_exists( $taskFile ) )
{
if ( !is_dir( $taskDirectory ) )
{
mkdir( $taskDirectory );
chmod( $taskDirectory, IPS_FOLDER_PERMISSION);
}
file_put_contents( $taskFile, str_replace(
array(
'{key}',
"{subpackage}\n",
'{date}',
'{namespace}',
),
array(
$values['dev_task_key'],
( $this->application->directory != 'core' ) ? ( " * @subpackage\t" . $this->application->directory . "\n" ) : '',
date( 'd M Y' ),
$this->application->directory . '\tasks',
),
file_get_contents( ROOT_PATH . "/applications/core/data/defaults/Task.txt" )
) );
}
/* Add to DB */
$frequency = "P{$values['dev_task_frequency']->y}Y{$values['dev_task_frequency']->m}M{$values['dev_task_frequency']->d}DT{$values['dev_task_frequency']->h}H{$values['dev_task_frequency']->i}M{$values['dev_task_frequency']->s}S";
Db::i()->replace( 'core_tasks', array(
'app' => $this->application->directory,
'key' => $values['dev_task_key'],
'frequency' => $frequency,
'next_run' => DateTime::create()->add( new DateInterval( $frequency ) )->getTimestamp(),
'running' => 0,
) );
/* Add to JSON file */
$tasks = json_decode( file_get_contents( $json ), TRUE );
$tasks[ $values['dev_task_key'] ] = $frequency;
if ( isset( $values['old'] ) and $values['old'] !== $values['dev_task_key'] and isset( $tasks[ $values['old'] ] ) )
{
unset( $tasks[ $values['old'] ] );
}
file_put_contents( $json, json_encode( $tasks ) );
/* Redirect */
Output::i()->redirect( $this->url, 'saved' );
}
Output::i()->output = (string) $form;
}
/**
* @return void
*/
protected function taskDelete() : void
{
Session::i()->csrfCheck();
$json = ROOT_PATH . "/applications/{$this->application->directory}/data/tasks.json";
$tasks = json_decode( file_get_contents( $json ), TRUE );
if ( array_key_exists( Request::i()->key, $tasks ) )
{
unset( $tasks[ Request::i()->key ] );
file_put_contents( $json, json_encode( $tasks ) );
$taskDirectory = ROOT_PATH . "/applications/{$this->application->directory}/tasks";
if ( file_exists( $taskDirectory . "/" . Request::i()->key . ".php" ) )
{
unlink( $taskDirectory . "/" . Request::i()->key . ".php" );
}
Db::i()->delete( 'core_tasks', array( 'app=? AND `key`=?', $this->application->directory, Request::i()->key ) );
}
Output::i()->redirect( $this->url, 'saved' );
}
}