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.33');
$propbag->add('requirements', array(
'serendipity' => '1.6',
'smarty' => '2.6.7',
'php' => '4.1.0'
));
$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,
'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'));
}
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(
'Netmirror.org',
's9y.org',
'github.com'
// 'openmirror.org'
),
'files' => array(
'Netmirror.org',
'SourceForge.net',
's9y.org',
'github.com'
// 'BerliOS.de (inactive)',
// 'openmirror.org'
)
);
static $http = array(
'xml' => array(
'http://netmirror.org/mirror/serendipity/',
'http://s9y.org/mirror/',
'https://raw.github.com/s9y/additional_plugins/master/',
// 'http://openmirror.org/pub/s9y/',
),
'files' => array(
'http://netmirror.org/mirror/serendipity/',
'http://php-blog.cvs.sourceforge.net/viewvc/php-blog/',
'http://s9y.org/mirror/',
'https://raw.github.com/s9y/',
// 'http://svn.berlios.de/viewcvs/serendipity/',
// 'http://openmirror.org/pub/s9y/',
),
'files_health' => array(
'http://netmirror.org/' => 'http://netmirror.org/mirror/serendipity/last.txt',
'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/',
// 'http://svn.berlios.de/' => 'http://svn.berlios.de/viewcvs/serendipity/',
// 'http://openmirror.org/' => 'http://openmirror.org/pub/s9y/last.txt',
)
);
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'],
'value' => $vals[$i]['value']
);
break;
case 'open':
$children[] = array(
'tag' => $vals[$i]['tag'],
'attributes' => $vals[$i]['attributes'],
'value' => $vals[$i]['value'],
'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] == $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;
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/Request.php';
$options = array('allowRedirects' => true, 'maxRedirects' => 5);
serendipity_plugin_api::hook_event('backend_http_request', $options, 'spartacus');
serendipity_request_start();
$req = new HTTP_Request($url, $options);
if (PEAR::isError($req->sendRequest()) || $req->getResponseCode() != '200') {
$resolved_url = $url . ' (IP ' . $url_ip . ')';
$this->outputMSG('error', sprintf(PLUGIN_EVENT_SPARTACUS_FETCHERROR, $resolved_url));
//--JAM: START FIREWALL DETECTION
if ($req->getResponseCode()) {
$this->outputMSG('error', sprintf(PLUGIN_EVENT_SPARTACUS_REPOSITORY_ERROR, $req->getResponseCode()));
}
$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 ($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_Request($health_url, $health_options);
$health_result = $health_req->sendRequest();
if (PEAR::isError($health_result)) {
$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);
}
} else if ($health_req->getResponseCode() != '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);
//--JAM: Parse response and display it.
}
//--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 (!$data) {
$data = $req->getResponseBody();
}
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 && (LANG_CHARSET == 'ISO-8859-1' || LANG_CHARSET == 'UTF-8')) {
return true;
}
switch (strtolower(LANG_CHARSET)) {
case 'utf-8':
// The XML file is UTF-8 format. No changes needed.
break;
case 'iso-8859-1':
$data = utf8_decode($data);
break;
default:
if (function_exists('iconv')) {
$data = iconv('UTF-8', LANG_CHARSET, $data);
} elseif (function_exists('recode')) {
$data = recode('utf-8..' . LANG_CHARSET, $data);
}
break;
}
}
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)];
$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); } } else { $propbag->add('type', 'suboption'); } 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 '