Skip to content

Commit

Permalink
Public API - starting with queue processing (#618)
Browse files Browse the repository at this point in the history
* Cleanup of notices, etc

* Initial setup of REST API

* Fixing URL

* Changing name from REST API to Public API

* Changing ping to status

* Changing version numbering

* Getting pref check to understand new return format

* 500 error if we didn't come through the right path to get here

* Sanitize all inputs cleanly

* Tidy up on scandirAndClean

* Adding queue metadata to logs

* First pass at queue processing

* Fixing filter in case of all queue files

* Creating the queue class and refactoring processing to be a method

* Refinements to queue, particularly checking for syntax errors

* Refinements to queue process, esp. asynch launching, hopefully

* Refinements to the queue mechanism after testing.

* Adding gitignore to queue folder

* Adding further to .gitignore

* Touchups to logged info

* Reversion of some problems

---------

Co-authored-by: Arvin Singla <[email protected]>
  • Loading branch information
jegelstaff and arvinsingla authored Feb 21, 2025
1 parent e8fff45 commit 557c95e
Show file tree
Hide file tree
Showing 14 changed files with 550 additions and 16 deletions.
20 changes: 15 additions & 5 deletions libraries/icms/config/item/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,27 @@ public function insert(&$config, $force=true) {
/**
* MAJOR HACK TO VERIFY IF FORMULIZE REWRITE URL SETTING IS CAPABLE OF BEING ENABLED
*/
if($config->getVar('conf_name') == 'formulizeRewriteRulesEnabled'
if(($config->getVar('conf_name') == 'formulizeRewriteRulesEnabled'
OR $config->getVar('conf_name') == 'formulizePublicAPIEnabled')
AND $config->getVar('conf_value') == 1
AND function_exists('curl_version')) {
switch($config->getVar('conf_name')) {
case 'formulizeRewriteRulesEnabled':
$url = XOOPS_URL.'/formulize-check-if-alternate-urls-are-properly-enabled-please'; // will resolve based on DNS available to server, so Docker gets confused by localhost!
break;
case 'formulizePublicAPIEnabled':
$url = XOOPS_URL.'/formulize-public-api/v1/status/formulize-check-if-public-api-is-properly-enabled-please'; // will resolve based on DNS available to server, so Docker gets confused by localhost!
break;
}
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, XOOPS_URL.'/formulize-check-if-alternate-urls-are-properly-enabled-please'); // will resolve based on DNS available to server, so Docker gets confused by localhost!
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
$response = curl_exec($curl);
$json = json_decode($response);
curl_close($curl);
if($response != 1) {
if($response != 1 AND (!is_object($json) OR $json->status != "healthy")) {
$config->setVar('conf_value', 0);
$config->cleanVars();
}
Expand Down
202 changes: 202 additions & 0 deletions modules/formulize/class/queue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<?php
###############################################################################
## Formulize - ad hoc form creation and reporting module for XOOPS ##
## Copyright (c) Formulize Project ##
###############################################################################
## XOOPS - PHP Content Management System ##
## Copyright (c) 2000 XOOPS.org ##
## <http://www.xoops.org/> ##
###############################################################################
## This program is free software; you can redistribute it and/or modify ##
## it under the terms of the GNU General Public License as published by ##
## the Free Software Foundation; either version 2 of the License, or ##
## (at your option) any later version. ##
## ##
## You may not change or alter any portion of this comment or credits ##
## of supporting developers from this source code or any supporting ##
## source code which is considered copyrighted (c) material of the ##
## original comment or credit authors. ##
## ##
## 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. ##
## ##
## You should have received a copy of the GNU General Public License ##
## along with this program; if not, write to the Free Software ##
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ##
###############################################################################
## Author of this file: Formulize Project ##
## Project: Formulize ##
###############################################################################

if (!defined("XOOPS_ROOT_PATH")) {
die("XOOPS root path not defined");
}

require_once XOOPS_ROOT_PATH.'/kernel/object.php';
class formulizeQueue extends FormulizeObject {

function __construct() {
$this->initVar('queue_handle', XOBJ_DTYPE_TXTBOX, '', 255);
$this->initVar('items', XOBJ_DTYPE_ARRAY, '', false, null);
}

function __get($name) {
if (!isset($this->$name)) {
if (method_exists($this, $name)) {
$this->$name = $this->$name();
} else {
$this->$name = $this->getVar($name);
}
}
return $this->$name;
}

}

class formulizeQueueHandler {

var $queueDir = XOOPS_ROOT_PATH.'/modules/formulize/queue/';
var $db;
function __construct(&$db) {
$this->db =& $db;
}

function &getInstance(&$db) {
static $instance;
if (!isset($instance)) {
$instance = new formulizeQueueHandler($db);
}
return $instance;
}

/**
* Create a new queue object with the handle name passed in, and and empty item list
* @param string queue_handle The identifier of the queue we're creating
* @return object The new queue object with this identifier
*/
function &create($queue_handle) {
$queue = new formulizeQueue();
$queue->setVar('queue_handle', FormulizeObject::sanitize_handle_name($queue_handle));
$queue->setVar('items', serialize(array()));
return $queue;
}

/**
* Get a queue based on the queued items identified by the given handle. Will create the queue if necessary.
* @param string queue_handle The identifier of the queue we're getting
* @return object The queue object based on the identifier, with all items already in the queue
*/
function get($queue_handle) {
$queue = $this->create($queue_handle);
$queue->setVar('items', serialize(formulize_scandirAndClean($this->queueDir, "_".$queue->getVar('queue_handle')."_", 0)));
return $queue;
}

/**
* Delete a queue by removing all the files for the queue from the queue directory
* @param mixed queue_or_queue_handle The queue object or a queue handle to identify the queue we are deleting.
*/
function delete($queue_or_queue_handle) {
$queue_handle = (is_object($queue_or_queue_handle) AND is_a($queue_or_queue_handle, 'formulizeQueue')) ? $queue_or_queue_handle->getVar('queue_handle') : FormulizeObject::sanitize_handle_name($queue_or_queue_handle);
formulize_scandirAndClean($this->queueDir, "_".$queue_handle."_", 1);
return true;
}

/**
* Append code to a queue, so it can be run later when the queue is processed. A passed queue object has its items updated to include the newly appended item/file. Objects are passed by reference by default in PHP since v5.
* @param mixed queue_or_queue_handle The queue object or a queue handle to identify the queue we are appending to.
* @param string code The code that we should add to the queue
* @param string item Optional. A string that provides a description of what this code is for. Meant to make the filename more intelligible.
* @param bool allowDuplicates Optional. A flag to indicate whether we allow a duplicate item into the queue. Use the item descriptor thoughtfully.
* @return mixed Returns the number of bytes that were written (this is the return value of file_put_contents), or null if the item is already in the queue, or false on failure to write.
*/
function append($queue_or_queue_handle, $code, $item='', $allowDuplicates=false) {
global $xoopsUser;
$queue_handle = (is_object($queue_or_queue_handle) AND is_a($queue_or_queue_handle, 'formulizeQueue')) ? $queue_or_queue_handle->getVar('queue_handle') : FormulizeObject::santitize_handle_name($queue_or_queue_handle);
$fileName = microtime(true)."_".$queue_handle."_".$item.".php";
if(!$allowDuplicates) {
$existingQueueItems = formulize_scandirAndClean($this->queueDir, "_".$queue_handle."_".$item.".php", 0); // check for files with same queue_handle and item descriptor
}
$writeResult = null;
if($allowDuplicates OR count($existingQueueItems) == 0) {
$writeResult = false;
$code = "<?php
try {
writeToFormulizeLog(array(
'formulize_event'=>'processing-queue-item',
'queue_id'=>'$queue_handle',
'queue_item_or_items'=>'$fileName'
));
$code
} catch (Exception \$e) {
error_log('Formulize Queue Error: queue item: $fileName, message: '.\$e->getMessage().', line: '.\$e->getLine());
writeToFormulizeLog(array(
'formulize_event'=>'error-processing-queue-item',
'queue_id'=>'$queue_handle',
'queue_item_or_items'=>'$fileName',
'PHP_error_string'=>\$e->getMessage(),
'PHP_error_file'=>'$fileName',
'PHP_error_errline'=>\$e->getLine()
));
}";
if(formulize_validatePHPCode($code) == '') { // no errors returned
if($writeResult = file_put_contents($this->queueDir.$fileName, $code)) {
if(is_object($queue_or_queue_handle) AND is_a($queue_or_queue_handle, 'formulizeQueue')) {
$items = $queue_or_queue_handle->getVar('items');
$items[] = $fileName;
$queue_or_queue_handle->setVar('items', serialize($items));
}
writeToFormulizeLog(array(
'formulize_event'=>'item-written-to-queue',
'queue_id'=>$queue_handle,
'queue_item_or_items'=>$fileName,
'user_id'=>($xoopsUser ? $xoopsUser->getVar('uid') : 0)
));
} else {
writeToFormulizeLog(array(
'formulize_event'=>'error-writing-item-to-queue',
'queue_id'=>$queue_handle,
'queue_item_or_items'=>$fileName,
'user_id'=>($xoopsUser ? $xoopsUser->getVar('uid') : 0)
));
}
} else {
error_log("Formulize Queue Error: the code for item $item in queue $queue_handle has syntax errors. This item has not been added to the queue.");
writeToFormulizeLog(array(
'formulize_event'=>'syntax-error-in-queue-item',
'queue_id'=>$queue_handle,
'queue_item_or_items'=>$fileName,
'user_id'=>($xoopsUser ? $xoopsUser->getVar('uid') : 0)
));
}
}
return $writeResult;
}

/**
* Process a queue or all the queues. If command line execution is available, entire queue processed that way without time limit. If not, then attempt as many queue items as possible in the time available for the current request.
* @param object queue Optional. A queue object that represents the queue we're processing. If omitted, all queues are processed, items handled in the order they were created.
* @return mixed An array of the queue filenames that were processed, if done as part of this request. True if the queue was handed off to the command line
*/
function process($queue_or_queue_handle=null) {
if(is_object($queue_or_queue_handle) AND is_a($queue_or_queue_handle, 'formulizeQueue')) {
$queue_handle = $queue_or_queue_handle->getVar('queue_handle');
} elseif($queue_or_queue_handle) {
$queue_handle = $queue_or_queue_handle;
} else {
$queue_handle = 'all';
}
$queueDir = $this->queueDir;
$queueIncludeFile = XOOPS_ROOT_PATH.'/modules/formulize/include/queue.php';
if(isEnabled('exec')) {
exec('php -f '.$queueIncludeFile.' '.escapeshellarg($queue_handle).' '.escapeshellarg($queueDir).' > /dev/null 2>&1 & echo $!');
return true;
} else {
include $queueIncludeFile; // sets processedFiles
return $processedFiles;
}
}

}
19 changes: 9 additions & 10 deletions modules/formulize/include/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4012,23 +4012,22 @@ function writeElementValue($formframe, $ele, $entry, $value, $append="replace",
* This function reads all the files in a directory and deletes old ones
* @param string $dir The directory to look in
* @param string $filter Optional. A string that the filenames must contain. Non-matching files will be excluded. By default all files are included.
* @param int $timeWindow Optional. The number of seconds old a file must be to be included. Older files will be deleted. Default is 21600 (6 hours).
* @param int $timeWindow Optional. The number of seconds old a file must be to be included. Older files will be deleted. Default is 21600 (6 hours). If 0 then all files are included.
* @param int $sortingOrder Optional. The sorting order for scandir to use. Same values as the $sorting_order param of the PHP scandir function.
* @return array An array of the files found
**/
function formulize_scandirAndClean($dir, $filter="", $timeWindow=21600) {
// filter must be present
if (!$filter) {
return false;
}

function formulize_scandirAndClean($dir, $filter="", $timeWindow=21600, $sortingOrder=SCANDIR_SORT_ASCENDING) {
$currentTime = time();
$targetTime = $currentTime - $timeWindow;
$foundFiles = array();

$dir = rtrim($dir, '/');
foreach (scandir($dir) as $fileName) {
if (strstr($fileName, $filter)) {
if (filemtime($dir.'/'.$fileName) < $targetTime) {
foreach (scandir($dir, $sortingOrder) as $fileName) {
if($fileName == '.' OR $fileName == '..') {
continue;
}
if (!$filter OR strstr($fileName, $filter)) {
if ($timeWindow AND filemtime($dir.'/'.$fileName) < $targetTime) {
unlink($dir.'/'.$fileName);
} else {
$foundFiles[] = $fileName;
Expand Down
81 changes: 81 additions & 0 deletions modules/formulize/include/queue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
###############################################################################
## Formulize - ad hoc form creation and reporting module for XOOPS ##
## Copyright (c) Formulize Project ##
###############################################################################
## XOOPS - PHP Content Management System ##
## Copyright (c) 2000 XOOPS.org ##
## <http://www.xoops.org/> ##
###############################################################################
## This program is free software; you can redistribute it and/or modify ##
## it under the terms of the GNU General Public License as published by ##
## the Free Software Foundation; either version 2 of the License, or ##
## (at your option) any later version. ##
## ##
## You may not change or alter any portion of this comment or credits ##
## of supporting developers from this source code or any supporting ##
## source code which is considered copyrighted (c) material of the ##
## original comment or credit authors. ##
## ##
## 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. ##
## ##
## You should have received a copy of the GNU General Public License ##
## along with this program; if not, write to the Free Software ##
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ##
###############################################################################
## Author of this file: Formulize Project ##
## Project: Formulize ##
###############################################################################


if(!defined("XOOPS_MAINFILE_INCLUDED")) {
include_once "../../../mainfile.php";
include_once XOOPS_ROOT_PATH."/modules/formulize/include/common.php";
set_time_limit(0);
ignore_user_abort(true);
$queue_handle = $argv[1];
$queueDir = $argv[2];
} else {
global $formulize_publicApiStartTime, $formulize_publicApiMaxExec;
$formulize_publicApiStartTime = $formulize_publicApiStartTime ? $formulize_publicApiStartTime : microtime(true);
$formulize_publicApiMaxExec = $formulize_publicApiMaxExec ? $formulize_publicApiMaxExec : 60;
}

global $xoopsDB;
$xoopsDB->allowWebChanges = true;
define('FORMULIZE_QUEUE_PROCESSING', true);

if(!$queue_handle OR $queue_handle == 'all') {
$queueFiles = formulize_scandirAndClean($queueDir, ".php", 0);
} else {
$queue_handler = xoops_getmodulehandler('queue', 'formulize');
$queue = $queue_handler->get($queue_handle);
$queueFiles = $queue->getVar('items');
}

$processedFiles = array();

writeToFormulizeLog(array(
'formulize_event'=>'queue-processing-beginning',
'queue_id'=>$queue_handle,
'queue_item_or_items'=>implode(',',$queueFiles)
));

foreach($queueFiles as $file) {
$curTime = microtime(true);
if(!isset($formulize_publicApiStartTime) OR $curTime - $formulize_publicApiStartTime < $formulize_publicApiMaxExec - 10) { // if we're running in an http request context (not command line), then ten second window when running inside a request because we hope no single queue operation takes over ten seconds by itself??
include $queueDir.$file;
unlink($queueDir.$file);
$processedFiles[] = $file;
} else {
break;
}
}
writeToFormulizeLog(array(
'formulize_event'=>'queue-processing-complete',
'queue_id'=>$queue_handle,
'queue_item_or_items'=>implode(',',$processedFiles)
));
4 changes: 3 additions & 1 deletion modules/formulize/include/writeToFormulizeLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ function writeToFormulizeLog($data) {
'PHP_error_number' => (isset($data['PHP_error_number']) ? $data['PHP_error_number'] : ''),
'PHP_error_string' => (isset($data['PHP_error_string']) ? $data['PHP_error_string'] : ''),
'PHP_error_file' => (isset($data['PHP_error_file']) ? $data['PHP_error_file'] : ''),
'PHP_error_errline' => (isset($data['PHP_error_errline']) ? $data['PHP_error_errline'] : '')
'PHP_error_errline' => (isset($data['PHP_error_errline']) ? $data['PHP_error_errline'] : ''),
'queue_id' => (isset($data['queue_id']) ? $data['queue_id'] : ''),
'queue_item_or_items' => (isset($data['queue_item_or_items']) ? $data['queue_item_or_items'] : ''),
);

// write the new log entry (to a new file if necessary, active file has generic name, archived files are named with the current date based on server timezone)
Expand Down
21 changes: 21 additions & 0 deletions modules/formulize/language/english/modinfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,27 @@
define("_MI_formulize_rewriteRulesEnabled", "Enable alternate URLs for screens".$rewriteRuleInstructions);
define("_MI_formulize_rewriteRulesEnabledDESC", "When this is enabled, you can specify alternate, clean URLs for accessing screens, instead of the default /modules/formulize/index.php?sid=1 style URLs.");

$publicAPIInstructions = '';
if(isset($GLOBALS['config'])) {
global $config;
foreach($config as $thisConfig) {
if(is_object($thisConfig) AND $thisConfig->getVar('conf_name') == 'formulizePublicAPIEnabled' AND $thisConfig->getVar('conf_value') == 0) {
$publicAPIInstructions = "<br><br>For the Public API to work, you will need to add code similar to this, to the .htaccess file at the root of your website:
<blockquote style=\"font-weight: normal; font-family: monospace; white-space: nowrap;\">
RewriteEngine On<br>
RewriteCond %{REQUEST_URI} ^/formulize-public-api/ [NC]
RewriteCond %{REQUEST_FILENAME} !-f<br>
RewriteCond %{REQUEST_FILENAME} !-d<br>
RewriteCond %{REQUEST_FILENAME} !-l<br>
RewriteRule ^(.*)$ /modules/formulize/public_api/index.php?apiPath=$1 [L]<br>
</blockquote><i>If you enabled this option, but these instructions are still here, and the option is off again, then your server is not yet properly configured for the Public API.</i>";
break;
}
}
}
define("_MI_formulize_PUBLICAPIENABLED", "Enable the Public API".$publicAPIInstructions);
define("_MI_formulize_PUBLICAPIENABLED_DESC", "When this is enabled, you can use the Public API documented at https://formulize.org/developers/public-api/");

// The name of this module
define("_MI_formulizeMENU_NAME","MyMenu");

Expand Down
Loading

0 comments on commit 557c95e

Please sign in to comment.