Skip to content

Instantly share code, notes, and snippets.

@hamiltonsw
Last active June 8, 2017 17:57
Show Gist options
  • Save hamiltonsw/6fbb23b99870e0186d6906163f377143 to your computer and use it in GitHub Desktop.
Save hamiltonsw/6fbb23b99870e0186d6906163f377143 to your computer and use it in GitHub Desktop.
PHP Git Webhooks

Webhooks to update files from Git Events (PHP)

Introduction

These files are additions to an OpenCart project I maintain, feel free to use them as you'd please!

Installation

Take a look at how OpenCart is set up and replace the system functions with your appropriate functions ($this->db, $this->request, etc.)

Usage

These files are set up to receive a payload from SVC Services (Github, Deveo, BitBucket, etc.), this can be sent as JSON or Web-Forms, my example is set up to POST to example.com/index.php?route=webhook/deveo with the appropriate POST data. This file can be placed in the root of your site and called directly, or pulled from its source folder via a request router (@see OpenCart).
The files that have been added/modified/deleted are then updated through traditional file writing & CURL.

BOTH FILES REQUIRE CONFIG VARIABLES WITH YOUR APPROPRIATE GIT USER INFO!

Included

Included are two files, one is for updating from Deveo & Github. Other services may require minor tweaks but should be extremely similar.

