LuckyCoinkydink/plugins/serendipity_event_spartacus/serendipity_event_spartacus.php

1407 lines
59 KiB
PHP

<?php
/************
TODO:
- Perform Serendipity version checks to only install plugins available for version
- Allow fetching files from mirrors / different locations - don't use ViewCVS hack (revision 1.999 dumbness)
***********/
if (IN_serendipity !== true) {
die ("Don't hack!");
}
@serendipity_plugin_api::load_language(dirname(__FILE__));
class serendipity_event_spartacus extends serendipity_event
{
var $title = PLUGIN_EVENT_SPARTACUS_NAME;
var $purgeCache = false;
function introspect(&$propbag)
{
global $serendipity;
$propbag->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 . '<br />', $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 '<span class="msg_notice"><span class="icon-info-circled" aria-hidden="true"></span> '. $msg .'</span>' . "\n";
break;
case 'error':
echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> '. $msg .'</span>' . "\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 '<span class="msg_success"><span class="icon-ok-circled" aria-hidden="true"></span> '. $msg .'</span>' . "\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, '<a target="_blank" href="' . $url . '">' . basename($url) . '</a>'));
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('<?xml version="1.0" encoding="UTF-8" ?>', '<?xml version="1.0" encoding="utf-8" ?>', $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 = '<?xml version="1.0" encoding="UTF-8" ?>';
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('@(<package version="[^"]+">.*</package>)@imsU', $xml, $xml_matches);
if (!is_array($xml_matches)) {
$out = 'cached';
return $out;
}
$i = 0;
$tree = array();
$tree[$i] = array(
'tag' => 'packages',
'attributes' => '',
'value' => '',
'children' => array()
);
foreach($xml_matches[0] as $xml_index => $xml_package) {
$i = 0;
switch(strtolower($encoding)) {
case 'iso-8859-1':
case 'utf-8':
$p = xml_parser_create($encoding);
break;
default:
$p = xml_parser_create('');
}
xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, 0);
$xml_package = $xml_string . "\n" . $xml_package;
xml_parse_into_struct($p, $xml_package, $vals);
xml_parser_free($p);
$tree[0]['children'][] = array(
'tag' => $vals[$i]['tag'],
'attributes' => $vals[$i]['attributes'],
'value' => $vals[$i]['value'],
'children' => $this->GetChildren($vals, $i)
);
unset($vals);
}
$this->set_config('last_crc_' . $url_type, $new_crc);
return $tree;
}
function &getCachedPlugins(&$plugins, $type)
{
global $serendipity;
static $pluginlist = null;
static $cachedtype = null;
if (isset($cachedtype) && $cachedtype != $type) {
// bust cache if called with other type
$pluginlist = null;
}
if ($pluginlist === null) {
$pluginlist = array();
$data = serendipity_db_query("SELECT p.*,
pc.category
FROM {$serendipity['dbPrefix']}pluginlist AS p
LEFT OUTER JOIN {$serendipity['dbPrefix']}plugincategories AS pc
ON pc.class_name = p.class_name
WHERE p.pluginlocation = 'Spartacus' AND
p.plugintype = '" . serendipity_db_escape_string($type) . "'");
if (is_array($data)) {
foreach($data AS $p) {
$p['stackable'] = serendipity_db_bool($p['stackable']);
$p['requirements'] = unserialize($p['requirements']);
$this->checkPlugin($p, $plugins, $p['plugintype']);
if (!isset($pluginlist[$p['plugin_file']])) {
$pluginlist[$p['plugin_file']] = $p;
}
$pluginlist[$p['plugin_file']]['groups'][] = $p['category'];
}
}
}
// save type of cached pluginlist
$cachedtype = $type;
return $pluginlist;
}
function checkPlugin(&$data, &$plugins, $type)
{
$installable = true;
$upgradeLink = '';
if (in_array($data['class_name'], $plugins)) {
$infoplugin =& serendipity_plugin_api::load_plugin($data['class_name']);
if (is_object($infoplugin)) {
$bag = new serendipity_property_bag;
$infoplugin->introspect($bag);
if ($bag->get('version') == $data['version']) {
$installable = false;
} elseif (version_compare($bag->get('version'), $data['version'], '<')) {
// I'm not sure if setting 'upgrade_version' to 'version' is correct
// for the context of pluginlocation = 'Spartacus', but leave it
// for now.
// See https://github.com/s9y/Serendipity/issues/685 for context.
$data['upgradable'] = true;
$data['upgrade_version'] = $data['version'];
$data['version'] = $bag->get('version');
$upgradeLink = '&amp;serendipity[spartacus_upgrade]=true';
}
}
}
$data['installable'] = $installable;
$data['pluginPath'] = 'online_repository';
$data['pluginlocation'] = 'Spartacus';
$data['plugintype'] = $type;
$data['customURI'] = '&amp;serendipity[spartacus_fetch]=' . $type . $upgradeLink;
return true;
}
function &buildList(&$tree, $type)
{
$plugins = serendipity_plugin_api::get_installed_plugins();
if ($tree === 'cached') {
return $this->getCachedPlugins($plugins, $type);
}
$pluginstack = array();
$i = 0;
$this->checkArray($tree);
foreach($tree[0]['children'] AS $idx => $subtree) {
if (is_array($subtree) && $subtree['tag'] == 'package') {
$i++;
foreach($subtree['children'] AS $child => $childtree) {
if (is_array($childtree) && isset($childtree['tag'])) {
switch($childtree['tag']) {
case 'name':
$pluginstack[$i]['plugin_class'] =
$pluginstack[$i]['plugin_file'] =
$pluginstack[$i]['class_name'] =
$pluginstack[$i]['true_name'] = $childtree['value'];
break;
case 'summary':
$pluginstack[$i]['name'] = $childtree['value'];
break;
case 'website':
$pluginstack[$i]['website'] = $childtree['value'];
break;
case 'changelog':
$pluginstack[$i]['changelog'] = $childtree['value'];
break;
case 'groups':
$pluginstack[$i]['groups'] = explode(',', $childtree['value']);
break;
case 'description':
$pluginstack[$i]['description'] = $childtree['value'];
break;
case 'release':
$pluginstack[$i]['version'] = $childtree['children'][0]['value'];
$pluginstack[$i]['requirements'] = array(
'serendipity' => '',
'php' => '',
'smarty' => ''
);
foreach((array)$childtree['children'] AS $relInfo) {
if (isset($relInfo['tag']) && $relInfo['tag'] == 'requirements:s9yVersion') {
$pluginstack[$i]['requirements']['serendipity'] = $relInfo['value'];
}
}
break;
case 'maintainers':
$pluginstack[$i]['author'] = $childtree['children'][0]['children'][0]['value']; // I dig my PHP arrays ;-)
break;
}
}
}
$this->checkPlugin($pluginstack[$i], $plugins, $type);
serendipity_plugin_api::setPluginInfo($pluginstack[$i], $pluginstack[$i]['plugin_file'], $i, $i, 'Spartacus');
// Remove the temporary $i reference, as the array should be associative
$plugname = $pluginstack[$i]['true_name'];
$pluginstack[$plugname] = $pluginstack[$i];
unset($pluginstack[$i]);
}
}
return $pluginstack;
}
function checkArray(&$tree)
{
if (!is_array($tree) || !is_array($tree[0]['children'])) {
$msg = "DEBUG: The XML file could not be successfully parsed. Download or caching error. " .
"Please try again later or switch your XML/File mirror location. ".
"You can also try to go to the plugin configuration of the Spartacus Plugin and simply click on 'Save' - this will purge all cached XML files and try to download it again.\n".
'<div style="display: none">' . print_r($tree, true) . "</div>\n";
echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> '. $msg .'</span>' . "\n";
}
}
function &buildTemplateList(&$tree)
{
global $serendipity;
$pluginstack = array();
$i = 0;
$gitloc = '';
$mirrors = $this->getMirrors('files', true);
$mirror = $mirrors[$this->get_config('mirror_files', 0)];
if ($mirror == null) {
$mirror = $mirrors[0];
}
$custom = $this->get_config('custommirror');
if (strlen($custom) > 2) {
$servers = explode('|', $custom);
$mirror = $servers[0];
}
if (stristr($mirror, 'github.com')) {
$gitloc = 'master/';
}
if (stristr($mirror, 'gitlab.com')) {
$gitloc = '-/raw/master/';
}
$this->checkArray($tree);
if (! file_exists($serendipity['serendipityPath'] . '/templates_c/template_cache')) {
mkdir($serendipity['serendipityPath'] . '/templates_c/template_cache');
}
foreach($tree[0]['children'] AS $idx => $subtree) {
if ($subtree['tag'] == 'package') {
$i++;
foreach($subtree['children'] AS $child => $childtree) {
if (is_array($childtree) && isset($childtree['tag'])) {
switch($childtree['tag']) {
case 'release':
$pluginstack[$i]['version'] = $childtree['children'][0]['value'];
$pluginstack[$i]['requirements'] = array(
'serendipity' => '',
'php' => '',
'smarty' => ''
);
foreach((array)$childtree['children'] AS $relInfo) {
if (isset($relInfo['tag']) && $relInfo['tag'] == 'requirements:s9yVersion') {
$pluginstack[$i]['requirements']['serendipity'] = $relInfo['value'];
}
if (isset($relInfo['tag']) && $relInfo['tag'] == 'date') {
$pluginstack[$i]['date'] = $relInfo['value'];
}
}
$pluginstack[$i]['require serendipity'] = $pluginstack[$i]['requirements']['serendipity'];
break;
case 'maintainers':
$pluginstack[$i]['author'] = $childtree['children'][0]['children'][0]['value'];
break;
default:
# Catches name, summary, template, description and recommended. Also a way to extend this later on
$pluginstack[$i][$childtree['tag']] = $childtree['value'];
break;
}
}
}
$plugname = $pluginstack[$i]['template'];
$pluginstack[$i]['demoURL'] = 'http://blog.s9y.org?user_template=additional_themes/' . $plugname;
$pluginstack[$i]['previewURL'] = $this->fixUrl($mirror . '/additional_themes/' . $gitloc . $plugname . '/preview.png?revision=1.9999');
$preview_fullsizeURL = $this->fixUrl($mirror . '/additional_themes/' . $gitloc . $plugname . '/preview_fullsize.jpg?revision=1.9999');
if (file_exists($serendipity['serendipityPath'] . '/templates_c/template_cache/'. $plugname .'.jpg')) {
$pluginstack[$i]['preview_fullsizeURL'] = $preview_fullsizeURL;
} else {
if ( ! file_exists($serendipity['serendipityPath'] . '/templates_c/template_cache/'. $plugname)) {
$file = @fopen($preview_fullsizeURL, 'r');
if ($file) {
file_put_contents($serendipity['serendipityPath'] . '/templates_c/template_cache/'. $plugname .'.jpg', $file);
$pluginstack[$i]['preview_fullsizeURL'] = $preview_fullsizeURL;
} else {
// place an empty file, so we don't have to check the server on every load
file_put_contents($serendipity['serendipityPath'] . '/templates_c/template_cache/'. $plugname, $file);
}
}
}
$pluginstack[$i]['customURI'] = '&amp;serendipity[spartacus_fetch]=' . $plugname;
$pluginstack[$i]['customIcon'] = '_spartacus';
// Remove the temporary $i reference, as the array should be associative
$pluginstack[$plugname] = $pluginstack[$i];
unset($pluginstack[$i]);
}
}
return $pluginstack;
}
function download(&$tree, $plugin_to_install, $sub = 'plugins')
{
global $serendipity;
$gitloc = '';
switch($sub) {
case 'plugins':
default:
$sfloc = 'additional_plugins';
break;
case 'templates':
$sfloc = 'additional_themes';
break;
}
$pdir = $serendipity['serendipityPath'] . '/' . $sub . '/';
if (!is_writable($pdir)) {
$this->outputMSG('error', sprintf(DIRECTORY_WRITE_ERROR, $pdir));
return false;
}
$files = array();
$found = false;
$this->checkArray($tree);
foreach($tree[0]['children'] AS $subtree) {
if ($subtree['tag'] != 'package') {
continue;
}
foreach($subtree['children'] AS $childtree) {
if (!is_array($childtree) || !isset($childtree['tag'])) {
continue;
}
if ($sub == 'templates' && $childtree['tag'] == 'template' && $childtree['value'] == $plugin_to_install) {
$found = true;
} elseif ($sub == 'plugins' && $childtree['tag'] == 'name' && $childtree['value'] == $plugin_to_install) {
$found = true;
}
if (!$found || $childtree['tag'] != 'release') {
continue;
}
foreach($childtree['children'] AS $childtree2) {
if (!is_array($childtree2) || !isset($childtree2['tag'])) {
continue;
}
if ($childtree2['tag'] != 'serendipityFilelist') {
continue;
}
foreach($childtree2['children'] AS $_files) {
if (!is_array($_files) || !isset($_files['tag'])) {
continue;
}
if ($_files['tag'] == 'file' && !empty($_files['value'])) {
$files[] = $_files['value'];
}
}
}
$found = false;
}
}
if (count($files) == 0) {
$msg = "DEBUG: ERROR: XML tree did not contain requested plugin:\n<div>" . print_r($tree, true) . "</div>\n";
echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> '. $msg .'</span>' . "\n";
}
$mirrors = $this->getMirrors('files', true);
$mirror = $mirrors[$this->get_config('mirror_files', 0)];
if ($mirror == null) {
$mirror = $mirrors[0];
}
$custom = $this->get_config('custommirror');
if (strlen($custom) > 2) {
$servers = explode('|', $custom);
$mirror = $servers[0];
}
if (stristr($mirror, 'github.com')) {
$gitloc = 'master/';
}
if (stristr($mirror, 'gitlab.com')) {
$gitloc = '-/raw/master/';
}
foreach($files AS $file) {
$url = $mirror . '/' . $sfloc . '/' . $gitloc . $file . '?revision=1.9999';
$target = $pdir . $file;
$target = $this->fixUrl($target);
$this->rmkdir($pdir . $plugin_to_install,$sub);
$this->fileperm($pdir . $plugin_to_install, true);
$this->fetchfile($url, $target);
if (!isset($baseDir)) {
$baseDirs = explode('/', $file);
$baseDir = $baseDirs[0];
}
}
if (isset($baseDir)) {
$this->outputMSG('success', PLUGIN_EVENT_SPARTACUS_FETCHED_DONE);
return $baseDir;
}
}
function make_dir_via_ftp($dir)
{
global $serendipity;
if (!serendipity_db_bool($this->get_config('use_ftp'))) {
return false;
}
$ftp_server = $this->get_config('ftp_server');
$ftp_user_name = $this->get_config('ftp_username');
$ftp_user_pass = $this->get_config('ftp_password');
$basedir = $this->get_config('ftp_basedir');
$dir_rules = intval($this->get_config('chmod_dir'), 8);
if (empty($ftp_server) || empty($ftp_user_name)) {
return false;
}
$dir = str_replace($serendipity['serendipityPath'],"",$dir);
// set up basic connection and log in with username and password
$conn_id = ftp_connect($ftp_server);
$login_result = ftp_login($conn_id, $ftp_user_name, $ftp_user_pass);
// check connection
if ((!$conn_id) || (!$login_result)) {
$this->outputMSG('error',PLUGIN_EVENT_SPARTACUS_FTP_ERROR_CONNECT);
return false;
} else {
$paths = preg_split('@/@', $basedir.$dir,-1,PREG_SPLIT_NO_EMPTY);
foreach ($paths as $path) {
// trying to change directory, if not successful, it means
// the directory does not exist and we must create it
if (!ftp_chdir($conn_id,$path)) {
if (!ftp_mkdir($conn_id,$path)) {
$this->outputMSG('error',PLUGIN_EVENT_SPARTACUS_FTP_ERROR_MKDIR);
return false;
}
if (!ftp_chmod($conn_id,$dir_rules,$path)) {
$this->outputMSG('error',PLUGIN_EVENT_SPARTACUS_FTP_ERROR_CHMOD);
return false;
}
if (!ftp_chdir($conn_id,$path)) {
return false;
}
$this->outputMSG('success',sprintf(PLUGIN_EVENT_SPARTACUS_FTP_SUCCESS, $path));
}
}
ftp_close($conn_id);
return true;
}
}
function count_plugin_upgrades()
{
// get installed plugins
$installedPlugins = serendipity_plugin_api::get_installed_plugins();
// get a list of all installable sidebar and event plugins
$foreignPlugins = $sidebarPlugins = $eventPlugins = array();
$sidebarPlugins = $this->buildList($this->fetchOnline('sidebar'), 'sidebar');
$eventPlugins = $this->buildList($this->fetchOnline('event'), 'event');
$foreignPlugins = array_merge($sidebarPlugins, $eventPlugins);
// count upgradable plugins
$upgradeCount = 0;
foreach ($installedPlugins as $plugin) {
$infoplugin =& serendipity_plugin_api::load_plugin($plugin);
if (is_object($infoplugin)) {
$bag = new serendipity_property_bag;
$infoplugin->introspect($bag);
$currentVersion = $bag->get('version');
if (array_key_exists($plugin, $foreignPlugins)) {
$upgradeVersion = ($foreignPlugins[$plugin]['upgrade_version'] ?? false) ? ($foreignPlugins[$plugin]['upgrade_version'] ?? '0') : $foreignPlugins[$plugin]['version'] ?? '0';
} else {
$upgradeVersion = null;
}
if (version_compare($currentVersion, $upgradeVersion, '<')) {
$upgradeCount++;
}
}
}
return $upgradeCount ?? 0;
}
function event_hook($event, &$bag, &$eventData, $addData = null)
{
global $serendipity;
$hooks = &$bag->get('event_hooks');
if (isset($hooks[$event])) {
switch($event) {
case 'cronjob':
if ($this->get_config('cronjob') == $eventData) {
serendipity_event_cronjob::log('Spartacus', 'plugin');
$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');
#echo "XAVAIL:<pre>" . print_r($avail, true) . "</pre>";
$install['event'] = serendipity_plugin_api::enum_plugin_classes(true);
$install['sidebar'] = serendipity_plugin_api::enum_plugin_classes(false);
#echo "XINSTALL:<pre>" . print_r($install, true) . "</pre>";
$mailtext = '';
foreach($meth AS $method) {
foreach ($install[$method] as $class_data) {
#echo "Probe " . $class_data['name']. "<br />\n"; // DEBUG
$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)) {
#echo "Non cached<br />\n";
#echo "<pre>" . print_r($avail[$method][$class_data['name']], true) . "</pre>";
// 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 "<pre>" . print_r($props, true) . "</pre>";
} elseif (is_array($plugin)) {
// Array is returned if a plugin could be fetched from info cache
$props = $plugin;
#echo "Cached<br />\n";
} else {
$props = false;
#echo "Error<br />\n";
}
if (is_array($props)) {
#echo "<pre>" . print_r($props, true) . "</pre>\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 '<div id="upgrade_notice" class="clearfix">';
if (version_compare($serendipity['version'], '2.1-alpha3', '<')) {
echo ' <a id="upgrade_sidebar" class="button_link" href="?serendipity[adminModule]=plugins&amp;serendipity[adminAction]=addnew&amp;serendipity[only_group]=UPGRADE">'. PLUGIN_EVENT_SPARTACUS_CHECK_SIDEBAR .'</a>';
echo ' <a id="upgrade_event" class="button_link" href="?serendipity[adminModule]=plugins&amp;serendipity[adminAction]=addnew&amp;serendipity[only_group]=UPGRADE&amp;serendipity[type]=event">'. PLUGIN_EVENT_SPARTACUS_CHECK_EVENT .'</a> ';
} else {
$upgradeCount = $this->count_plugin_upgrades();
$upgradeBadge = '';
if ($upgradeCount > 0) {
$upgradeBadge = sprintf(' (%u)', $upgradeCount);
}
echo ' <a id="upgrade_plugins" class="button_link" href="?serendipity[adminModule]=plugins&amp;serendipity[adminAction]=addnew&amp;serendipity[only_group]=UPGRADE' . '&amp;' . serendipity_setFormToken('url') . '">'. PLUGIN_EVENT_SPARTACUS_CHECK . $upgradeBadge .'</a>';
}
echo '</div>';
}
break;
case 'backend_plugins_upgradecount':
if (serendipity_db_bool($this->get_config('enable_plugins'))) {
$upgradeCount = $this->count_plugin_upgrades();
if ($upgradeCount > 0) {
$updateLink = '?serendipity[adminModule]=plugins&amp;serendipity[adminAction]=addnew&amp;serendipity[only_group]=UPGRADE&amp;' . serendipity_setFormToken('url');
if ($upgradeCount > 1) {
$eventData = sprintf(PLUGIN_EVENT_SPARTACUS_DASHBOARD_UPDATES, $upgradeCount, $updateLink);
} else {
$eventData = sprintf(PLUGIN_EVENT_SPARTACUS_DASHBOARD_UPDATE, $updateLink);
}
return true;
}
}
return false;
break;
case 'backend_templates_fetchlist':
if (serendipity_db_bool($this->get_config('enable_themes'))) {
$eventData = $this->buildTemplateList($this->fetchOnline('template', true), 'template');
}
break;
case 'backend_templates_fetchtemplate':
if (serendipity_db_bool($this->get_config('enable_themes'))) {
if (!empty($eventData['GET']['spartacus_fetch'])) {
$this->download(
$this->fetchOnline('template', true),
$eventData['GET']['theme'],
'templates'
);
}
}
break;
case 'backend_plugins_fetchlist':
if (serendipity_db_bool($this->get_config('enable_plugins'))) {
$type = (isset($serendipity['GET']['type']) && !empty($serendipity['GET']['type']) ? $serendipity['GET']['type'] : 'sidebar');
$eventData = array(
'pluginstack' => $this->buildList($this->fetchOnline($type), $type),
'errorstack' => array(),
'upgradeURI' => '&amp;serendipity[spartacus_upgrade]=true',
'baseURI' => '&amp;serendipity[spartacus_fetch]=' . $type
);
}
break;
case 'backend_plugins_fetchplugin':
if (serendipity_db_bool($this->get_config('enable_plugins'))) {
if (!empty($eventData['GET']['spartacus_fetch'])) {
$baseDir = $this->download($this->fetchOnline($eventData['GET']['spartacus_fetch'], true), $eventData['GET']['install_plugin']);
if ($baseDir === false) {
$eventData['install'] = false;
} elseif (!empty($baseDir)) {
$eventData['GET']['pluginPath'] = $baseDir;
} else {
$eventData['GET']['pluginPath'] = $eventData['GET']['install_plugin'];
}
if ($eventData['GET']['spartacus_upgrade'] ?? false) {
$eventData['install'] = false;
}
}
}
break;
case 'backend_directory_create':
if (serendipity_db_bool($this->get_config('use_ftp')) && (!is_dir($eventData))) {
return $this->make_dir_via_ftp($eventData);
}
break;
default:
return false;
}
return true;
} else {
return false;
}
}
}
/* vim: set sts=4 ts=4 expandtab : */
?>