2012-04-06 10:10:36 +00:00
< ? php
2015-09-25 15:42:00 +00:00
2012-04-06 10:10:36 +00:00
/*
2015-09-25 15:42:00 +00:00
----------------------------------------------------------------------
LICENSE
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License ( GPL )
as published by the Free Software Foundation ; either version 2
of the License , or ( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
To read the license please visit http :// www . gnu . org / copyleft / gpl . html
----------------------------------------------------------------------
2017-10-06 21:42:39 +00:00
*/
2012-08-26 10:30:38 +00:00
2012-04-06 10:10:36 +00:00
/**
2012-08-26 10:30:38 +00:00
* This class manage web - cron tasks
2017-10-08 17:31:34 +00:00
*
* @ copyright AlternC - Team 2000 - 2017 https :// alternc . com /
2012-04-06 10:10:36 +00:00
*/
class m_cron {
2015-09-25 15:42:00 +00:00
const MAX_SOCKETS = 8 ;
const DEFAULT_CAFILE = " /etc/ssl/certs/ca-certificates.crt " ;
2017-10-08 17:31:34 +00:00
/**
*
2015-09-25 15:42:00 +00:00
*/
function schedule () {
return Array (
Array ( 'unit' => 1440 , 'name' => _ ( " Daily " )),
Array ( 'unit' => 60 , 'name' => _ ( " Hour " )),
Array ( 'unit' => 30 , 'name' => _ ( " Half Hour " )),
);
2012-04-06 10:10:36 +00:00
}
2015-09-25 15:42:00 +00:00
2017-10-08 17:31:34 +00:00
/**
* List the crontab for the current user .
2015-09-25 15:42:00 +00:00
* @ return array an hash for each crontab .
*/
function lst_cron () {
2017-08-17 01:32:18 +00:00
global $cuid , $db , $msg ;
2018-06-27 22:27:10 +00:00
$msg -> debug ( " cron " , " lst_cron " );
2016-05-17 15:21:08 +00:00
$db -> query ( " SELECT * FROM cron WHERE uid = ? ORDER BY url; " , array ( $cuid ));
2015-09-25 15:42:00 +00:00
$r = Array ();
while ( $db -> next_record ()) {
$tmp = Array ();
$tmp [ 'id' ] = $db -> f ( 'id' );
$tmp [ 'url' ] = urldecode ( $db -> f ( 'url' ));
$tmp [ 'user' ] = urldecode ( $db -> f ( 'user' ));
$tmp [ 'password' ] = urldecode ( $db -> f ( 'password' ));
$tmp [ 'schedule' ] = $db -> f ( 'schedule' );
$tmp [ 'email' ] = urldecode ( $db -> f ( 'email' ));
$tmp [ 'next_execution' ] = $db -> f ( 'next_execution' );
$r [] = $tmp ;
}
return $r ;
2012-04-06 10:10:36 +00:00
}
2017-10-08 17:31:34 +00:00
/**
* Hook called by menu class to add menu to the left panel
*/
2015-09-25 15:42:00 +00:00
function hook_menu () {
$obj = array (
'title' => _ ( " Scheduled tasks " ),
'link' => 'cron.php' ,
2017-08-17 19:32:21 +00:00
'pos' => 120 ,
2017-10-06 21:42:39 +00:00
);
2012-09-13 14:23:18 +00:00
2015-09-25 15:42:00 +00:00
return $obj ;
2012-09-13 14:23:18 +00:00
}
2015-09-25 15:42:00 +00:00
2017-10-08 17:31:34 +00:00
/**
* update the crontab
2015-09-25 15:42:00 +00:00
* @ param $arr array the crontab information , including its ID
* @ return boolean TRUE if the crontab has been edited
*/
function update ( $arr ) {
$ok = true ;
foreach ( $arr as $a ) {
if ( ! isset ( $a [ 'id' ])) {
$a [ 'id' ] = null ;
}
if ( empty ( $a [ 'url' ]) && is_null ( $a [ 'id' ])) {
continue ;
}
if ( ! $this -> _update_one ( $a [ 'url' ], $a [ 'user' ], $a [ 'password' ], $a [ 'email' ], $a [ 'schedule' ], $a [ 'id' ])) {
$ok = false ;
}
}
return $ok ;
2012-09-13 14:23:18 +00:00
}
2015-09-25 15:42:00 +00:00
2017-10-08 17:31:34 +00:00
/**
* delete a crontab
2015-09-25 15:42:00 +00:00
* @ param $id the id of the crontab to delete
* @ return boolean TRUE if the crontab has been deleted
*/
function delete_one ( $id ) {
2017-08-17 01:32:18 +00:00
global $db , $msg , $cuid ;
$msg -> log ( " cron " , " delete_one " );
2016-05-17 15:21:08 +00:00
return $db -> query ( " DELETE FROM cron WHERE id= ? AND uid= ? LIMIT 1; " , array ( intval ( $id ), $cuid ));
2012-04-06 10:10:36 +00:00
}
2015-09-25 15:42:00 +00:00
2017-10-08 17:31:34 +00:00
/**
* update a crontab ,
2015-09-25 15:42:00 +00:00
* @ return boolean TRUE if the crontab has been edited
*/
private function _update_one ( $url , $user , $password , $email , $schedule , $id = null ) {
2017-08-17 01:32:18 +00:00
global $db , $msg , $quota , $cuid ;
$msg -> log ( " cron " , " update_one " );
2015-09-25 15:42:00 +00:00
if ( empty ( $url ) && ! is_null ( $id )) {
return $this -> delete_one ( $id );
}
if ( filter_var ( $url , FILTER_VALIDATE_URL ) === false ) {
2017-10-06 16:04:36 +00:00
$msg -> raise ( " ERROR " , " cron " , _ ( " URL not valid " ));
2015-09-25 15:42:00 +00:00
return false ;
}
$url = urlencode ( $url );
$user = urlencode ( $user );
if ( empty ( $user )) {
$password = '' ;
}
$password = urlencode ( $password );
//@todo remove checkmail cf functions.php
if ( ! empty ( $email ) && ! checkmail ( $email ) == 0 ) {
2017-10-06 16:04:36 +00:00
$msg -> raise ( " ERROR " , " cron " , _ ( " Email address is not valid " ));
2015-09-25 15:42:00 +00:00
return false ;
}
$email = urlencode ( $email );
if ( ! $this -> valid_schedule ( $schedule )) {
return false ;
}
if ( is_null ( $id )) { // if a new insert, quotacheck
$q = $quota -> getquota ( " cron " );
if ( $q [ " u " ] >= $q [ " t " ]) {
2017-10-06 16:04:36 +00:00
$msg -> raise ( " ERROR " , " cron " , _ ( " You quota of cron entries is over. You cannot create more cron entries " ));
2015-09-25 15:42:00 +00:00
return false ;
}
} else { // if not a new insert, check the $cuid
2016-05-17 15:21:08 +00:00
$db -> query ( " SELECT uid FROM cron WHERE id = ? ; " , array ( $id ));
2015-09-25 15:42:00 +00:00
if ( ! $db -> next_record ()) {
return " false " ;
} // return false if pb
2017-10-06 21:42:39 +00:00
if ( $db -> f ( 'uid' ) != $cuid ) {
$msg -> raise ( " ERROR " , " cron " , _ ( " Identity problem " ));
2015-09-25 15:42:00 +00:00
return false ;
}
}
2016-05-17 15:21:08 +00:00
return $db -> query ( " REPLACE INTO cron (id, uid, url, user, password, schedule, email) VALUES (?, ?, ?, ?, ?, ?, ?) ; " , array ( $id , $cuid , $url , $user , $password , $schedule , $email ));
2012-04-06 10:10:36 +00:00
}
2015-09-25 15:42:00 +00:00
2017-10-08 17:31:34 +00:00
/**
* validate a crontab schedule
2015-09-25 15:42:00 +00:00
* @ param $s array schedule paramters
* @ return boolean TRUE if the schedule is valid
*/
function valid_schedule ( $s ) {
$s2 = intval ( $s );
if ( $s2 != $s ) {
return false ;
}
$r = false ;
foreach ( $this -> schedule () as $cs ) {
if ( $cs [ 'unit' ] == $s ) {
return true ;
}
}
return $r ;
2012-04-06 10:10:36 +00:00
}
2015-09-25 15:42:00 +00:00
2017-10-08 17:31:34 +00:00
/**
* hook for quota computation
2015-09-25 15:42:00 +00:00
*/
function hook_quota_get () {
2017-08-17 01:32:18 +00:00
global $cuid , $db , $msg ;
2018-06-27 22:27:10 +00:00
$msg -> debug ( " cron " , " alternc_get_quota " );
2015-09-25 15:42:00 +00:00
$q = Array ( " name " => " cron " , " description " => _ ( " Scheduled tasks " ), " used " => 0 );
2016-05-17 15:21:08 +00:00
$db -> query ( " select count(*) as cnt from cron where uid = ? ; " , array ( $cuid ));
2015-09-25 15:42:00 +00:00
if ( $db -> next_record ()) {
$q [ 'used' ] = $db -> f ( 'cnt' );
}
return $q ;
2014-11-25 13:36:20 +00:00
}
2015-09-25 15:42:00 +00:00
/**
* Execute the required crontab of AlternC users
* this function EXIT at the end .
*/
function execute_cron () {
2018-06-27 22:27:10 +00:00
global $db , $msg ;
2015-09-25 15:42:00 +00:00
2018-06-27 22:27:10 +00:00
$msg -> debug ( " cron " , " execute_cron " );
2015-09-25 15:42:00 +00:00
if ( ! isset ( $GLOBALS [ " DEBUG " ])) {
$GLOBALS [ " DEBUG " ] = false ;
}
$db -> query ( " SELECT id, url, email, schedule, user, password FROM cron WHERE next_execution <= NOW(); " );
$urllist = array ();
while ( $db -> next_record ()) {
$db -> Record [ " url " ] = urldecode ( $db -> Record [ " url " ]);
$db -> Record [ " user " ] = urldecode ( $db -> Record [ " user " ]);
$db -> Record [ " email " ] = urldecode ( $db -> Record [ " email " ]);
$db -> Record [ " password " ] = urldecode ( $db -> Record [ " password " ]);
// we support only http or https schemes:
if ( substr ( $db -> Record [ " url " ], 0 , 7 ) == " http:// " || substr ( $db -> Record [ " url " ], 0 , 8 ) == " https:// " ) {
$u = array (
" url " => $db -> Record [ " url " ],
" id " => $db -> Record [ " id " ], " email " => $db -> Record [ " email " ],
);
if ( $db -> Record [ " user " ] && $db -> Record [ " password " ]) {
$u [ " login " ] = $db -> Record [ " user " ];
$u [ " password " ] = $db -> Record [ " password " ];
}
if ( $GLOBALS [ " DEBUG " ])
echo " Will run cron : \n " . print_r ( $u , true ) . " \n " ;
$urllist [] = $u ;
}
if ( empty ( $urllist )) { // nothing to do :
exit ( 0 );
}
}
2018-02-28 09:39:55 +00:00
// cron_callback($url, $content, $curlobj) will be called at the end of each http call.
$this -> rolling_curl ( $urllist , array ( " m_cron " , " cron_callback " ));
2014-11-25 13:36:20 +00:00
}
2015-09-25 15:42:00 +00:00
/**
* Callback function called by rolling_curl when a cron resulr has been received
* schedule it for next run and send the mail if needed
*/
function cron_callback ( $url , $content , $curl ) {
global $db , $L_FQDN ;
if ( empty ( $url [ " id " ])) {
return ; // not normal...
}
$id = intval ( $url [ " id " ]);
if ( $curl [ " http_code " ] == 200 ) {
$ok = true ;
} else {
$ok = false ;
}
if ( isset ( $url [ " email " ]) && $url [ " email " ] && $content ) {
if ( ! mail ( $url [ " email " ], " AlternC Cron # $id - Report " . date ( " r " ), " Please find below the stdout content produced by your cron task. \n ------------------------------------------------------------ \n \n " . $content , " From: postmaster@ $L_FQDN " )) {
echo " Error sending mail for cron # $id to address ' " . $url [ " email " ] . " ' \n " ;
}
}
// now schedule it for next run:
2016-05-17 15:21:08 +00:00
$db -> query ( " UPDATE cron SET next_execution=FROM_UNIXTIME( UNIX_TIMESTAMP(NOW()) + schedule * 60) WHERE id= ? " , array ( $id ));
2014-11-25 13:36:20 +00:00
}
2015-09-25 15:42:00 +00:00
/**
* Launch parallel ( using MAX_SOCKETS sockets maximum ) retrieval
* of URL using CURL
* @ param $urls array of associative array , each having the following keys :
* url = url to get ( of the form http [ s ] :// login : password @ host / path / file ? querystring )
* login & password = if set , tell the login and password to use as simple HTTP AUTH .
* - any other key will be sent as it is to the callback function
* @ param $callback function called for each request when completing . First argument is the $url object , second is the content ( output )
* third is the info structure from curl for the returned page . 200 for OK , 403 for AUTH FAILED , 0 for timeout , dump it to know it ;)
* this function should return as soon as possible to allow other curl calls to complete properly .
* @ param $cursom_options array of custom CURL options for all transfers
*/
function rolling_curl ( $urls , $callback , $custom_options = null ) {
// make sure the rolling window isn't greater than the # of urls
if ( ! isset ( $GLOBALS [ " DEBUG " ]))
$GLOBALS [ " DEBUG " ] = false ;
$rolling_window = m_cron :: MAX_SOCKETS ;
$rolling_window = ( count ( $urls ) < $rolling_window ) ? count ( $urls ) : $rolling_window ;
$master = curl_multi_init ();
// add additional curl options here
$std_options = array ( CURLOPT_RETURNTRANSFER => true ,
2017-10-06 21:42:39 +00:00
CURLOPT_FOLLOWLOCATION => false ,
CURLOPT_CONNECTTIMEOUT => 5 ,
CURLOPT_TIMEOUT => 240 , // 4 minutes timeout for a page
CURLOPT_USERAGENT => " AlternC (Cron Daemon) " ,
CURLOPT_MAXREDIRS => 0 );
2015-09-25 15:42:00 +00:00
if ( $GLOBALS [ " DEBUG " ]) {
$std_options [ CURLOPT_VERBOSE ] = true ;
}
$options = ( $custom_options ) ? ( $std_options + $custom_options ) : $std_options ;
// start the first batch of requests
for ( $i = 0 ; $i < $rolling_window ; $i ++ ) {
$ch = curl_init ();
$options [ CURLOPT_URL ] = $urls [ $i ][ " url " ];
if ( $GLOBALS [ " DEBUG " ]) {
echo " URL: " . $urls [ $i ][ " url " ] . " \n " ;
}
curl_setopt_array ( $ch , $options );
// Handle custom cafile for some https url
if ( strtolower ( substr ( $options [ CURLOPT_URL ], 0 , 5 )) == " https " ) {
curl_setopt ( $ch , CURLOPT_CAINFO , m_cron :: DEFAULT_CAFILE );
if ( $GLOBALS [ " DEBUG " ]) {
echo " cainfo set to DEFAULT \n " ;
}
}
if ( isset ( $urls [ $i ][ " login " ]) && isset ( $urls [ $i ][ " password " ])) { // set basic http authentication
curl_setopt ( $ch , CURLOPT_HTTPAUTH , CURLAUTH_BASIC );
curl_setopt ( $ch , CURLOPT_USERPWD , $urls [ $i ][ " login " ] . " : " . $urls [ $i ][ " password " ]);
if ( $GLOBALS [ " DEBUG " ]) {
echo " set basic auth \n " ;
}
}
curl_multi_add_handle ( $master , $ch );
}
do {
while (( $execrun = curl_multi_exec ( $master , $running )) == CURLM_CALL_MULTI_PERFORM );
if ( $execrun != CURLM_OK ) {
break ;
}
// a request was just completed -- find out which one
while ( $done = curl_multi_info_read ( $master )) {
$info = curl_getinfo ( $done [ 'handle' ]);
// TODO : since ssl_verify_result is buggy, if we have [header_size] => 0 && [request_size] => 0 && [http_code] => 0, AND https, we can pretend the SSL certificate is buggy.
if ( $GLOBALS [ " DEBUG " ]) {
echo " Info for " . $done [ 'handle' ] . " \n " ;
print_r ( $info );
}
if ( $info [ 'http_code' ] == 200 ) {
$output = curl_multi_getcontent ( $done [ 'handle' ]);
} else {
// request failed. add error handling.
$output = " " ;
}
// request terminated. process output using the callback function.
// Pass the url array to the callback, so we need to search it
foreach ( $urls as $url ) {
if ( $url [ " url " ] == $info [ " url " ]) {
call_user_func ( $callback , $url , $output , $info );
break ;
}
}
// If there is more: start a new request
// (it's important to do this before removing the old one)
if ( $i < count ( $urls )) {
$ch = curl_init ();
$options [ CURLOPT_URL ] = $urls [ $i ++ ]; // increment i
curl_setopt_array ( $ch , $options );
if ( strtolower ( substr ( $options [ CURLOPT_URL ], 0 , 5 )) == " https " ) {
curl_setopt ( $ch , CURLOPT_CAINFO , m_cron :: DEFAULT_CAFILE );
if ( $GLOBALS [ " DEBUG " ]) {
echo " cainfo set to DEFAULT \n " ;
}
}
if ( isset ( $urls [ $i ][ " login " ]) && isset ( $urls [ $i ][ " password " ])) { // set basic http authentication
curl_setopt ( $ch , CURLOPT_HTTPAUTH , CURLAUTH_BASIC );
curl_setopt ( $ch , CURLOPT_USERPWD , urlencode ( $urls [ $i ][ " login " ]) . " : " . urlencode ( $urls [ $i ][ " password " ]));
if ( $GLOBALS [ " DEBUG " ]) {
echo " set basic auth \n " ;
}
}
curl_multi_add_handle ( $master , $ch );
}
// remove the curl handle that just completed
curl_multi_remove_handle ( $master , $done [ 'handle' ]);
}
} while ( $running );
curl_multi_close ( $master );
return true ;
2014-11-25 13:36:20 +00:00
}
2015-09-25 15:42:00 +00:00
2017-10-08 17:31:34 +00:00
} /* Class cron */