add('name', PLUGIN_EVENT_SPARTACUS_NAME);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_DESC);
$propbag->add('stackable', false);
$propbag->add('author', 'Garvin Hicking');
$propbag->add('version', '2.39.2');
$propbag->add('requirements', array(
'serendipity' => '1.6',
));
$propbag->add('event_hooks', array(
'backend_plugins_fetchlist' => true,
'backend_plugins_fetchplugin' => true,
'backend_templates_fetchlist' => true,
'backend_templates_fetchtemplate' => true,
'backend_pluginlisting_header' => true,
'backend_plugins_upgradecount' => true,
'external_plugin' => true,
'backend_directory_create' => true,
'cronjob' => true,
));
$propbag->add('groups', array('BACKEND_FEATURES'));
$propbag->add('configuration', array('enable_plugins', 'enable_themes', 'enable_remote', 'remote_url', 'cronjob', 'mirror_xml', 'mirror_files', 'custommirror', 'chown', 'chmod_files', 'chmod_dir', 'use_ftp', 'ftp_server', 'ftp_username', 'ftp_password', 'ftp_basedir'));
$propbag->add('legal', array(
'services' => array(
'spartacus' => array(
'url' => 'http://spartacus.s9y.org',
'desc' => 'Package server for plugin downloads'
),
'github.com' => array(
'url' => 'https://www.github.com',
'desc' => 'Package server for plugin downloads'
),
's9y.org' => array(
'url' => 'http://www.s9y.org',
'desc' => 'Package server for plugin downloads'
),
'sourceforge.net' => array(
'url' => 'http://www.sourceforget.net',
'desc' => 'Package server for plugin downloads'
),
'gitlab.com' => array(
'url' => 'http://www.gitlab.com',
'desc' => 'Package server for plugin downloads'
)
),
'frontend' => array(
),
'backend' => array(
'Allows to download plugins from configured remote sources from the webserver, may also connect via FTP to a configured server.'
),
'cookies' => array(
),
'stores_user_input' => false,
'stores_ip' => false,
'uses_ip' => false,
'transmits_user_input' => false
));
}
function generate_content(&$title)
{
$title = $this->title;
}
function cleanup()
{
global $serendipity;
// Purge DB cache
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}pluginlist");
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}plugincategories");
// Purge cached XML files.
$files = serendipity_traversePath($serendipity['serendipityPath'] . PATH_SMARTY_COMPILE, '', false, '/package_.+\.xml$/');
if (!is_array($files)) {
return false;
}
foreach ($files as $file) {
$this->outputMSG('notice', sprintf(DELETING_FILE . '
', $file['name']));
@unlink($serendipity['serendipityPath'] . PATH_SMARTY_COMPILE . '/' . $file['name']);
}
}
function &getMirrors($type = 'xml', $loc = false)
{
static $mirror = array(
'xml' => array(
'github.com',
's9y.org',
'gitlab.com'
),
'files' => array(
'github.com',
'SourceForge.net',
's9y.org',
'gitlab.com'
)
);
static $http = array(
'xml' => array(
'https://raw.github.com/s9y/additional_plugins/master/',
'http://s9y.org/mirror/',
'https://gitlab.com/s9y_blog/additional_plugins/-/raw/master/'
),
'files' => array(
'https://raw.github.com/s9y/',
'http://php-blog.cvs.sourceforge.net/viewvc/php-blog/',
'http://s9y.org/mirror/',
'https://gitlab.com/s9y_blog/'
),
'files_health' => array(
'http://php-blog.cvs.sourceforge.net/' => 'http://php-blog.cvs.sourceforge.net/viewvc/php-blog/serendipity/docs/LICENSE',
'http://s9y.org/' => 'http://s9y.org/',
'https://raw.github.com/' => 'https://raw.github.com/',
'https://gitlab.com/' => 'https://gitlab.com/',
)
);
if ($loc) {
return $http[$type];
} else {
return $mirror[$type];
}
}
function introspect_config_item($name, &$propbag)
{
global $serendipity;
switch($name) {
case 'cronjob':
if (class_exists('serendipity_event_cronjob')) {
$propbag->add('type', 'select');
$propbag->add('name', sprintf(PLUGIN_EVENT_SPARTACUS_CRONJOB_WHEN, $serendipity['blogMail']));
$propbag->add('description', '');
$propbag->add('default', 'none');
$propbag->add('select_values', serendipity_event_cronjob::getValues());
} else {
$propbag->add('type', 'content');
$propbag->add('default', PLUGIN_EVENT_SPARTACUS_CRONJOB);
}
break;
case 'enable_plugins':
$propbag->add('type', 'boolean');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_ENABLE_PLUGINS);
$propbag->add('description', '');
$propbag->add('default', 'true');
break;
case 'enable_themes':
$propbag->add('type', 'boolean');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_ENABLE_THEMES);
$propbag->add('description', '');
$propbag->add('default', 'true');
break;
case 'enable_remote':
$propbag->add('type', 'boolean');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_ENABLE_REMOTE);
$propbag->add('description', sprintf(PLUGIN_EVENT_SPARTACUS_ENABLE_REMOTE_DESC, $serendipity['baseURL'] . $serendipity['indexFile'] . '?/plugin/' . $this->get_config('remote_url')));
$propbag->add('default', 'false');
break;
case 'remote_url':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_ENABLE_REMOTE_URL);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_ENABLE_REMOTE_URL_DESC);
$propbag->add('default', 'spartacus_remote');
break;
case 'chmod_files':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_CHMOD);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_CHMOD_DESC);
$propbag->add('default', '');
break;
case 'chmod_dir':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_CHMOD_DIR);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_CHMOD_DIR_DESC);
$propbag->add('default', '');
break;
case 'chown':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_CHOWN);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_CHOWN_DESC);
$propbag->add('default', '');
break;
case 'custommirror':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_CUSTOMMIRROR);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_CUSTOMMIRROR_DESC);
$propbag->add('default', '');
break;
case 'mirror_xml':
$propbag->add('type', 'select');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_MIRROR_XML);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_MIRROR_DESC);
$propbag->add('select_values', $this->getMirrors('xml'));
$propbag->add('default', 0);
break;
case 'mirror_files':
$propbag->add('type', 'select');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_MIRROR_FILES);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_MIRROR_DESC);
$propbag->add('select_values', $this->getMirrors('files'));
$propbag->add('default', 0);
break;
case 'use_ftp':
if (function_exists('ftp_connect')) {
$propbag->add('type', 'boolean');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_FTP_USE);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_FTP_USE_DESC);
if (@ini_get('safe_mode')) {
$propbag->add('default', 'true');
} else {
$propbag->add('default', 'false');
}
}
break;
case 'ftp_server':
if (function_exists('ftp_connect')) {
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_FTP_SERVER);
$propbag->add('description', '');
$propbag->add('default', '');
}
break;
case 'ftp_username':
if (function_exists('ftp_connect')) {
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_FTP_USERNAME);
$propbag->add('description', '');
$propbag->add('default', '');
}
break;
case 'ftp_password':
if (function_exists('ftp_connect')) {
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_FTP_PASS);
$propbag->add('description', '');
$propbag->add('default', '');
}
break;
case 'ftp_basedir':
if (function_exists('ftp_connect')) {
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPARTACUS_FTP_BASEDIR);
$propbag->add('description', PLUGIN_EVENT_SPARTACUS_FTP_BASEDIR_DESC);
$propbag->add('default', $serendipity['serendipityHTTPPath']);
}
break;
default:
return false;
}
return true;
}
function GetChildren(&$vals, &$i)
{
$children = array();
$cnt = sizeof($vals);
while (++$i < $cnt) {
// compare type
switch ($vals[$i]['type']) {
case 'cdata':
$children[] = $vals[$i]['value'];
break;
case 'complete':
$children[] = array(
'tag' => $vals[$i]['tag'],
'attributes' => $vals[$i]['attributes'] ?? null,
'value' => $vals[$i]['value'] ?? null
);
break;
case 'open':
$children[] = array(
'tag' => $vals[$i]['tag'],
'attributes' => $vals[$i]['attributes'] ?? null,
'value' => $vals[$i]['value'] ?? null,
'children' => $this->GetChildren($vals, $i)
);
break;
case 'close':
return $children;
}
}
}
// remove double slashes without breaking URL
protected function fixUrl($s)
{
return preg_replace('%([^:])([/]{2,})%', '\\1/', $s);
}
// Create recursive directories; begins at serendipity plugin root folder level
function rmkdir($dir, $sub = 'plugins')
{
global $serendipity;
if (serendipity_db_bool($this->get_config('use_ftp')) && $this->get_config('ftp_password') != '') {
return $this->make_dir_via_ftp($dir);
}
$spaths = explode('/', $serendipity['serendipityPath'] . $sub . '/');
$paths = explode('/', $dir);
$stack = '';
foreach($paths AS $pathid => $path) {
$stack .= $path . '/';
if (($spaths[$pathid] ?? null) == $path) {
continue;
}
if (!is_dir($stack) && !mkdir($stack)) {
return false;
} else {
$this->fileperm($stack, true);
}
}
return true;
}
// Apply file permission settings.
function fileperm($stack, $is_dir)
{
$chmod_dir = intval($this->get_config('chmod_dir'), 8);
$chmod_files = intval($this->get_config('chmod_files'), 8);
$chown = $this->get_config('chown');
if ($is_dir && !empty($chmod_dir) && function_exists('chmod')) {
@chmod($stack, $chmod_dir); // Always ensure directory traversal.
}
if (!$is_dir && !empty($chmod_files) && function_exists('chmod')) {
@chmod($stack, $chmod_files); // Always ensure directory traversal.
}
if (!empty($chown) && function_exists('chown')) {
$own = explode('.', $chown);
if (isset($own[1])) {
@chgrp($stack, $own[1]);
}
@chown($stack, $own[0]);
}
return true;
}
function outputMSG($status, $msg)
{
global $serendipity;
switch($status) {
case 'notice':
echo ' '. $msg .'' . "\n";
break;
case 'error':
echo ' '. $msg .'' . "\n";
if ($serendipity['ajax']) {
// we need to set an actual error header so the ajax request can react to the error state
header('HTTP/1.1 400');
}
break;
case 'success':
default:
echo ' '. $msg .'' . "\n";
break;
}
}
function &fetchfile($url, $target, $cacheTimeout = 0, $decode_utf8 = false, $sub = 'plugins')
{
global $serendipity;
$serendipity['logger'] ?? $serendipity['logger'] = false;
static $error = false;
// Fix double URL strings.
$url = preg_replace('@http(s)?:/@i', 'http\1://', str_replace('//', '/', $url));
// --JAM: Get the URL's IP in the most error-free way possible
$url_parts = @parse_url($url);
$url_hostname = 'localhost';
if (is_array($url_parts)) {
$url_hostname = $url_parts['host'];
}
$url_ip = gethostbyname($url_hostname);
if (is_object($serendipity['logger'])) $serendipity['logger']->debug(sprintf(PLUGIN_EVENT_SPARTACUS_FETCHING, '' . basename($url) . ''));
if (file_exists($target) && filesize($target) > 0 && filemtime($target) >= (time()-$cacheTimeout)) {
$data = file_get_contents($target);
if (is_object($serendipity['logger'])) $serendipity['logger']->debug(sprintf(PLUGIN_EVENT_SPARTACUS_FETCHED_BYTES_CACHE, strlen($data), $target));
} else {
require_once S9Y_PEAR_PATH . 'HTTP/Request2.php';
$options = array('follow_redirects' => true, 'max_redirects' => 5);
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
// On earlier PHP versions, the certificate validation fails. We deactivate it on them to restore the functionality we had with HTTP/Request1
$options['ssl_verify_peer'] = false;
}
serendipity_plugin_api::hook_event('backend_http_request', $options, 'spartacus');
serendipity_request_start();
$req = new HTTP_Request2($url, HTTP_Request2::METHOD_GET, $options);
try {
$response = $req->send();
if ($response->getStatus() != '200') {
throw new HTTP_Request2_Exception('Statuscode not 200, Akismet HTTP verification request failed.');
}
} catch (HTTP_Request2_Exception $e) {
$resolved_url = $url . ' (IP ' . $url_ip . ')';
$this->outputMSG('error', sprintf(PLUGIN_EVENT_SPARTACUS_FETCHERROR, $resolved_url));
//--JAM: START FIREWALL DETECTION
if ((isset($response) && $response->getStatus())) {
$this->outputMSG('error', sprintf(PLUGIN_EVENT_SPARTACUS_REPOSITORY_ERROR, $response->getStatus()));
}
$check_health = true;
if (function_exists('curl_init')) {
$this->outputMSG('notice', PLUGIN_EVENT_SPARTACUS_TRYCURL);
$curl_handle=curl_init();
curl_setopt($curl_handle, CURLOPT_URL, $url);
curl_setopt($curl_handle, CURLOPT_HEADER, 0);
$curl_result = curl_exec($curl_handle);
curl_close($curl_handle);
if ($curl_result) {
$check_health = false;
} else {
$this->outputMSG('error', PLUGIN_EVENT_SPARTACUS_CURLFAIL . "\n");
}
}
}
if (isset($check_health) && $check_health) {
/*--JAM: Useful for later, when we have a health monitor for SPARTACUS
$propbag = new serendipity_property_bag;
$this->introspect($propbag);
$health_url = 'http://spartacus.s9y.org/spartacus_health.php?version=' . $propbag->get('version');
*/
// Garvin: Temporary health. Better than nothing, eh?
$health_url = $url;
$matches = array();
preg_match('#http://[^/]*/#', $url, $matches);
if ($matches[0]) {
$health_url = $matches[0];
}
$mirrors = $this->getMirrors('files_health', true);
$health_url = $mirrors[$health_url];
$this->outputMSG('notice', sprintf(PLUGIN_EVENT_SPARTACUS_HEALTHCHECK, $health_url));
$health_options = $options;
serendipity_plugin_api::hook_event('backend_http_request', $health_options, 'spartacus_health');
$health_req = new HTTP_Request2($health_url, HTTP_Request2::METHOD_GET, $health_options);
try {
$health_result = $health_req->send();
if ($health_result->getStatus() != '200') {
$this->outputMSG('error', sprintf(PLUGIN_EVENT_SPARTACUS_HEALTHERROR, $health_req->getResponseCode()));
$this->outputMSG('notice', sprintf(PLUGIN_EVENT_SPARTACUS_HEALTHLINK, $health_url));
} else {
$this->outputMSG('error', PLUGIN_EVENT_SPARTACUS_HEALTFIREWALLED);
}
} catch (HTTP_Request2_Exception $e) {
$fp = @fsockopen('www.google.com', 80, $errno, $errstr);
if (!$fp) {
$this->outputMSG('error', sprintf(PLUGIN_EVENT_SPARTACUS_HEALTHBLOCKED, $errno, $errstr));
} else {
$this->outputMSG('error', PLUGIN_EVENT_SPARTACUS_HEALTHDOWN);
$this->outputMSG('notice', sprintf(PLUGIN_EVENT_SPARTACUS_HEALTHLINK, $health_url));
fclose($fp);
}
}
//--JAM: END FIREWALL DETECTION
if (file_exists($target) && filesize($target) > 0) {
$data = file_get_contents($target);
$this->outputMSG('success', sprintf(PLUGIN_EVENT_SPARTACUS_FETCHED_BYTES_CACHE, strlen($data), $target));
}
} else {
// Fetch file
if (! isset($data) || !$data) {
$data = $response->getBody();
}
if (is_object($serendipity['logger'])) $serendipity['logger']->debug(sprintf(PLUGIN_EVENT_SPARTACUS_FETCHED_BYTES_URL, strlen($data), $target));
$tdir = dirname($target);
if (!is_dir($tdir) && !$this->rmkdir($tdir, $sub)) {
$this->outputMSG('error', sprintf(FILE_WRITE_ERROR, $tdir));
return $error;
}
$fp = @fopen($target, 'w');
if (!$fp) {
$this->outputMSG('error', sprintf(FILE_WRITE_ERROR, $target));
return $error;
}
if ($decode_utf8) {
$data = str_replace('', '', $data);
$this->decode($data, true);
}
fwrite($fp, $data);
fclose($fp);
$this->fileperm($target, false);
$this->purgeCache = true;
}
serendipity_request_end();
}
return $data;
}
function decode(&$data, $force = false)
{
// xml_parser_* functions to recoding from ISO-8859-1/UTF-8
if ($force === false) {
return true;
}
}
function &fetchOnline($type, $no_cache = false)
{
global $serendipity;
switch($type) {
// Sanitize to not fetch other URLs
default:
case 'event':
$url_type = 'event';
$i18n = true;
break;
case 'sidebar':
$url_type = 'sidebar';
$i18n = true;
break;
case 'template':
$url_type = 'template';
$i18n = false;
break;
}
if (!$i18n) {
$lang = '';
} elseif (isset($serendipity['languages'][$serendipity['lang']])) {
$lang = '_' . $serendipity['lang'];
} else {
$lang = '_en';
}
$mirrors = $this->getMirrors('xml', true);
$custom = $this->get_config('custommirror');
if (strlen($custom) > 2) {
$servers = explode('|', $custom);
$cacheTimeout = 60*60*12; // XML file is cached for half a day
$valid = false;
foreach($servers AS $server) {
if ($valid) continue;
$url = $server . '/package_' . $url_type . $lang . '.xml';
$target = $serendipity['serendipityPath'] . PATH_SMARTY_COMPILE . '/package_' . $url_type . $lang . '.xml';
$xml = $this->fetchfile($url, $target, $cacheTimeout, true);
if (strlen($xml) > 0) {
$valid = true;
}
}
} else {
$mirror = $mirrors[$this->get_config('mirror_xml', 0)];
if ($mirror == null) {
$mirror = $mirrors[0];
}
$url = $mirror . '/package_' . $url_type . $lang . '.xml';
$cacheTimeout = 60*60*12; // XML file is cached for half a day
$target = $serendipity['serendipityPath'] . PATH_SMARTY_COMPILE . '/package_' . $url_type . $lang . '.xml';
$xml = $this->fetchfile($url, $target, $cacheTimeout, true);
}
$new_crc = md5($xml);
$last_crc = $this->get_config('last_crc_' . $url_type);
if (!$no_cache && !$this->purgeCache && $last_crc == $new_crc) {
$out = 'cached';
return $out;
}
// XML functions
$xml_string = '';
if (preg_match('@(<\?xml.+\?>)@imsU', $xml, $xml_head)) {
$xml_string = $xml_head[1];
}
$encoding = 'UTF-8';
if (preg_match('@encoding="([^"]+)"@', $xml_string, $xml_encoding)) {
$encoding = $xml_encoding[1];
}
preg_match_all('@(
" . print_r($avail, true) . ""; $install['event'] = serendipity_plugin_api::enum_plugin_classes(true); $install['sidebar'] = serendipity_plugin_api::enum_plugin_classes(false); #echo "XINSTALL:
" . print_r($install, true) . ""; $mailtext = ''; foreach($meth AS $method) { foreach ($install[$method] as $class_data) { #echo "Probe " . $class_data['name']. "
" . print_r($avail[$method][$class_data['name']], true) . ""; // Object is returned when a plugin could not be cached. $bag = new serendipity_property_bag; $plugin->introspect($bag); // If a foreign plugin is upgradable, keep the new version number. if (isset($avail[$method][$class_data['name']])) { $class_data['upgrade_version'] = $avail[$method][$class_data['name']]['upgrade_version']; } $props = serendipity_plugin_api::setPluginInfo($plugin, $pluginFile, $bag, $class_data, 'local', $avail[$method]); #echo "
" . print_r($props, true) . ""; } elseif (is_array($plugin)) { // Array is returned if a plugin could be fetched from info cache $props = $plugin; #echo "Cached
" . print_r($props, true) . "\n"; if (version_compare($props['version'], $props['upgrade_version'], '<')) { $mailtext .= ' * ' . $class_data['name'] . " NEW VERSION: " . $props['upgrade_version'] . " - CURRENT VERSION: " . $props['version'] . "\n"; } } else { $mailtext .= " X ERROR: " . $class_data['true_name'] . "\n"; } } } if (!empty($mailtext)) { serendipity_sendMail($serendipity['blogMail'], 'Spartacus update report ' . $serendipity['baseURL'], $mailtext, $serendipity['blogMail']); echo nl2br($mailtext); } } break; case 'external_plugin': if (!serendipity_db_bool($this->get_config('enable_remote'))) { return false; } if ($eventData == $this->get_config('remote_url')) { header('Content-Type: text/plain'); $avail = array(); $install = array(); $meth = array('event', 'sidebar'); $active = serendipity_plugin_api::get_installed_plugins(); $avail['event'] = $this->buildList($this->fetchOnline('event'), 'event'); $avail['sidebar'] = $this->buildList($this->fetchOnline('sidebar'), 'sidebar'); $install['event'] = serendipity_plugin_api::enum_plugin_classes(true); $install['sidebar'] = serendipity_plugin_api::enum_plugin_classes(false); foreach($meth AS $method) { echo "LISTING: $method\n-------------------\n"; foreach ($install[$method] as $class_data) { $pluginFile = serendipity_plugin_api::probePlugin($class_data['name'], $class_data['classname'], $class_data['pluginPath']); $plugin = serendipity_plugin_api::getPluginInfo($pluginFile, $class_data, $method); if (is_object($plugin)) { // Object is returned when a plugin could not be cached. $bag = new serendipity_property_bag; $plugin->introspect($bag); // If a foreign plugin is upgradable, keep the new version number. if (isset($avail[$method][$class_data['name']])) { $class_data['upgrade_version'] = $avail[$method][$class_data['name']]['upgrade_version']; } $props = serendipity_plugin_api::setPluginInfo($plugin, $pluginFile, $bag, $class_data, 'local', $avail[$method]); } elseif (is_array($plugin)) { // Array is returned if a plugin could be fetched from info cache $props = $plugin; } else { $props = false; } if (is_array($props)) { #print_r($props); if (version_compare($props['version'], $props['upgrade_version'], '<')) { echo "UPGRADE: " . $class_data['name'] . " -- " . $props['upgrade_version'] . "\n"; } else { echo "OK: " . $class_data['name'] . " -- " . $props['version'] . "\n"; } } else { echo "ERROR: " . $class_data['true_name'] . "\n"; } } } } break; case 'backend_pluginlisting_header': if (serendipity_db_bool($this->get_config('enable_plugins'))) { echo '