<?php
class ControllerWebhookDeveo extends Controller {
public function index() {
ignore_user_abort(true);
set_time_limit(0);
// if signature is sent, verify it before proceeding
if (isset($this->request->server['HTTP_X_HUB_SIGNATURE'])) {
$signature_verified = false;
if ($this->config->get('config_deveo_secret')) {
list($algorithm, $hash) = explode('=', $this->request->server['HTTP_X_HUB_SIGNATURE'], 2) + array('', '');
if ($hash === hash_hmac($algorithm, file_get_contents('php://input'), $this->config->get('config_deveo_secret'))) {
$signature_verified = true;
}
}
if (!$signature_verified) {
return;
}
}
if (!empty($this->request->post)) {
$payload = $this->request->post;
} else {
$payload = false;
}
// make sure this is a push request for the correct branch
if ($payload && isset($payload['ref']) && $payload['ref'] == 'refs/heads/' . $this->config->get('config_deveo_branch_id')) {
if (!empty($payload['commits'])) {
$changes = [];
$project_id = $this->config->get('config_deveo_project_id');
$repository_id = $this->config->get('config_deveo_repository_id');
$account_key = $this->config->get('config_deveo_account_key');
$company_key = $this->config->get('config_deveo_company_key');
$plugin_key = $this->config->get('config_deveo_plugin_key');
foreach ($payload['commits'] as $commit) {
$time = new \DateTime($commit['timestamp']);
$timestamp = $time->format('U');
if (!empty($commit['removed'])) {
$dir_array = array();
foreach ($commit['removed'] as $file => $value) {
// only use files in the upload folder and strip upload/ out
if (strpos($value, 'upload/') === 0) {
$file = substr($value, 7);
} else {
continue;
}
if (isset($changes[$file])) {
if ($changes[$file]['timestamp'] < $timestamp) {
unset($changes[$file]);
$changes[$file] = array(
'file' => $file,
'timestamp' => $timestamp,
'action' => 'removed',
'id' => $commit['id']
);
}
} else {
$changes[$file] = array(
'file' => $file,
'timestamp' => $timestamp,
'action' => 'removed',
'id' => $commit['id']
);
}
}
}
if (!empty($commit['added'])) {
foreach ($commit['added'] as $file => $value) {
// only use files in the upload folder and strip upload/ out
if (strpos($value, 'upload/') === 0) {
$file = substr($value, 7);
} else {
continue;
}
if (isset($changes[$file])) {
if ($changes[$file]['timestamp'] < $timestamp) {
unset($changes[$file]);
$changes[$file] = array(
'file' => $file,
'timestamp' => $timestamp,
'action' => 'added',
'id' => $commit['id']
);
}
} else {
$changes[$file] = array(
'file' => $file,
'timestamp' => $timestamp,
'action' => 'added',
'id' => $commit['id']
);
}
}
}
if (!empty($commit['modified'])) {
foreach ($commit['modified'] as $file => $value) {
// only use files in the upload folder and strip upload/ out
if (strpos($value, 'upload/') === 0) {
$file = substr($value, 7);
} else {
continue;
}
if (isset($changes[$file])) {
if ($changes[$file]['timestamp'] < $timestamp) {
unset($changes[$file]);
$changes[$file] = array(
'file' => $file,
'timestamp' => $timestamp,
'action' => 'modified',
'id' => $commit['id']
);
}
} else {
$changes[$file] = array(
'file' => $file,
'timestamp' => $timestamp,
'action' => 'modified',
'id' => $commit['id']
);
}
}
}
}
if (!empty($changes)) {
if ($this->config->get('config_deveo_debug')) {
$this->log->write('Update From Deveo Webhook');
$this->log->write('Commits Added: ' . $payload['commit_count']);
}
// Sort by Timestamp ascending
function sortByTimestamp($a, $b) {
return $a['timestamp'] - $b['timestamp'];
}
usort($changes, "sortByTimestamp");
// Perform all the changes based on action type
foreach ($changes as $change) {
if ($change['action'] == 'removed') {
if (file_exists($change['file'])) {
unlink($change['file']);
}
if ($this->config->get('config_deveo_debug')) {
$this->log->write('Removed: ' . $change['file']);
}
$this->deleteDirectory(dirname($change['file']));
}
if ($change['action'] == 'added') {
$this->getFile($change['file'], $project_id, $repository_id, $change['id'], $account_key, $company_key, $plugin_key);
if ($this->config->get('config_deveo_debug')) {
$this->log->write('Added: ' . $change['file']);
}
}
if ($change['action'] == 'modified') {
if ($this->config->get('config_deveo_debug')) {
$this->log->write('Modified: ' . $change['file']);
}
$this->getFile($change['file'], $project_id, $repository_id, $change['id'], $account_key, $company_key, $plugin_key);
}
}
}
}
}
$this->response->setOutput('OK');
}
private function getFile($file, $project, $repository, $id, $account_key, $company_key, $plugin_key) {
$dirname = dirname($file);
if (!is_dir($dirname)) {
mkdir($dirname, 0775, true);
}
if (file_exists($file)) {
unlink($file);
}
$fp = fopen($file, 'w');
$curl = curl_init('https://app.deveo.com/api/blob?path=upload/' . $file . '&project_id=' . $project . '&repository_id=' . $repository . '&id=' . $id . '&account_key=' . $account_key . '&company_key=' . $company_key . '&plugin_key=' . $plugin_key);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FORBID_REUSE, true);
curl_setopt($curl, CURLOPT_FRESH_CONNECT, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
curl_setopt($curl, CURLOPT_FILE, $fp);
curl_exec($curl);
curl_close($curl);
fclose($fp);
chmod($file, 0664);
}
private function deleteDirectory($path) {
$parts = explode('/', $path);
while ($parts) {
$dir = implode('/', $parts);
if (file_exists($dir)) {
if ($this->config->get('config_deveo_debug')) {
$this->log->write('Checking if directory is empty: ' . $dir);
}
if (!(new \FilesystemIterator($dir))->valid()) {
rmdir($dir);
if ($this->config->get('config_deveo_debug')) {
$this->log->write('Removed: ' . $dir);
}
}
}
array_pop($parts);
}
}
}
<?php
class ControllerWebhookGithub extends Controller {
public function index() {
ignore_user_abort(true);
set_time_limit(0);
// if signature is sent, verify it before proceeding
if (isset($this->request->server['HTTP_X_HUB_SIGNATURE'])) {
$signature_verified = false;
if ($this->config->get('config_github_secret')) {
list($algorithm, $hash) = explode('=', $this->request->server['HTTP_X_HUB_SIGNATURE'], 2) + array('', '');
if ($hash === hash_hmac($algorithm, file_get_contents('php://input'), $this->config->get('config_github_secret'))) {
$signature_verified = true;
}
}
if (!$signature_verified) {
return;
}
}
if (!empty($this->request->post['payload'])) {
$payload = json_decode(htmlspecialchars_decode($this->request->post['payload'], ENT_COMPAT));
} else {
$payload = false;
}
// make sure this is a push request for the correct branch
if ($payload && isset($payload->ref) && $payload->ref == 'refs/heads/' . $this->config->get('config_github_branch')) {
if (!empty($payload->commits)) {
foreach ($payload->commits as $commit) {
if ($this->config->get('config_github_debug')) {
$this->log->write('Author: ' . $commit->author->name);
$this->log->write('Message: ' . $commit->message);
}
if (!empty($commit->added)) {
foreach ($commit->added as $file) {
// only use files in the upload folder and strip upload/ out
if (strpos($file, 'upload/') === 0) {
$file = substr($file, 7);
} else {
continue;
}
if ($this->config->get('config_github_debug')) {
$this->log->write('Added: ' . $file);
}
$this->getFile($file, $payload->repository->full_name, $payload->after, $this->config->get('config_github_username'), $this->config->get('config_github_access_token'));
}
}
if (!empty($commit->removed)) {
$dir_array = array();
foreach ($commit->removed as $file) {
// only use files in the upload folder and strip upload/ out
if (strpos($file, 'upload/') === 0) {
$file = substr($file, 7);
} else {
continue;
}
$dirname = dirname($file);
if (file_exists($file)) {
unlink($file);
}
if (!in_array($dirname, $dir_array)) {
$dir_array[] = $dirname;
}
if ($this->config->get('config_github_debug')) {
$this->log->write('Removed: ' . $file);
}
}
if (!empty($dir_array)) {
foreach ($dir_array as $dir) {
$this->deleteDirectory($dir);
}
}
}
if (!empty($commit->modified)) {
foreach ($commit->modified as $file) {
// only use files in the upload folder and strip upload/ out
if (strpos($file, 'upload/') === 0) {
$file = substr($file, 7);
} else {
continue;
}
if ($this->config->get('config_github_debug')) {
$this->log->write('Modified: ' . $file);
}
$this->getFile($file, $payload->repository->full_name, $payload->after, $this->config->get('config_github_username'), $this->config->get('config_github_access_token'));
}
}
}
}
}
$this->response->setOutput('OK');
}
private function getFile($file, $repository, $commit_id, $username, $token) {
$dirname = dirname($file);
if (!is_dir($dirname)) {
mkdir($dirname, 0775, true);
}
if (file_exists($file)) {
unlink($file);
}
$fp = fopen($file, 'w');
$curl = curl_init('https://raw.githubusercontent.com/' . $repository . '/' . $commit_id . '/upload/' . $file);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $token);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FORBID_REUSE, true);
curl_setopt($curl, CURLOPT_FRESH_CONNECT, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
curl_setopt($curl, CURLOPT_FILE, $fp);
curl_exec($curl);
curl_close($curl);
}
private function deleteDirectory($path) {
$parts = explode('/', $path);
while ($parts) {
$dir = implode('/', $parts);
if (file_exists($dir)) {
if ($this->config->get('config_github_debug')) {
$this->log->write('Checking if directory is empty: ' . $dir);
}
if (!(new \FilesystemIterator($dir))->valid()) {
rmdir($dir);
if ($this->config->get('config_github_debug')) {
$this->log->write('Removed: ' . $dir);
}
}
}
array_pop($parts);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment