1440, 'name' => _("Daily")), Array('unit' => 60, 'name' => _("Hour")), Array('unit' => 30, 'name' => _("Half Hour")), ); } /** * List the crontab for the current user. * @return array an hash for each crontab. */ function lst_cron() { global $cuid, $db, $msg; $msg->debug("cron", "lst_cron"); $db->query("SELECT * FROM cron WHERE uid = ? ORDER BY url;", array($cuid)); $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; } /** * Hook called by menu class to add menu to the left panel */ function hook_menu() { $obj = array( 'title' => _("Scheduled tasks"), 'link' => 'cron.php', 'pos' => 120, ); return $obj; } /** * update the crontab * @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; } /** * delete a crontab * @param $id the id of the crontab to delete * @return boolean TRUE if the crontab has been deleted */ function delete_one($id) { global $db, $msg, $cuid; $msg->log("cron", "delete_one"); return $db->query("DELETE FROM cron WHERE id= ? AND uid= ? LIMIT 1;", array(intval($id), $cuid)); } /** * update a crontab, * @return boolean TRUE if the crontab has been edited */ private function _update_one($url, $user, $password, $email, $schedule, $id = null) { global $db, $msg, $quota, $cuid; $msg->log("cron", "update_one"); if (empty($url) && !is_null($id)) { return $this->delete_one($id); } if (filter_var($url, FILTER_VALIDATE_URL) === false) { $msg->raise("ERROR", "cron", _("URL not valid")); 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) { $msg->raise("ERROR", "cron", _("Email address is not valid")); 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"]) { $msg->raise("ERROR", "cron", _("You quota of cron entries is over. You cannot create more cron entries")); return false; } } else { // if not a new insert, check the $cuid $db->query("SELECT uid FROM cron WHERE id = ? ;", array($id)); if (!$db->next_record()) { return "false"; } // return false if pb if ($db->f('uid') != $cuid) { $msg->raise("ERROR", "cron", _("Identity problem")); return false; } } return $db->query("REPLACE INTO cron (id, uid, url, user, password, schedule, email) VALUES (?, ?, ?, ?, ?, ?, ?) ;" , array($id, $cuid, $url, $user, $password, $schedule, $email)); } /** * validate a crontab schedule * @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; } /** * hook for quota computation */ function hook_quota_get() { global $cuid, $db, $msg; $msg->debug("cron", "alternc_get_quota"); $q = Array("name" => "cron", "description" => _("Scheduled tasks"), "used" => 0); $db->query("select count(*) as cnt from cron where uid = ? ;", array($cuid)); if ($db->next_record()) { $q['used'] = $db->f('cnt'); } return $q; } /** * Execute the required crontab of AlternC users * this function EXIT at the end. */ function execute_cron() { global $db,$msg; $msg->debug("cron", "execute_cron"); 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); } } // cron_callback($url, $content, $curlobj) will be called at the end of each http call. $this->rolling_curl($urllist, array("m_cron", "cron_callback")); } /** * 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: $db->query("UPDATE cron SET next_execution=FROM_UNIXTIME( UNIX_TIMESTAMP(NOW()) + schedule * 60) WHERE id= ?", array($id)); } /** * 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, CURLOPT_FOLLOWLOCATION => false, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 240, // 4 minutes timeout for a page CURLOPT_USERAGENT => "AlternC (Cron Daemon)", CURLOPT_MAXREDIRS => 0); 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; } } /* Class cron */