aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/activitypub-actor.php75
-rw-r--r--lib/activitypub-followers.php70
-rw-r--r--lib/activitypub-functions.php521
-rw-r--r--lib/activitypub-inbox.php196
-rw-r--r--lib/activitypub-outbox.php72
-rw-r--r--lib/activitypub-webfinger.php24
-rw-r--r--lib/database.php2
-rw-r--r--lib/functions.php24
-rw-r--r--lib/twitter_api.php410
9 files changed, 2 insertions, 1392 deletions
diff --git a/lib/activitypub-actor.php b/lib/activitypub-actor.php
deleted file mode 100644
index b34f582..0000000
--- a/lib/activitypub-actor.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
- if(!$config['activitypub']) exit('ActivityPub is disabled via config file.');
-
- $public_key = activitypub_get_key('public');
-
- // generate a key pair, if neccessary
- if(!$public_key) {
- $key = activitypub_new_key('sha512', 4096, 'RSA');
-
- if(!empty($key)) exit('Fatal error: Could not generate a new key!');
- $public_key = $key['key_public'];
- }
-
- /*
- // old, file-based key system
- if(!file_exists(ROOT.DS.'keys'.DS.'id_rsa')) {
- if(!is_dir(ROOT.DS.'keys')) {
- mkdir(ROOT.DS.'keys');
- }
-
- // generate a key pair, if neccessary
- $rsa = openssl_pkey_new([
- 'digest_alg' => 'sha512',
- 'private_key_bits' => 4096,
- 'private_key_type' => OPENSSL_KEYTYPE_RSA,
- ]);
- openssl_pkey_export($rsa, $private_key);
- $public_key = openssl_pkey_get_details($rsa)['key'];
-
- file_put_contents(ROOT.DS.'keys'.DS.'id_rsa', $private_key);
- file_put_contents(ROOT.DS.'keys'.DS.'id_rsa.pub', $public_key);
- } else {
- $public_key = file_get_contents(ROOT.DS.'keys'.DS.'id_rsa.pub');
- }
- */
-
- if(strpos($_SERVER['HTTP_ACCEPT'], 'application/activity+json') !== false):
-
- header('Content-Type: application/ld+json');
-
-?>{
- "@context": [
- "https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1"
- ],
- "id": "<?= $config['url'] ?>/actor",
- "type": "Person",
- "name": "<?= trim($config['site_title']) ?>",
- "summary": "<?= trim($config['site_claim']) ?>",
- "preferredUsername": "<?= ltrim($config['microblog_account'], '@') ?>",
- "manuallyApprovesFollowers": false,
- "discoverable": true,
- "publishedDate": "2023-01-01T00:00:00Z",
- "icon": {
- "url": "<?= $config['url'] ?>/favicon-large.png",
- "mediaType": "image/png",
- "type": "Image"
- },
- "inbox": "<?= $config['url'] ?>/inbox",
- "outbox": "<?= $config['url'] ?>/outbox",
- "followers": "<?= $config['url'] ?>/followers",
- "publicKey": {
- "id": "<?= $config['url'] ?>/actor#main-key",
- "owner": "<?= $config['url'] ?>/actor",
- "publicKeyPem": "<?= preg_replace('/\n/', '\n', $public_key) ?>"
- }
-}
-<?php
- else:
- // this is for people who click through to the profile URL in their mastodon client
- header('Location: '.$config['url']);
- exit();
- endif;
-?>
diff --git a/lib/activitypub-followers.php b/lib/activitypub-followers.php
deleted file mode 100644
index bc21ab5..0000000
--- a/lib/activitypub-followers.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-
-if(!$config['activitypub']) exit('ActivityPub is disabled via config file.');
-
-// get total amount
-$statement = $db->prepare('SELECT COUNT(id) as total FROM followers WHERE follower_actor IS NOT NULL');
-$statement->execute();
-$followers_total = $statement->fetchAll(PDO::FETCH_ASSOC);
-$followers_total = (!empty($followers_total)) ? $followers_total[0]['total'] : 0;
-
-if(!isset($_GET['page'])):
-
- $output = [
- '@context' => 'https://www.w3.org/ns/activitystreams',
- 'id' => $config['url'].'/followers',
- 'type' => 'OrderedCollection',
- 'totalItems' => $followers_total,
- 'first' => $config['url'].'/followers?page=1',
- ];
-
- header('Content-Type: application/ld+json');
- echo(json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
-else:
-
- // get items
- $items_per_page = 12; // mastodon default?
-
- // pagination
- $current_page = (isset($_GET['page']) && is_numeric($_GET['page'])) ? (int) $_GET['page'] : 1;
- $total_pages = ceil($followers_total / $items_per_page);
- $offset = ($current_page-1)*$items_per_page;
-
- if($current_page < 1 || $current_page > $total_pages) {
- http_response_code(404);
- header('Content-Type: application/ld+json');
- die('{}');
- }
-
- $statement = $db->prepare('SELECT follower_actor FROM followers WHERE follower_actor IS NOT NULL ORDER BY follower_added ASC LIMIT :limit OFFSET :page');
- $statement->bindValue(':limit', $items_per_page, PDO::PARAM_INT);
- $statement->bindValue(':page', $offset, PDO::PARAM_INT);
- $statement->execute();
- $followers = $statement->fetchAll(PDO::FETCH_ASSOC);
-
- $ordered_items = [];
- if(!empty($followers)) {
- $ordered_items = array_column($followers, 'follower_actor');
- }
-
- $output = [
- '@context' => 'https://www.w3.org/ns/activitystreams',
- 'id' => $config['url'].'/followers?page='.$current_page,
- 'type' => 'OrderedCollectionPage',
- 'totalItems' => $followers_total,
- 'partOf' => $config['url'].'/followers'
- ];
-
- if($current_page > 1) {
- $output['prev'] = $config['url'].'/followers?page='.($current_page-1);
- }
-
- if($current_page < $total_pages) {
- $output['next'] = $config['url'].'/followers?page='.($current_page+1);
- }
-
- $output['orderedItems'] = $ordered_items;
-
- header('Content-Type: application/ld+json');
- echo(json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
-endif;
diff --git a/lib/activitypub-functions.php b/lib/activitypub-functions.php
deleted file mode 100644
index 4b427eb..0000000
--- a/lib/activitypub-functions.php
+++ /dev/null
@@ -1,521 +0,0 @@
-<?php
-
-if($config['subdir_install'] == true && $config['activitypub'] == true) {
- exit('For ActivityPub to work, you can\'t be running in a subdirectory, sadly.');
-}
-
-function ap_log($name, $data) {
- // file_put_contents(ROOT.DS.'inbox-log.txt', date('H:i:s ').$name.":\n".$data."\n\n", FILE_APPEND | LOCK_EX);
-}
-
-function activitypub_new_key($algo = 'sha512', $bits = 4096, $type = 'rsa') {
- global $db;
-
- $key_type = (mb_strtolower($type) == 'rsa') ? OPENSSL_KEYTYPE_RSA : $type; // todo: improve!
-
- $rsa = openssl_pkey_new([
- 'digest_alg' => $algo,
- 'private_key_bits' => $bits,
- 'private_key_type' => $key_type
- ]);
- openssl_pkey_export($rsa, $private_key);
- $public_key = openssl_pkey_get_details($rsa)['key'];
- $created = time();
-
- try {
- $statement = $db->prepare('INSERT INTO keys (key_private, key_public, key_algo, key_bits, key_type, key_created) VALUES (:private, :public, :algo, :bits, :type, :created)');
-
- $statement->bindValue(':private', $private_key, PDO::PARAM_STR);
- $statement->bindValue(':public', $public_key, PDO::PARAM_STR);
- $statement->bindValue(':algo', $algo, PDO::PARAM_STR);
- $statement->bindValue(':bits', $bits, PDO::PARAM_INT);
- $statement->bindValue(':type', mb_strtolower($type), PDO::PARAM_STR);
- $statement->bindValue(':created', $created, PDO::PARAM_INT);
-
- $statement->execute();
-
- } catch(PDOException $e) {
- ap_log('ERROR', $e->getMessage());
- return false;
- }
-
- if($db->lastInsertId() > 0) {
- return [
- 'id' => $db->lastInsertId(),
- 'key_private' => $private_key,
- 'key_public' => $public_key,
- 'key_algo' => $algo,
- 'key_bits' => $bits,
- 'key_type' => mb_strtolower($type),
- 'key_created' => $created
- ];
- }
- return false;
-}
-
-function activitypub_get_key($type = 'public') {
- global $db;
-
- $sql = '';
-
- if($type == 'public') {
- $sql = 'SELECT key_public FROM keys ORDER BY key_created DESC LIMIT 1';
- } elseif($type == 'private') {
- $sql = 'SELECT key_private FROM keys ORDER BY key_created DESC LIMIT 1';
- } else {
- $sql = 'SELECT * FROM keys ORDER BY key_created DESC LIMIT 1';
- }
-
- try {
- $statement = $db->prepare($sql);
-
- $statement->execute();
- } catch(PDOException $e) {
- ap_log('ERROR', $e->getMessage());
- return false;
- }
-
- $key = $statement->fetch(PDO::FETCH_ASSOC);
-
- if(!empty($key)) {
- if($type == 'public') {
- return $key['key_public'];
- } elseif($type == 'private') {
- return $key['key_private'];
- } else {
- return $key;
- }
- }
-
- return false;
-}
-
-function activitypub_get_actor_url($handle, $full_profile = false) {
- list($user, $host) = explode('@', ltrim($handle, '@'));
-
- $ch = curl_init();
-
- $url = sprintf('https://%s/.well-known/webfinger?resource=acct%%3A%s', $host, urlencode($user.'@'.$host));
-
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
- $server_response = curl_exec($ch);
- // ap_log('WEBFINGER RESPONSE', $server_response);
-
- curl_close($ch);
-
- $profile = json_decode($server_response, true);
- if($full_profile) {
- return $profile;
- }
-
- // make this more robust by iterating over links where href = self?
- return $profile['links'][1]['href'];
-}
-
-function activitypub_get_actor_data($actor_url='') {
- if(empty($actor_url)) return false;
-
- $opts = [
- "http" => [
- "method" => "GET",
- "header" => join("\r\n", [
- "Accept: application/activity+json",
- "Content-type: application/activity+json",
- ])
- ]
- ];
-
- $context = stream_context_create($opts);
-
- $file = @file_get_contents($actor_url, false, $context); // fix?
-
- if(!empty($file)) {
- return json_decode($file, true);
- }
-
- return false;
-}
-
-function activitypub_plaintext($path, $host, $date, $digest, $type='application/activity+json'): string {
- $plaintext = sprintf(
- "(request-target): post %s\nhost: %s\ndate: %s\ndigest: %s\ncontent-type: %s",
- $path,
- $host,
- $date,
- $digest,
- $type
- );
-
- // ap_log('PLAINTEXT', $plaintext);
-
- return $plaintext;
-}
-
-function activitypub_digest(string $data): string {
- return sprintf('SHA-256=%s', base64_encode(hash('sha256', $data, true)));
-}
-
-function activitypub_sign($path, $host, $date, $digest): string {
- $private_key = activitypub_get_key('private');
-
- openssl_sign(activitypub_plaintext($path, $host, $date, $digest), $signature, openssl_get_privatekey($private_key), OPENSSL_ALGO_SHA256);
-
- return $signature;
-}
-
-function activitypub_verify(string $signature, string $pubkey, string $plaintext): bool {
- return openssl_verify($plaintext, base64_decode($signature), $pubkey, OPENSSL_ALGO_SHA256);
-}
-
-function activitypub_send_request($host, $path, $data): void {
- global $config;
-
- $encoded = json_encode($data);
-
- $date = gmdate('D, d M Y H:i:s T', time());
- $digest = activitypub_digest($encoded);
-
- $signature = activitypub_sign(
- $path,
- $host,
- $date,
- $digest
- );
-
- $signature_header = sprintf(
- 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="%s"',
- $config['url'].'/actor#main-key',
- base64_encode($signature)
- );
-
- // DEBUG
- $fp = fopen(ROOT.DS.'inbox-log.txt', 'a');
-
- $curl_headers = [
- 'Content-Type: application/activity+json',
- 'Date: ' . $date,
- 'Signature: ' . $signature_header,
- 'Digest: ' . $digest
- ];
-
- ap_log('SEND MESSAGE', json_encode([$data, $curl_headers], JSON_PRETTY_PRINT));
-
- $ch = curl_init();
-
- curl_setopt($ch, CURLOPT_URL, sprintf('https://%s%s', $host, $path));
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
- curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
- curl_setopt($ch, CURLOPT_HEADER, 1);
- curl_setopt($ch, CURLOPT_VERBOSE, false);
- curl_setopt($ch, CURLOPT_STDERR, $fp);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
- $server_output = curl_exec($ch);
-
- curl_close($ch);
- fclose($fp);
-
- ap_log('SERVER RESPONSE', $server_output);
-}
-
-function activitypub_activity_from_post($post, $json=false) {
- global $config;
-
- if(empty($post)) return false;
-
- $output = [
- '@context' => 'https://www.w3.org/ns/activitystreams',
-
- 'id' => $config['url'].'/'.$post['id'].'/json',
- 'type' => 'Create',
- 'actor' => $config['url'].'/actor',
- 'to' => ['https://www.w3.org/ns/activitystreams#Public'],
- 'cc' => [$config['url'].'/followers'],
- 'object' => [
- 'id' => $config['url'].'/'.$post['id'],
- 'type' => 'Note',
- 'published' => gmdate('Y-m-d\TH:i:s\Z', $post['post_timestamp']),
- 'attributedTo' => $config['url'].'/actor',
- 'content' => filter_tags($post['post_content']),
- 'to' => ['https://www.w3.org/ns/activitystreams#Public']
- ]
- ];
-
- $attachments = db_get_attached_files($post['id']);
-
- if(!empty($attachments) && !empty($attachments[$post['id']])) {
- $output['object']['attachment'] = [];
-
- foreach ($attachments[$post['id']] as $key => $a) {
- if(strpos($a['file_mime_type'], 'image') !== 0) continue; // skip non-image files
-
- $url = $config['url'] .'/'. get_file_path($a);
-
- $output['object']['attachment'][] = [
- 'type' => 'Image',
- 'mediaType' => $a['file_mime_type'],
- 'url' => $url,
- 'name' => null
- ];
- }
- }
-
- if ($json) {
- return json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- }
-
- return $output;
-}
-
-function activitypub_notify_followers($post_id): void {
- global $db;
- // todo: make this a queue
-
- // API ENDPOINTS
- // https://mastodon.social/api/v2/instance
-
- // users without shared inbox
- $statement = $db->prepare('SELECT * FROM followers WHERE follower_shared_inbox IS NULL');
- $statement->execute();
- $followers = $statement->fetchAll(PDO::FETCH_ASSOC);
-
- // users with shared inbox
- $statement = $db->prepare('SELECT follower_shared_inbox as shared_inbox, GROUP_CONCAT(follower_name) as shared_inbox_followers FROM followers WHERE follower_shared_inbox IS NOT NULL GROUP BY follower_shared_inbox');
- $statement->execute();
- $shared_inboxes = $statement->fetchAll(PDO::FETCH_ASSOC);
-
- // get the activity data, eg. https://microblog.oelna.de/11/json
- $post = db_select_post($post_id);
- $post_activity = activitypub_activity_from_post($post);
-
- $update = [
- 'id' => null,
- 'inbox' => null,
- 'actor' => null
- ];
-
- // prepare db for possible updates
- $statement = $db->prepare('UPDATE followers SET follower_inbox = :inbox, follower_actor = :actor WHERE id = :id');
- $statement->bindParam(':id', $update['id'], PDO::PARAM_INT);
- $statement->bindParam(':inbox', $update['inbox'], PDO::PARAM_STR);
- $statement->bindParam(':actor', $update['actor'], PDO::PARAM_STR);
-
- // iterate over shared inboxes to deliver those quickly
- foreach($shared_inboxes as $inbox) {
- $info = parse_url($inbox['shared_inbox']);
- // ap_log('SHARED_INBOX_DELIVERY', json_encode([$inbox, $info, $post_activity], JSON_PRETTY_PRINT));
- // todo: verify we don't need to handle single usernames here
- // using the followers URL as CC is enough?
- activitypub_send_request($info['host'], $info['path'], $post_activity);
- }
-
- // iterate over followers and send create activity
- foreach($followers as $follower) {
-
- // retrieve actor info, if missing (is this necessary?)
- if(empty($follower['follower_inbox'])) {
-
- $actor_url = activitypub_get_actor_url($follower['follower_name'].'@'.$follower['follower_host']);
- if (empty($actor_url)) continue;
-
- $actor_data = activitypub_get_actor_data($actor_url);
- if (empty($actor_data) || empty($actor_data['inbox'])) continue;
-
- // cache this info
- $update['id'] = $follower['id'];
- $update['inbox'] = $actor_data['inbox'];
- $update['actor'] = $actor_url;
-
- try {
- $statement->execute();
- } catch(PDOException $e) {
- continue;
- }
-
- $follower['follower_inbox'] = $actor_data['inbox'];
- }
-
- $info = parse_url($follower['follower_inbox']);
-
- activitypub_send_request($info['host'], $info['path'], $post_activity);
-
- ap_log('SENDING TO', json_encode([$info['host'], $info['path']], JSON_PRETTY_PRINT));
- }
-}
-
-function activitypub_post_from_url($url="") {
- // todo: this should be more robust and conform to url scheme on this site
-
- $path = parse_url($url, PHP_URL_PATH);
-
- $items = explode('/', $path);
- $post_id = end($items);
-
- if (is_numeric($post_id)) {
- return (int) $post_id;
- }
-
- return false;
-}
-
-function activitypub_do($type, $user, $host, $post_id) {
- if (empty($type)) return false;
-
- global $db;
-
- $activity = [
- 'actor_name' => $user,
- 'actor_host' => $host,
- 'type' => (mb_strtolower($type) == 'like') ? 'like' : 'announce',
- 'object_id' => (int) $post_id,
- 'updated' => time()
- ];
-
- try {
- $statement = $db->prepare('INSERT OR IGNORE INTO activities (activity_actor_name, activity_actor_host, activity_type, activity_object_id, activity_updated) VALUES (:actor_name, :actor_host, :type, :object_id, :updated)');
-
- $statement->bindValue(':actor_name', $activity['actor_name'], PDO::PARAM_STR);
- $statement->bindValue(':actor_host', $activity['actor_host'], PDO::PARAM_STR);
- $statement->bindValue(':type', $activity['type'], PDO::PARAM_STR);
- $statement->bindValue(':object_id', $activity['object_id'], PDO::PARAM_INT);
- $statement->bindValue(':updated', $activity['updated'], PDO::PARAM_INT);
-
- $statement->execute();
-
- } catch(PDOException $e) {
- print 'Exception : '.$e->getMessage();
- ap_log('ERROR', $e->getMessage());
- return false;
- }
-
- ap_log('INSERTED ACTIVITY', json_encode([$activity, $db->lastInsertId()], JSON_PRETTY_PRINT));
- return $db->lastInsertId();
-}
-
-function activitypub_undo($type, $user, $host, $post_id) {
- if (empty($type)) return false;
-
- global $db;
-
- $activity = [
- 'actor_name' => $user,
- 'actor_host' => $host,
- 'type' => (mb_strtolower($type) == 'like') ? 'like' : 'announce', // todo: make this safer
- 'object_id' => (int) $post_id
- ];
- try {
- $statement = $db->prepare('DELETE FROM activities WHERE activity_actor_name = :actor_name AND activity_actor_host = :actor_host AND activity_type = :type AND activity_object_id = :object_id');
- $statement->bindValue(':actor_name', $activity['actor_name'], PDO::PARAM_STR);
- $statement->bindValue(':actor_host', $activity['actor_host'], PDO::PARAM_STR);
- $statement->bindValue(':type', $activity['type'], PDO::PARAM_STR);
- $statement->bindValue(':object_id', $activity['object_id'], PDO::PARAM_INT);
-
- $statement->execute();
- } catch(PDOException $e) {
- print 'Exception : '.$e->getMessage();
- ap_log('ERROR', $e->getMessage());
- return false;
- }
-
- ap_log('SQL DELETE', json_encode([$statement->rowCount()]));
- return true;
- return $statement->rowCount();
-}
-
-function activitypub_update_post($post_id) {
- // https://www.w3.org/TR/activitypub/#update-activity-inbox
-}
-
-function activitypub_delete_user($name, $host) {
- if(empty($name) || empty($host)) return false;
-
- global $db;
-
- // delete all records of user as follower
- try {
- $statement = $db->prepare('DELETE FROM followers WHERE follower_name = :actor_name AND follower_host = :actor_host');
- $statement->bindValue(':actor_name', $name, PDO::PARAM_STR);
- $statement->bindValue(':actor_host', $host, PDO::PARAM_STR);
-
- $statement->execute();
- } catch(PDOException $e) {
- print 'Exception : '.$e->getMessage();
- ap_log('ERROR', $e->getMessage());
- return false;
- }
-
- // remove likes and boosts
- try {
- $statement = $db->prepare('DELETE FROM activities WHERE activity_actor_name = :actor_name AND activity_actor_host = :actor_host');
- $statement->bindValue(':actor_name', $name, PDO::PARAM_STR);
- $statement->bindValue(':actor_host', $host, PDO::PARAM_STR);
-
- $statement->execute();
- } catch(PDOException $e) {
- print 'Exception : '.$e->getMessage();
- ap_log('ERROR', $e->getMessage());
- return false;
- }
-
- return true;
-}
-
-function activitypub_get_post_stats($type="like", $post_id=null) {
- global $db;
- if(empty($db)) return false;
- if(empty($post_id)) return false;
-
- // normalize type input, liberally
- if(in_array($type, ['announce', 'announced', 'boost', 'boosts', 'boosted'])) $type = 'announce';
- if($type == 'both' || $type == 'all') $type = 'both';
- if($type !== 'both' && $type !== 'announce') $type = 'like';
-
- $type_clause = 'activity_type = "like"';
- if($type == 'both') {
- $type_clause = '(activity_type = "like" OR activity_type = "announce")';
- } elseif($type == 'announce') {
- $type_clause = 'activity_type = "announce"';
- }
-
- $sql = 'SELECT activity_type, COUNT(id) AS amount FROM activities WHERE activity_object_id = :post_id AND '.$type_clause.' GROUP BY activity_type ORDER BY activity_type ASC';
-
- try {
- $statement = $db->prepare($sql);
- $statement->bindValue(':post_id', (int) $post_id, PDO::PARAM_INT);
- $statement->execute();
- $rows = $statement->fetchAll(PDO::FETCH_ASSOC);
- } catch(PDOException $e) {
- print 'Exception : '.$e->getMessage();
- return false;
- }
-
- $return = [
- 'announce' => 0,
- 'like' => 0
- ];
-
- if(!empty($rows)) {
- foreach ($rows as $row) {
- if($row['activity_type'] == 'announce') {
- $return['announce'] = (int) $row['amount'];
- } else if($row['activity_type'] == 'like') {
- $return['like'] = (int) $row['amount'];
- }
- }
- }
-
- if($type == 'both') {
- return $return;
- } elseif($type == 'announce') {
- unset($return['like']);
- return $return;
- } else {
- unset($return['announce']);
- return $return;
- }
-
- return $return;
-}
diff --git a/lib/activitypub-inbox.php b/lib/activitypub-inbox.php
deleted file mode 100644
index cea4066..0000000
--- a/lib/activitypub-inbox.php
+++ /dev/null
@@ -1,196 +0,0 @@
-<?php
-
- // https://paul.kinlan.me/adding-activity-pub-to-your-static-site/
- // https://bovine.readthedocs.io/en/latest/tutorial_server.html
- // https://github.com/timmot/activity-pub-tutorial
- // https://magazine.joomla.org/all-issues/february-2023/turning-the-joomla-website-into-an-activitypub-server
- // https://codeberg.org/mro/activitypub/src/commit/4b1319d5363f4a836f23c784ef780b81bc674013/like.sh#L101
-
- // todo: handle account moves
- // https://seb.jambor.dev/posts/understanding-activitypub/
-
- if(!$config['activitypub']) exit('ActivityPub is disabled via config file.');
-
- $postdata = file_get_contents('php://input');
-
- if(!empty($postdata)) {
-
- $data = json_decode($postdata, true);
- $inbox = parse_url($config['url'].'/inbox');
-
- $request = [
- 'host' => $inbox['host'],
- 'path' => $inbox['path'],
- 'digest' => $_SERVER['HTTP_DIGEST'],
- 'date' => $_SERVER['HTTP_DATE'],
- 'length' => $_SERVER['CONTENT_LENGTH'],
- 'type' => $_SERVER['CONTENT_TYPE']
- ];
-
- header('Content-Type: application/ld+json');
-
- ap_log('POSTDATA', $postdata);
- // ap_log('REQUEST', json_encode($request));
-
- // verify message digest
- $digest_verify = activitypub_digest($postdata);
- if($digest_verify === $request['digest']) {
- // ap_log('DIGEST', 'Passed verification for ' . $digest_verify);
- } else {
- ap_log('ERROR', json_encode(['digest verification failed!', $request['digest'], $digest_verify], JSON_PRETTY_PRINT));
- }
-
- // GET ACTOR DETAILS
- if(!empty($data) && !empty($data['actor'])) {
- $actor = activitypub_get_actor_data($data['actor']);
-
- if(!empty($actor)) {
- $actor_key = $actor['publicKey'];
- $info = parse_url($actor['inbox']);
- } else {
- exit('could not parse actor data');
- }
- } else {
- exit('no actor provided');
- }
-
- $signature = [];
- $signature_string = $_SERVER['HTTP_SIGNATURE'];
- $parts = explode(',', stripslashes($signature_string));
- foreach ($parts as $part) {
- $part = trim($part, '"');
- list($k, $v) = explode('=', $part);
- $signature[$k] = trim($v, '"');
- }
-
- // ap_log('SIGNATURE', json_encode($signature));
- // ap_log('ACTOR', json_encode($actor));
- // ap_log('PUBLIC KEY', str_replace("\n", '\n', $actor_key['publicKeyPem']));
-
- $plaintext = activitypub_plaintext($request['path'], $request['host'], $request['date'], $request['digest'], $request['type']);
-
- // verify request signature
- $result = activitypub_verify($signature['signature'], $actor_key['publicKeyPem'], $plaintext);
-
- if($result != 1) {
- ap_log('REQUEST', json_encode($request));
- ap_log('SIGNATURE', json_encode($signature));
- ap_log('PUBLIC KEY', str_replace("\n", '\n', $actor_key['publicKeyPem']));
- ap_log('RESULT', json_encode([$result, $plaintext], JSON_PRETTY_PRINT));
- ap_log('SSL ERROR', 'message signature did not match');
- exit('message signature did not match');
- } else {
- ap_log('SSL OKAY', json_encode([$request, $signature, $result, $plaintext, $actor_key['publicKeyPem']], JSON_PRETTY_PRINT));
- }
-
- // message signature was ok, now handle the request
-
- if(!empty($data['type'])) {
- if(mb_strtolower($data['type']) == 'follow') {
- // follow
-
- $accept_data = [
- '@context' => 'https://www.w3.org/ns/activitystreams',
- 'id' => sprintf('%s/activity/%s', $config['url'], uniqid()),
- 'type' => 'Accept',
- 'actor' => sprintf('%s/actor', $config['url']),
- 'object' => $data
- ];
-
- // send back Accept activity
- activitypub_send_request($info['host'], $info['path'], $accept_data);
-
- $now = time();
- $follower = [
- 'name' => $actor['preferredUsername'],
- 'host' => $info['host'],
- 'actor' => $data['actor'],
- 'inbox' => $actor['inbox'],
- 'added' => time()
- ];
- try {
- $statement = $db->prepare('INSERT OR IGNORE INTO followers (follower_name, follower_host, follower_actor, follower_inbox, follower_shared_inbox, follower_added) VALUES (:follower_name, :follower_host, :follower_actor, :follower_inbox, :follower_shared_inbox, :follower_added)');
-
- $statement->bindValue(':follower_name', $follower['name'], PDO::PARAM_STR);
- $statement->bindValue(':follower_host', $follower['host'], PDO::PARAM_STR);
- $statement->bindValue(':follower_actor', $follower['actor'], PDO::PARAM_STR);
- $statement->bindValue(':follower_inbox', $follower['inbox'], PDO::PARAM_STR);
- $statement->bindValue(':follower_added', $follower['added'], PDO::PARAM_INT);
-
- // store shared inbox if possible
- if(!empty($actor['endpoints']) && !empty($actor['endpoints']['sharedInbox'])) {
- $statement->bindValue(':follower_shared_inbox', $actor['endpoints']['sharedInbox'], PDO::PARAM_STR);
- } else {
- $statement->bindValue(':follower_shared_inbox', null, PDO::PARAM_NULL);
- }
-
- $statement->execute();
-
- } catch(PDOException $e) {
- print 'Exception : '.$e->getMessage();
- ap_log('ERROR FOLLOWING', $e->getMessage());
- }
-
- ap_log('FOLLOW', json_encode([$actor['inbox'], $info, $accept_data], JSON_PRETTY_PRINT));
-
- } elseif(mb_strtolower($data['type']) == 'like') {
- // like/favorite
- ap_log('LIKE', json_encode([$actor['inbox'], $info, $data], JSON_PRETTY_PRINT));
- $post_id = activitypub_post_from_url($data['object']);
- activitypub_do('like', $actor['preferredUsername'], $info['host'], $post_id);
- } elseif(mb_strtolower($data['type']) == 'announce') {
- // boost
- ap_log('ANNOUNCE/BOOST', json_encode([$actor['inbox'], $info, $data], JSON_PRETTY_PRINT));
- $post_id = activitypub_post_from_url($data['object']);
- activitypub_do('announce', $actor['preferredUsername'], $info['host'], $post_id);
- } elseif(mb_strtolower($data['type']) == 'undo') {
- if(mb_strtolower($data['object']['type']) == 'follow') {
- // undo follow
-
- ap_log('UNDO FOLLOW', json_encode([$plaintext]));
-
- // remove from db
- $follower = [
- 'name' => $actor['preferredUsername'],
- 'host' => $info['host']
- ];
-
- try {
- $statement = $db->prepare('DELETE FROM followers WHERE follower_name = :name AND follower_host = :host');
- $statement->bindValue(':name', $follower['name'], PDO::PARAM_STR);
- $statement->bindValue(':host', $follower['host'], PDO::PARAM_STR);
-
- $statement->execute();
- } catch(PDOException $e) {
- print 'Exception : '.$e->getMessage();
- ap_log('ERROR UNFOLLOWING', $e->getMessage());
- }
-
- } elseif(mb_strtolower($data['object']['type']) == 'like') {
- // undo like
- $post_id = activitypub_post_from_url($data['object']['object']);
- activitypub_undo('like', $actor['preferredUsername'], $info['host'], $post_id);
- ap_log('UNDO LIKE', json_encode([$actor['inbox'], $info, $data], JSON_PRETTY_PRINT));
- } elseif(mb_strtolower($data['object']['type']) == 'announce') {
- // undo boost
- $post_id = activitypub_post_from_url($data['object']['object']);
- activitypub_undo('announce', $actor['preferredUsername'], $info['host'], $post_id);
- ap_log('UNDO ANNOUNCE/BOOST', json_encode([$actor['inbox'], $info, $data], JSON_PRETTY_PRINT));
- }
- } elseif(mb_strtolower($data['type']) == 'delete') {
- // user is to be deleted and all references removed or replaced by Tombstone
- // https://www.w3.org/TR/activitypub/#delete-activity-inbox
- ap_log('DELETE 1', json_encode(['trying to delete', $data]));
- activitypub_delete_user($actor['preferredUsername'], $info['host']);
- ap_log('DELETE 2', json_encode([$actor['preferredUsername'], $info['host']]));
- }
- }
-
- } else {
-
- if(file_exists(ROOT.DS.'inbox-log.txt')) {
- echo(nl2br(file_get_contents(ROOT.DS.'inbox-log.txt')));
- } else {
- echo('no inbox activity');
- }
- }
diff --git a/lib/activitypub-outbox.php b/lib/activitypub-outbox.php
deleted file mode 100644
index 242c695..0000000
--- a/lib/activitypub-outbox.php
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-if(!$config['activitypub']) exit('ActivityPub is disabled via config file.');
-
-$posts_per_page = 20; // 20 is mastodon default?
-$posts_total = db_posts_count(); // get total amount of posts
-$total_pages = ceil($posts_total / $posts_per_page);
-
-if(!isset($_GET['page'])):
-
- $output = [
- '@context' => 'https://www.w3.org/ns/activitystreams',
- 'id' => $config['url'].'/outbox',
- 'type' => 'OrderedCollection',
- 'totalItems' => $posts_total,
- 'first' => $config['url'].'/outbox?page=1',
- 'last' => $config['url'].'/outbox?page='.$total_pages
- ];
-
- header('Content-Type: application/ld+json');
- echo(json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
-else:
-
- // pagination
- $current_page = (isset($_GET['page']) && is_numeric($_GET['page'])) ? (int) $_GET['page'] : 1;
- $offset = ($current_page-1)*$posts_per_page;
-
- if($current_page < 1 || $current_page > $total_pages) {
- http_response_code(404);
- header('Content-Type: application/ld+json');
- die('{}');
- }
-
- $posts = db_select_posts(NOW, $posts_per_page, 'desc', $offset);
-
- $ordered_items = [];
- if(!empty($posts)) {
- foreach ($posts as $post) {
-
- $item = [];
- $item['id'] = $config['url'].'/'.$post['id'].'/json';
- $item['type'] = 'Create';
- $item['actor'] = $config['url'].'/actor';
- $item['published'] = gmdate('Y-m-d\TH:i:s\Z', $post['post_timestamp']);
- $item['to'] = ['https://www.w3.org/ns/activitystreams#Public'];
- $item['cc'] = [$config['url'].'/followers'];
- $item['object'] = $config['url'].'/'.$post['id'].'/';
-
- $ordered_items[] = $item;
- }
- }
-
- $output = [
- '@context' => 'https://www.w3.org/ns/activitystreams',
- 'id' => $config['url'].'/outbox?page='.$current_page,
- 'type' => 'OrderedCollectionPage',
- 'partOf' => $config['url'].'/outbox'
- ];
-
- if($current_page > 1) {
- $output['prev'] = $config['url'].'/outbox?page='.($current_page-1);
- }
-
- if($current_page < $total_pages) {
- $output['next'] = $config['url'].'/outbox?page='.($current_page+1);
- }
-
- $output['orderedItems'] = $ordered_items;
-
- header('Content-Type: application/ld+json');
- echo(json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
-endif;
diff --git a/lib/activitypub-webfinger.php b/lib/activitypub-webfinger.php
deleted file mode 100644
index 15177cb..0000000
--- a/lib/activitypub-webfinger.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
- if(!$config['activitypub']) exit();
-
- // todo: handle empty usernames
-
- $actor = ltrim($config['microblog_account'], '@');
- $url = parse_url($config['url']);
- $domain = $url['host'];
- if(!empty($url['path'])) $domain .= rtrim($url['path'], '/');
-
- $data = [
- 'subject' => 'acct:'.$actor.'@'.$domain,
- 'links' => [
- [
- 'rel' => 'self',
- 'type' => 'application/activity+json',
- 'href' => $config['url'].'/actor'
- ]
- ]
- ];
-
- header('Content-Type: application/jrd+json');
- echo(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
diff --git a/lib/database.php b/lib/database.php
index e64a80c..5d0fa5c 100644
--- a/lib/database.php
+++ b/lib/database.php
@@ -2,7 +2,7 @@
//connect or create the database
try {
- $db = new PDO('sqlite:'.ROOT.DS.'posts.db');
+ $db = new PDO($config['database_path']);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
diff --git a/lib/functions.php b/lib/functions.php
index 6fe13d8..608d473 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -12,7 +12,7 @@ function check_login() {
if(isset($_COOKIE['microblog_login'])) {
if($_COOKIE['microblog_login'] === sha1($config['url'].$config['admin_pass'])) {
// correct auth data, extend cookie life
- $domain = ($_SERVER['HTTP_HOST'] != 'localhost') ? $_SERVER['HTTP_HOST'] : false;
+ $domain = ($_SERVER['SERVER_NAME'] != 'localhost') ? $_SERVER['HTTP_HOST'] : false;
setcookie('microblog_login', sha1($config['url'].$config['admin_pass']), NOW+$config['cookie_life'], '/', $domain, false);
return true;
@@ -607,25 +607,3 @@ function uuidv4($data = null) { // https://stackoverflow.com/a/15875555/3625228
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
-
-function twitter_post_status($status='') {
- global $config;
- require_once(ROOT.DS.'lib'.DS.'twitter_api.php');
-
- if(empty($status)) return array('errors' => 1);
- if(empty($config['twitter']['oauth_access_token']) ||
- empty($config['twitter']['oauth_access_token_secret']) ||
- empty($config['twitter']['consumer_key']) ||
- empty($config['twitter']['consumer_secret'])) return array('errors' => 2);
-
- $url = 'https://api.twitter.com/1.1/statuses/update.json';
- $postfields = array(
- 'status' => $status,
- 'trim_user' => 1
- );
-
- $twitter = new TwitterAPIExchange($config['twitter']);
- return $twitter->buildOauth($url, 'POST')->setPostfields($postfields)->performRequest();
-}
-
-require_once(__DIR__.DS.'activitypub-functions.php');
diff --git a/lib/twitter_api.php b/lib/twitter_api.php
deleted file mode 100644
index d6e3a58..0000000
--- a/lib/twitter_api.php
+++ /dev/null
@@ -1,410 +0,0 @@
-<?php
-
-/**
- * Twitter-API-PHP : Simple PHP wrapper for the v1.1 API
- *
- * PHP version 5.3.10
- *
- * @category Awesomeness
- * @package Twitter-API-PHP
- * @author James Mallison <me@j7mbo.co.uk>
- * @license MIT License
- * @version 1.0.4
- * @link http://github.com/j7mbo/twitter-api-php
- */
-class TwitterAPIExchange
-{
- /**
- * @var string
- */
- private $oauth_access_token;
-
- /**
- * @var string
- */
- private $oauth_access_token_secret;
-
- /**
- * @var string
- */
- private $consumer_key;
-
- /**
- * @var string
- */
- private $consumer_secret;
-
- /**
- * @var array
- */
- private $postfields;
-
- /**
- * @var string
- */
- private $getfield;
-
- /**
- * @var mixed
- */
- protected $oauth;
-
- /**
- * @var string
- */
- public $url;
-
- /**
- * @var string
- */
- public $requestMethod;
-
- /**
- * The HTTP status code from the previous request
- *
- * @var int
- */
- protected $httpStatusCode;
-
- /**
- * Create the API access object. Requires an array of settings::
- * oauth access token, oauth access token secret, consumer key, consumer secret
- * These are all available by creating your own application on dev.twitter.com
- * Requires the cURL library
- *
- * @throws \RuntimeException When cURL isn't loaded
- * @throws \InvalidArgumentException When incomplete settings parameters are provided
- *
- * @param array $settings
- */
- public function __construct(array $settings)
- {
- if (!function_exists('curl_init'))
- {
- throw new RuntimeException('TwitterAPIExchange requires cURL extension to be loaded, see: http://curl.haxx.se/docs/install.html');
- }
-
- if (!isset($settings['oauth_access_token'])
- || !isset($settings['oauth_access_token_secret'])
- || !isset($settings['consumer_key'])
- || !isset($settings['consumer_secret']))
- {
- throw new InvalidArgumentException('Incomplete settings passed to TwitterAPIExchange');
- }
-
- $this->oauth_access_token = $settings['oauth_access_token'];
- $this->oauth_access_token_secret = $settings['oauth_access_token_secret'];
- $this->consumer_key = $settings['consumer_key'];
- $this->consumer_secret = $settings['consumer_secret'];
- }
-
- /**
- * Set postfields array, example: array('screen_name' => 'J7mbo')
- *
- * @param array $array Array of parameters to send to API
- *
- * @throws \Exception When you are trying to set both get and post fields
- *
- * @return TwitterAPIExchange Instance of self for method chaining
- */
- public function setPostfields(array $array)
- {
- if (!is_null($this->getGetfield()))
- {
- throw new Exception('You can only choose get OR post fields (post fields include put).');
- }
-
- if (isset($array['status']) && substr($array['status'], 0, 1) === '@')
- {
- $array['status'] = sprintf("\0%s", $array['status']);
- }
-
- foreach ($array as $key => &$value)
- {
- if (is_bool($value))
- {
- $value = ($value === true) ? 'true' : 'false';
- }
- }
-
- $this->postfields = $array;
-
- // rebuild oAuth
- if (isset($this->oauth['oauth_signature']))
- {
- $this->buildOauth($this->url, $this->requestMethod);
- }
-
- return $this;
- }
-
- /**
- * Set getfield string, example: '?screen_name=J7mbo'
- *
- * @param string $string Get key and value pairs as string
- *
- * @throws \Exception
- *
- * @return \TwitterAPIExchange Instance of self for method chaining
- */
- public function setGetfield($string)
- {
- if (!is_null($this->getPostfields()))
- {
- throw new Exception('You can only choose get OR post / post fields.');
- }
-
- $getfields = preg_replace('/^\?/', '', explode('&', $string));
- $params = array();
-
- foreach ($getfields as $field)
- {
- if ($field !== '')
- {
- list($key, $value) = explode('=', $field);
- $params[$key] = $value;
- }
- }
-
- $this->getfield = '?' . http_build_query($params, '', '&');
-
- return $this;
- }
-
- /**
- * Get getfield string (simple getter)
- *
- * @return string $this->getfields
- */
- public function getGetfield()
- {
- return $this->getfield;
- }
-
- /**
- * Get postfields array (simple getter)
- *
- * @return array $this->postfields
- */
- public function getPostfields()
- {
- return $this->postfields;
- }
-
- /**
- * Build the Oauth object using params set in construct and additionals
- * passed to this method. For v1.1, see: https://dev.twitter.com/docs/api/1.1
- *
- * @param string $url The API url to use. Example: https://api.twitter.com/1.1/search/tweets.json
- * @param string $requestMethod Either POST or GET
- *
- * @throws \Exception
- *
- * @return \TwitterAPIExchange Instance of self for method chaining
- */
- public function buildOauth($url, $requestMethod)
- {
- if (!in_array(strtolower($requestMethod), array('post', 'get', 'put', 'delete')))
- {
- throw new Exception('Request method must be either POST, GET or PUT or DELETE');
- }
-
- $consumer_key = $this->consumer_key;
- $consumer_secret = $this->consumer_secret;
- $oauth_access_token = $this->oauth_access_token;
- $oauth_access_token_secret = $this->oauth_access_token_secret;
-
- $oauth = array(
- 'oauth_consumer_key' => $consumer_key,
- 'oauth_nonce' => time(),
- 'oauth_signature_method' => 'HMAC-SHA1',
- 'oauth_token' => $oauth_access_token,
- 'oauth_timestamp' => time(),
- 'oauth_version' => '1.0'
- );
-
- $getfield = $this->getGetfield();
-
- if (!is_null($getfield))
- {
- $getfields = str_replace('?', '', explode('&', $getfield));
-
- foreach ($getfields as $g)
- {
- $split = explode('=', $g);
-
- /** In case a null is passed through **/
- if (isset($split[1]))
- {
- $oauth[$split[0]] = urldecode($split[1]);
- }
- }
- }
-
- $postfields = $this->getPostfields();
-
- if (!is_null($postfields)) {
- foreach ($postfields as $key => $value) {
- $oauth[$key] = $value;
- }
- }
-
- $base_info = $this->buildBaseString($url, $requestMethod, $oauth);
- $composite_key = rawurlencode($consumer_secret) . '&' . rawurlencode($oauth_access_token_secret);
- $oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
- $oauth['oauth_signature'] = $oauth_signature;
-
- $this->url = $url;
- $this->requestMethod = $requestMethod;
- $this->oauth = $oauth;
-
- return $this;
- }
-
- /**
- * Perform the actual data retrieval from the API
- *
- * @param boolean $return If true, returns data. This is left in for backward compatibility reasons
- * @param array $curlOptions Additional Curl options for this request
- *
- * @throws \Exception
- *
- * @return string json If $return param is true, returns json data.
- */
- public function performRequest($return = true, $curlOptions = array())
- {
- if (!is_bool($return))
- {
- throw new Exception('performRequest parameter must be true or false');
- }
-
- $header = array($this->buildAuthorizationHeader($this->oauth), 'Expect:');
-
- $getfield = $this->getGetfield();
- $postfields = $this->getPostfields();
-
- if (in_array(strtolower($this->requestMethod), array('put', 'delete')))
- {
- $curlOptions[CURLOPT_CUSTOMREQUEST] = $this->requestMethod;
- }
-
- $options = $curlOptions + array(
- CURLOPT_HTTPHEADER => $header,
- CURLOPT_HEADER => false,
- CURLOPT_URL => $this->url,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_TIMEOUT => 10,
- );
-
- if (!is_null($postfields))
- {
- $options[CURLOPT_POSTFIELDS] = http_build_query($postfields, '', '&');
- }
- else
- {
- if ($getfield !== '')
- {
- $options[CURLOPT_URL] .= $getfield;
- }
- }
-
- $feed = curl_init();
- curl_setopt_array($feed, $options);
- $json = curl_exec($feed);
-
- $this->httpStatusCode = curl_getinfo($feed, CURLINFO_HTTP_CODE);
-
- if (($error = curl_error($feed)) !== '')
- {
- curl_close($feed);
-
- throw new \Exception($error);
- }
-
- curl_close($feed);
-
- return $json;
- }
-
- /**
- * Private method to generate the base string used by cURL
- *
- * @param string $baseURI
- * @param string $method
- * @param array $params
- *
- * @return string Built base string
- */
- private function buildBaseString($baseURI, $method, $params)
- {
- $return = array();
- ksort($params);
-
- foreach($params as $key => $value)
- {
- $return[] = rawurlencode($key) . '=' . rawurlencode($value);
- }
-
- return $method . "&" . rawurlencode($baseURI) . '&' . rawurlencode(implode('&', $return));
- }
-
- /**
- * Private method to generate authorization header used by cURL
- *
- * @param array $oauth Array of oauth data generated by buildOauth()
- *
- * @return string $return Header used by cURL for request
- */
- private function buildAuthorizationHeader(array $oauth)
- {
- $return = 'Authorization: OAuth ';
- $values = array();
-
- foreach($oauth as $key => $value)
- {
- if (in_array($key, array('oauth_consumer_key', 'oauth_nonce', 'oauth_signature',
- 'oauth_signature_method', 'oauth_timestamp', 'oauth_token', 'oauth_version'))) {
- $values[] = "$key=\"" . rawurlencode($value) . "\"";
- }
- }
-
- $return .= implode(', ', $values);
- return $return;
- }
-
- /**
- * Helper method to perform our request
- *
- * @param string $url
- * @param string $method
- * @param string $data
- * @param array $curlOptions
- *
- * @throws \Exception
- *
- * @return string The json response from the server
- */
- public function request($url, $method = 'get', $data = null, $curlOptions = array())
- {
- if (strtolower($method) === 'get')
- {
- $this->setGetfield($data);
- }
- else
- {
- $this->setPostfields($data);
- }
-
- return $this->buildOauth($url, $method)->performRequest(true, $curlOptions);
- }
-
- /**
- * Get the HTTP status code for the previous request
- *
- * @return integer
- */
- public function getHttpStatusCode()
- {
- return $this->httpStatusCode;
- }
-}