'geshi' => 'MediaWiki/geshi/geshi/geshi.php',
'index' => 'new',//"new" or "list"
'perPage' => 10,
- 'webhooks' => array(
- /* array of urls that get called when
- a paste is created, edited or deleted */
- )
+ 'notificator' => array(
+ /* send out pingback/webmentions when a remote paste is forked */
+ 'linkback' => true,
+ 'webhook' => array(
+ /* array of urls that get called when
+ a paste is created, edited or deleted */
+ )
+ ),
);
$GLOBALS['phorkie']['auth'] = array(
// 0 = public, no authentication, 1 = protect adds/edits/deletes,
{% set conns = repo.getConnectionInfo() %}
{% if conns.isFork() %}
{% set origin = conns.getOrigin() %}
- <h4>Fork of</h4>
+ <h4 id="parent">Fork of</h4>
<p>
{% set webpage = origin.getWebURL() %}
{% if webpage %}
{% else %}
{{origin.getTitle()}}
{% endif %}
- (<a href="{{origin.getCloneUrl()}}">clone URL</a>)
+ <a href="{{origin.getCloneUrl()}}" title="Clone URL"><i class="icon-globe"></i></a>
</p>
+{% endif %}
+{% if conns.hasForks() %}
+ <h4 id="forks">Forks</h4>
+ <ul>
+ {% for remote in conns.getForks %}
+ <li>
+ {% set webpage = remote.getWebURL() %}
+ {% if webpage %}
+ <a href="{{webpage}}">{{remote.getTitle()}}</a>
+ {% else %}
+ {{remote.getTitle()}}
+ {% endif %}
+
+ {% set cloneUrl = remote.getCloneUrl() %}
+ {% if cloneUrl %}
+ <a href="{{cloneUrl}}" title="Clone URL"><i class="icon-globe"></i></a>
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
{% endif %}
\ No newline at end of file
public function parse()
{
- if ($this->url == '') {
- $this->error = 'Empty fork URL';
- return false;
- }
-
- $arUrl = parse_url($this->url);
- $scheme = isset($arUrl['scheme']) ? $arUrl['scheme'] : '';
-
- if ($scheme == 'https' && isset($arUrl['host'])
- && $arUrl['host'] == 'gist.github.com'
- ) {
- $this->arGitUrls[][] = 'git://gist.github.com/'
- . ltrim($arUrl['path'], '/') . '.git';
- return true;
- }
-
- switch ($scheme) {
- case 'git':
- //clearly a git url
- $this->arGitUrls = array(array($this->url));
- return true;
-
- case 'ssh':
- //FIXME: maybe loosen this when we know how to skip the
- //"do you trust this server" question of ssh
- $this->error = 'ssh:// URLs are not supported';
- return false;
-
- case 'http':
- case 'https':
- return $this->extractUrlsFromHtml($this->url);
- }
-
- $this->error = 'Unknown URLs scheme: ' . $scheme;
- return false;
- }
-
- protected function extractUrlsFromHtml($url)
- {
- //HTML is not necessarily well-formed, and Gitorious has many problems
- // in this regard
- //$sx = simplexml_load_file($url);
- libxml_use_internal_errors(true);
- $sx = simplexml_import_dom(\DomDocument::loadHtmlFile($url));
- $elems = $sx->xpath('//*[@rel="vcs-git"]');
- $titles = $sx->xpath('/html/head/title');
- $pageTitle = $this->cleanPageTitle((string) reset($titles));
-
- $count = $anonymous = 0;
- foreach ($elems as $elem) {
- if (!isset($elem['href'])) {
- continue;
- }
- $str = (string)$elem;
- if (isset($elem['title'])) {
- //<link href=".." rel="vcs-git" title="title" />
- $title = (string)$elem['title'];
- } else if ($str != '') {
- //<a href=".." rel="vcs-git">title</a>
- $title = $str;
- } else if ($pageTitle != '') {
- $title = $pageTitle;
- } else {
- $title = 'Unnamed repository #' . ++$anonymous;
- }
- $url = (string)$elem['href'];
- if ($this->isSupported($url)) {
- ++$count;
- $this->arGitUrls[$title][] = $url;
- }
- }
-
- if ($count > 0) {
- return true;
- }
+ $hp = new HtmlParser();
+ $ret = $hp->extractGitUrls($this->url);
+ $this->arGitUrls = $hp->getGitUrls();
+ $this->error = $hp->error;
- $this->error = 'No git:// clone URL found';
- return false;
+ return $ret;
}
/**
{
$this->url = $url;
}
-
- public function isSupported($url)
- {
- $scheme = parse_url($url, PHP_URL_SCHEME);
- return $scheme == 'git'
- || $scheme == 'http' || $scheme == 'https';
- }
-
- /**
- * Remove application names from HTML page titles
- *
- * @param string $title HTML page title
- *
- * @return string Cleaned HTML page title
- */
- protected function cleanPageTitle($title)
- {
- $title = trim($title);
- if (substr($title, -9) == '- phorkie') {
- $title = trim(substr($title, 0, -9));
- }
-
- return $title;
- }
}
-
?>
--- /dev/null
+<?php
+namespace phorkie;
+
+class HtmlParser
+{
+ /**
+ * Contains error message when parse() failed
+ */
+ public $error;
+
+ /**
+ * Array with keys (URL title) and values (arrays of urls)
+ * Only supported URLs are included.
+ *
+ * @var array
+ */
+ protected $arGitUrls;
+
+
+
+ /**
+ * Extract git URLs from the given URL, eventually fetching
+ * HTML and extracting URLs from there.
+ *
+ * Sets $error and $arGitUrls class variables
+ *
+ * @param string $url Git or HTTP URL
+ * @param string $html HTML content of $url
+ *
+ * @return boolean True when all went well, false in case of an error
+ * @uses $error
+ * @uses $arGitUrls
+ */
+ public function extractGitUrls($url, $html = null)
+ {
+ if ($url == '') {
+ $this->error = 'Empty fork URL';
+ return false;
+ }
+
+ $arUrl = parse_url($url);
+ $scheme = isset($arUrl['scheme']) ? $arUrl['scheme'] : '';
+
+ if ($scheme == 'https' && isset($arUrl['host'])
+ && $arUrl['host'] == 'gist.github.com'
+ ) {
+ //FIXME: title
+ $this->arGitUrls[][] = 'git://gist.github.com/'
+ . ltrim($arUrl['path'], '/') . '.git';
+ return true;
+ }
+
+ switch ($scheme) {
+ case 'git':
+ //clearly a git url
+ $this->arGitUrls = array(array($url));
+ return true;
+
+ case 'ssh':
+ //FIXME: maybe loosen this when we know how to skip the
+ //"do you trust this server" question of ssh
+ $this->error = 'ssh:// URLs are not supported';
+ return false;
+
+ case 'http':
+ case 'https':
+ return $this->extractUrlsFromHtml($url, $html);
+ }
+
+ $this->error = 'Unknown URLs scheme: ' . $scheme;
+ return false;
+ }
+
+ protected function extractUrlsFromHtml($url, $html = null)
+ {
+ //HTML is not necessarily well-formed, and Gitorious has many problems
+ // in this regard
+ //$sx = simplexml_load_file($url);
+
+ libxml_use_internal_errors(true);
+ if ($html === null) {
+ $sx = simplexml_import_dom(\DOMDocument::loadHTMLFile($url));
+ } else {
+ $sx = simplexml_import_dom(\DOMDocument::loadHTML($html));
+ }
+
+ $elems = $sx->xpath('//*[@rel="vcs-git"]');
+ $titles = $sx->xpath('/html/head/title');
+ $pageTitle = $this->cleanPageTitle((string) reset($titles));
+
+ $count = $anonymous = 0;
+ foreach ($elems as $elem) {
+ if (!isset($elem['href'])) {
+ continue;
+ }
+ $str = (string)$elem;
+ if (isset($elem['title'])) {
+ //<link href=".." rel="vcs-git" title="title" />
+ $title = (string)$elem['title'];
+ } else if ($str != '') {
+ //<a href=".." rel="vcs-git">title</a>
+ $title = $str;
+ } else if ($pageTitle != '') {
+ $title = $pageTitle;
+ } else {
+ $title = 'Unnamed repository #' . ++$anonymous;
+ }
+ $url = (string)$elem['href'];
+ if ($this->isSupported($url)) {
+ ++$count;
+ $this->arGitUrls[$title][] = $url;
+ }
+ }
+
+ if ($count > 0) {
+ return true;
+ }
+
+ $this->error = 'No git:// clone URL found';
+ return false;
+ }
+
+ public function getGitUrls()
+ {
+ return $this->arGitUrls;
+ }
+
+ /**
+ * Remove application names from HTML page titles
+ *
+ * @param string $title HTML page title
+ *
+ * @return string Cleaned HTML page title
+ */
+ protected function cleanPageTitle($title)
+ {
+ $title = trim($title);
+ if (substr($title, -9) == '- phorkie') {
+ $title = trim(substr($title, 0, -9));
+ }
+
+ return $title;
+ }
+
+ public function isSupported($url)
+ {
+ $scheme = parse_url($url, PHP_URL_SCHEME);
+ return $scheme == 'git'
+ || $scheme == 'http' || $scheme == 'https';
+ }
+
+}
+?>
*/
class Notificator
{
+ protected $notificators = array();
+
+ public function __construct()
+ {
+ $this->loadNotificators();
+ }
+
+ protected function loadNotificators()
+ {
+ foreach ($GLOBALS['phorkie']['cfg']['notificator'] as $type => $config) {
+ $class = '\\phorkie\\Notificator_' . ucfirst($type);
+ $this->notificators[] = new $class($config);
+ }
+ }
+
/**
* A repository has been created
*/
}
/**
- * Call webhook URLs with our payload
+ * Call all notificator plugins
*/
protected function send($event, Repository $repo)
{
- if (count($GLOBALS['phorkie']['cfg']['webhooks']) == 0) {
- return;
- }
-
- /* slightly inspired by
- https://help.github.com/articles/post-receive-hooks */
- $payload = (object) array(
- 'event' => $event,
- 'author' => array(
- 'name' => $_SESSION['name'],
- 'email' => $_SESSION['email']
- ),
- 'repository' => array(
- 'name' => $repo->getTitle(),
- 'url' => $repo->getLink('display', null, true),
- 'description' => $repo->getDescription(),
- 'owner' => $repo->getOwner()
- )
- );
- foreach ($GLOBALS['phorkie']['cfg']['webhooks'] as $url) {
- $req = new \HTTP_Request2($url);
- $req->setMethod(\HTTP_Request2::METHOD_POST)
- ->setHeader('Content-Type: application/vnd.phorkie.webhook+json')
- ->setBody(json_encode($payload));
- try {
- $response = $req->send();
- //FIXME log response codes != 200
- } catch (HTTP_Request2_Exception $e) {
- //FIXME log exceptions
- }
+ foreach ($this->notificators as $notificator) {
+ $notificator->send($event, $repo);
}
}
}
--- /dev/null
+<?php
+namespace phorkie;
+
+/**
+ * Send out linkbacks for the remote paste URL when it gets forked here
+ */
+class Notificator_Linkback
+{
+ protected $config;
+
+ public function __construct($config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * Send linkback on "create" events to remote repositories
+ */
+ public function send($event, Repository $repo)
+ {
+ if ($this->config === false) {
+ return;
+ }
+
+ if ($event != 'create') {
+ return;
+ }
+
+ $origin = $repo->getConnectionInfo()->getOrigin();
+ if ($origin === null) {
+ return;
+ }
+ $originWebUrl = $origin->getWebUrl(true);
+ if ($originWebUrl === null) {
+ return;
+ }
+
+
+ $this->pbc = new \PEAR2\Services\Linkback\Client();
+ $req = $this->pbc->getRequest();
+ $req->setConfig(
+ array(
+ 'ssl_verify_peer' => false,
+ 'ssl_verify_host' => false
+ )
+ );
+ $this->pbc->setRequestTemplate($req);
+ $req->setHeader('user-agent', 'phorkie');
+ try {
+ $res = $this->pbc->send(
+ $repo->getLink('display', null, true),
+ $originWebUrl
+ );
+ } catch (\Exception $e) {
+ //FIXME: log errors
+ }
+ }
+}
+?>
--- /dev/null
+<?php
+namespace phorkie;
+
+/**
+ * Send out webhook callbacks when something happens
+ */
+class Notificator_Webhook
+{
+ protected $config;
+
+ public function __construct($config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * Call webhook URLs with our payload
+ */
+ public function send($event, Repository $repo)
+ {
+ if (count($this->config) == 0) {
+ return;
+ }
+
+ /* slightly inspired by
+ https://help.github.com/articles/post-receive-hooks */
+ $payload = (object) array(
+ 'event' => $event,
+ 'author' => array(
+ 'name' => $_SESSION['name'],
+ 'email' => $_SESSION['email']
+ ),
+ 'repository' => array(
+ 'name' => $repo->getTitle(),
+ 'url' => $repo->getLink('display', null, true),
+ 'description' => $repo->getDescription(),
+ 'owner' => $repo->getOwner()
+ )
+ );
+ foreach ($this->config as $url) {
+ $req = new \HTTP_Request2($url);
+ $req->setMethod(\HTTP_Request2::METHOD_POST)
+ ->setHeader('Content-Type: application/vnd.phorkie.webhook+json')
+ ->setBody(json_encode($payload));
+ try {
+ $response = $req->send();
+ //FIXME log response codes != 200
+ } catch (HTTP_Request2_Exception $e) {
+ //FIXME log exceptions
+ }
+ }
+ }
+}
+?>
+
/**
* Loads the list of files in this repository
*
- * @return File[] Array of files
+ * @return File[] Array of file objects
*/
public function getFiles()
{
return $name;
}
+ /**
+ * Return array with all file paths in this repository
+ *
+ * @return array
+ */
protected function getFilePaths()
{
if ($this->hash === null) {
$link = $this->id . '/delete/confirm';
} else if ($type == 'revision') {
$link = $this->id . '/rev/' . $option;
+ } else if ($type == 'linkback') {
+ $link = $this->id . '/linkback';
} else {
throw new Exception('Unknown link type');
}
return $this->getOrigin() !== null;
}
+ public function hasForks()
+ {
+ return count($this->getForks()) > 0;
+ }
+
public function getOrigin()
{
return new Repository_Remote($name, $this->arConfig['remote ' . $name]);
}
+ public function getForks()
+ {
+ $arForks = array();
+ foreach ($this->arConfig as $name => $data) {
+ if (substr($name, 0, 12) != 'remote fork-') {
+ continue;
+ }
+ $arForks[substr($name, 7)] = new Repository_Remote(
+ substr($name, 7), $data
+ );
+ }
+ return $arForks;
+ }
}
-
-
?>
--- /dev/null
+<?php
+namespace phorkie;
+
+class Repository_LinkbackReceiver
+ implements \PEAR2\Services\Linkback\Server\Callback\IStorage
+{
+ protected $repo;
+
+ public function __construct($repo)
+ {
+ $this->repo = $repo;
+ }
+
+ public function storeLinkback(
+ $target, $source, $sourceBody, \HTTP_Request2_Response $res
+ ) {
+ //FIXME: deleted
+ //FIXME: updated
+ //FIXME: cleanuptask
+
+ $hp = new HtmlParser();
+ $ok = $hp->extractGitUrls($source, $sourceBody);
+ if ($ok === false) {
+ //failed to extract git URL from linkback source
+ //FIXME: send exception
+ //$hp->error
+ return;
+ }
+
+ $ci = $this->repo->getConnectionInfo();
+ $forks = $ci->getForks();
+
+ $remoteCloneUrl = $remoteTitle = null;
+ $arRemoteCloneUrls = array();
+ $arGitUrls = $hp->getGitUrls();
+ foreach ($arGitUrls as $remoteTitle => $arUrls) {
+ foreach ($arUrls as $remoteCloneUrl) {
+ $arRemoteCloneUrls[$remoteCloneUrl] = $remoteTitle;
+ }
+ }
+
+ $remoteid = 'fork-' . uniqid();
+ //check if we already know this remote
+ foreach ($forks as $remote) {
+ if (isset($arRemoteCloneUrls[$remote->getCloneUrl()])
+ || $source == $remote->getWebURL(true)
+ ) {
+ $remoteid = $remote->getName();
+ break;
+ }
+ }
+
+ if ($this->isLocalWebUrl($source)) {
+ //convert both web and clone url to local urls
+ }
+
+ $vc = $this->repo->getVc();
+ $vc->getCommand('config')
+ ->addArgument('remote.' . $remoteid . '.homepage')
+ ->addArgument($source)
+ ->execute();
+ if ($remoteTitle !== null) {
+ $vc->getCommand('config')
+ ->addArgument('remote.' . $remoteid . '.title')
+ ->addArgument($remoteTitle)
+ ->execute();
+ }
+ if ($remoteCloneUrl !== null) {
+ $vc->getCommand('config')
+ ->addArgument('remote.' . $remoteid . '.url')
+ ->addArgument($remoteCloneUrl)
+ ->execute();
+ }
+ }
+
+ protected function isLocalWebUrl($url)
+ {
+ $base = Tools::fullUrl();
+ if (substr($url, 0, strlen($base)) != $base) {
+ //base does not match
+ return false;
+ }
+
+ $remainder = substr($url, strlen($base));
+ //FIXME: check if it exists
+ }
+}
+?>
}
+ public function getName()
+ {
+ return $this->name;
+ }
+
public function getTitle()
{
if (isset($this->arConfig['title'])) {
}
}
- return $this->arConfig['url'];
+ if (isset($this->arConfig['url'])) {
+ return $this->arConfig['url'];
+ }
+ return null;
}
- public function getWebURL()
+ public function getWebURL($full = false)
{
if (isset($this->arConfig['homepage'])) {
return $this->arConfig['homepage'];
if ($this->isLocal()) {
$local = $this->getLocalRepository();
if ($local !== null) {
- return $local->getLink('display');
+ return $local->getLink('display', null, $full);
}
}
RewriteRule ^([0-9]+)/doap$ doap.php?id=$1
RewriteRule ^([0-9]+)/edit$ edit.php?id=$1
RewriteRule ^([0-9]+)/fork$ fork.php?id=$1
+RewriteRule ^([0-9]+)/linkback$ linkback.php?id=$1
RewriteRule ^([0-9]+)/raw/(.+)$ raw.php?id=$1&file=$2
RewriteRule ^([0-9]+)/rev/(.+)$ revision.php?id=$1&rev=$2
RewriteRule ^([0-9]+)/rev-raw/(.+)/(.+)$ raw.php?id=$1&rev=$2&file=$3
$repo = new Repository();
$repo->loadFromRequest();
+header('X-Pingback: ' . $repo->getLink('linkback', null, true));
+header(
+ 'Link: <' . $repo->getLink('linkback', null, true) . '>;'
+ . 'rel="http://webmention.org/"'
+);
+
render(
'display',
array(
--- /dev/null
+<?php
+namespace phorkie;
+/**
+ * Receive linkback
+ */
+$reqWritePermissions = false;
+require_once 'www-header.php';
+
+$repo = new Repository();
+$repo->loadFromRequest();
+
+$s = new \PEAR2\Services\Linkback\Server();
+$s->addCallback(new Repository_LinkbackReceiver($repo));
+$s->run();
+?>
\ No newline at end of file