Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3767087
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
40 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/app/Analyzers/DockerHub/BaseEvent.php b/app/Analyzers/DockerHub/BaseEvent.php
index 95ae983..106d021 100644
--- a/app/Analyzers/DockerHub/BaseEvent.php
+++ b/app/Analyzers/DockerHub/BaseEvent.php
@@ -1,48 +1,48 @@
<?php
namespace Nasqueron\Notifications\Analyzers\DockerHub;
abstract class BaseEvent {
/**
* @var \stdClass
*/
protected $payload;
/**
* Initializes a new instance of the BaseEvent object.
*
* @param \stdClass $payload The payload to analyze
*/
- public function __construct ($payload) {
+ public function __construct (\stdClass $payload) {
$this->payload = $payload;
}
///
/// Public methods
///
/**
* Gets notification payload.
*
* This method allows analyzer to edit the payload.
*/
public function getPayload () {
return $this->payload;
}
/**
* Gets notification text for this event.
*
* @return string
*/
abstract public function getText();
/**
* Gets notification link related to this event.
*
* @return string
*/
abstract public function getLink();
}
diff --git a/app/Analyzers/DockerHub/BuildFailureEvent.php b/app/Analyzers/DockerHub/BuildFailureEvent.php
index acd5c60..35ec1c8 100644
--- a/app/Analyzers/DockerHub/BuildFailureEvent.php
+++ b/app/Analyzers/DockerHub/BuildFailureEvent.php
@@ -1,67 +1,67 @@
<?php
namespace Nasqueron\Notifications\Analyzers\DockerHub;
use Nasqueron\Notifications\Facades\Mailgun;
class BuildFailureEvent extends BaseEvent {
/**
* Initializes a new instance of the BuildFailureEvent object.
*
* @param \stdClass $payload The payload to analyze
*/
- public function __construct ($payload) {
+ public function __construct (\stdClass $payload) {
parent::__construct($payload);
$this->payload = $this->getMailGunPayload();
}
/**
* Gets a MailGun message.
*
* @return \stdClass
*/
private function getMailGunPayload () {
return Mailgun::fetchMessageFromPayload($this->payload);
}
/**
* @return string
*/
private function getMailBody () {
$bodyProperty = 'body-plain';
return $this->payload->$bodyProperty;
}
/**
* Extracts a regular expression from the mail body.
*
* @param $string Regular expression
* @return string
*/
private function extractFromBody ($regex) {
preg_match($regex, $this->getMailBody(), $matches);
return $matches[1];
}
/**
* Gets text from payload.
*
* @return string
*/
public function getText() {
$repo = $this->extractFromBody("@\"(.*?\/.*?)\"@");
return "Image build by Docker Hub registry failure for $repo";
}
/**
* Gets link from payload.
*
* @return string
*/
public function getLink() {
return $this->extractFromBody("@(https\:\/\/hub.docker.com\/r.*)@");
}
}
diff --git a/app/Analyzers/GitHub/Events/RepositoryEvent.php b/app/Analyzers/GitHub/Events/RepositoryEvent.php
index 1e51521..ffa9335 100644
--- a/app/Analyzers/GitHub/Events/RepositoryEvent.php
+++ b/app/Analyzers/GitHub/Events/RepositoryEvent.php
@@ -1,66 +1,66 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* RepositoryEvent payload analyzer
*
* @link https://developer.github.com/v3/activity/events/types/#repositoryevent
*/
class RepositoryEvent extends Event {
/**
* Determines if the action is valid.
*
* @param string $action The action to check
* @return bool true if the action is valid; otherwise, false
*/
- protected static function isValidAction ($action) {
+ protected static function isValidAction (string $action) {
$actions = ['created', 'deleted', 'publicized', 'privatized'];
return in_array($action, $actions);
}
/**
* Gets description for the payload
*
* @return string
*/
public function getDescription () : string {
$action = $this->payload->action;
if (!static::isValidAction($action)) {
return trans(
'GitHub.EventsDescriptions.RepositoryEventUnknown',
['action' => $action]
);
}
$key = 'GitHub.EventsDescriptions.RepositoryEventPerAction.';
$key .= $action;
$repository = $this->payload->repository->full_name;
$message = trans($key, ['repository' => $repository]);
if ($this->payload->repository->fork) {
$message .= trans('GitHub.EventsDescriptions.RepositoryEventFork');
}
$description = (string)$this->payload->repository->description;
if ($description !== "") {
$message .= trans('GitHub.Separator');
$message .= $description;
}
return $message;
}
/**
* Gets link for the payload
*
* @return string
*/
public function getLink () : string {
return $this->payload->repository->html_url;
}
}
diff --git a/app/Analyzers/GitHub/Events/WithCommit.php b/app/Analyzers/GitHub/Events/WithCommit.php
index 8d8463c..87fc4aa 100644
--- a/app/Analyzers/GitHub/Events/WithCommit.php
+++ b/app/Analyzers/GitHub/Events/WithCommit.php
@@ -1,64 +1,63 @@
<?php
namespace Nasqueron\Notifications\Analyzers\GitHub\Events;
/**
* Helper methods for events with a need to specify commit information
* (e.g. push)
*
* @link https://developer.github.com/v3/activity/events/types/#pushevent
*/
trait WithCommit {
/**
* Gets the title of the head commit
- *
* @return string
*/
private function getHeadCommitTitle () {
return static::getCommitTitle($this->payload->head_commit->message);
}
/**
* Extracts the commit title from the whole commit message.
*
* @param string $message The commit message
* @return string The commit title
*/
- public static function getCommitTitle ($message) {
+ public static function getCommitTitle (string $message) {
// Discards extra lines
$pos = strpos($message, "\n");
if ($pos > 0) {
$message = substr($message, 0, $pos);
}
// Short messages are returned as is
// Longer messages are truncated
return self::cut($message, 72);
}
/**
* Gets the description text for the head commit.
*
* @return string
*/
private function getHeadCommitDescription () {
$commit = $this->payload->head_commit;
$committer = $commit->committer->username;
$author = $commit->author->username;
$message = trans('GitHub.Commits.Message', [
'committer' => $committer,
'title' => $this->getHeadCommitTitle(),
]);
if ($committer !== $author) {
$message .= trans('GitHub.Commits.Authored', [
'author' => $author,
]);
}
return $message;
}
}
diff --git a/app/Config/Services/Services.php b/app/Config/Services/Services.php
index 1e06852..22b5928 100644
--- a/app/Config/Services/Services.php
+++ b/app/Config/Services/Services.php
@@ -1,107 +1,107 @@
<?php
namespace Nasqueron\Notifications\Config\Services;
use Illuminate\Support\Facades\Storage;
class Services {
///
/// Properties
///
/**
* @var Service[]
*/
public $services = [];
///
/// Constructors
///
/**
* @param string $file The JSON file to deserialize
* @return Services The deserialized instance
*/
public static function loadFromJson (string $file) : Services {
$data = json_decode(Storage::get($file));
$mapper = new \JsonMapper();
return $mapper->map($data, new self());
}
///
/// Methods to get a list of services
///
/**
* Gets the services found in credentials.json configuration file.
*
* @return Service[]
*/
public function get () {
return $this->services;
}
/**
* Gets all the services for a specific gate.
*
* @param string $gate The gate (e.g. GitHub)
* @return Service[]
*/
public function getForGate (string $gate) : array {
$services = [];
foreach ($this->services as $service) {
if ($service->gate === $gate) {
$services[] = $service;
}
}
return $services;
}
///
/// Methods to find a service matching criteria
///
/**
* Gets the service for a specific gate and door
*
* @param string $gate The gate (e.g. GitHub)
* @param string $door The door (e.g. Nasqueron)
* @return Service|null The service information is found; otherwise, null.
*/
public function findServiceByDoor (string $gate, string $door) : ?Service {
foreach ($this->services as $service) {
if ($service->gate === $gate && $service->door === $door) {
return $service;
}
}
return null;
}
/**
* Finds a service for a specific gate, property and value
*
* @param string $gate The gate (e.g. Phabricator)
* @param string $property The property to check (e.g. instance)
* @param mixed $value The property value to find
* (e.g. 'http://devcentral.nasqueron.org')
* @return Service|null The service information is found; otherwise, null.
*/
public function findServiceByProperty (
string $gate,
string $property,
- $value
+ mixed $value
) : ?Service {
foreach ($this->services as $service) {
if ($service->gate === $gate && $service->$property === $value) {
return $service;
}
}
return null;
}
}
diff --git a/app/Events/DockerHubPayloadEvent.php b/app/Events/DockerHubPayloadEvent.php
index e5a2581..4372c91 100644
--- a/app/Events/DockerHubPayloadEvent.php
+++ b/app/Events/DockerHubPayloadEvent.php
@@ -1,52 +1,52 @@
<?php
namespace Nasqueron\Notifications\Events;
use Illuminate\Queue\SerializesModels;
class DockerHubPayloadEvent extends Event {
use SerializesModels;
/**
* The gate door which receives the request
* @var string
*/
public $door;
/**
* The event triggering this request
* @var string
*/
public $event;
/**
* The request content, as a structured data
* @var \stdClass
*/
public $payload;
/**
* Gets event according the kind of payload we receive.
*
* @return string
*/
public function getEvent () : string {
if (isset($this->payload->repository->repo_url)) {
return "push";
}
return "buildFailure";
}
/**
* Creates a new event instance.
*
- * @param string $door
+ * @param string $door
* @param \stdClass $payload
*/
- public function __construct($door, $payload) {
+ public function __construct(string $door, \stdClass $payload) {
$this->door = $door;
$this->payload = $payload;
$this->event = $this->getEvent();
}
}
diff --git a/app/Events/GitHubPayloadEvent.php b/app/Events/GitHubPayloadEvent.php
index f45faac..ea47b75 100644
--- a/app/Events/GitHubPayloadEvent.php
+++ b/app/Events/GitHubPayloadEvent.php
@@ -1,40 +1,40 @@
<?php
namespace Nasqueron\Notifications\Events;
use Illuminate\Queue\SerializesModels;
class GitHubPayloadEvent extends Event {
use SerializesModels;
/**
* The gate door which receives the request
* @var string
*/
public $door;
/**
* The GitHub event triggering this request
* @var string
*/
public $event;
/**
* The request content, as a structured data
* @var \stdClass
*/
public $payload;
/**
* Creates a new event instance.
*
- * @param string $door
- * @param string $event
+ * @param string $door
+ * @param string $event
* @param \stdClass $payload
*/
- public function __construct($door, $event, $payload) {
+ public function __construct(string $door, string $event, \stdClass $payload) {
$this->door = $door;
$this->event = $event;
$this->payload = $payload;
}
}
diff --git a/app/Events/JenkinsPayloadEvent.php b/app/Events/JenkinsPayloadEvent.php
index 01cae98..14f4070 100644
--- a/app/Events/JenkinsPayloadEvent.php
+++ b/app/Events/JenkinsPayloadEvent.php
@@ -1,32 +1,32 @@
<?php
namespace Nasqueron\Notifications\Events;
use Illuminate\Queue\SerializesModels;
class JenkinsPayloadEvent extends Event {
use SerializesModels;
/**
* The gate door which receives the request
* @var string
*/
public $door;
/**
* The request content, as a structured data
* @var \stdClass
*/
public $payload;
/**
* Creates a new event instance.
*
- * @param string $door
+ * @param string $door
* @param \stdClass $payload
*/
- public function __construct($door, $payload) {
+ public function __construct(string $door, \stdClass $payload) {
$this->door = $door;
$this->payload = $payload;
}
}
diff --git a/app/Listeners/LastPayloadSaver.php b/app/Listeners/LastPayloadSaver.php
index 6ec4da1..5a2bffe 100644
--- a/app/Listeners/LastPayloadSaver.php
+++ b/app/Listeners/LastPayloadSaver.php
@@ -1,29 +1,29 @@
<?php
namespace Nasqueron\Notifications\Listeners;
use Nasqueron\Notifications\Events\Event;
class LastPayloadSaver {
///
/// Events handling
///
/**
* Handles payload events
*/
public function handle (Event $event) : void {
self::savePayload($event->payload);
}
/**
* Saves payload to log file
*
* @param mixed $payload The payload to save
*/
- public static function savePayload ($payload) : void {
+ public static function savePayload (mixed $payload) : void {
$filename = storage_path('logs/payload.json');
$content = json_encode($payload);
file_put_contents($filename, $content);
}
}
diff --git a/app/Notifications/JenkinsNotification.php b/app/Notifications/JenkinsNotification.php
index 1ce9d9e..aa01357 100644
--- a/app/Notifications/JenkinsNotification.php
+++ b/app/Notifications/JenkinsNotification.php
@@ -1,112 +1,112 @@
<?php
namespace Nasqueron\Notifications\Notifications;
use Nasqueron\Notifications\Analyzers\Jenkins\JenkinsPayloadAnalyzer;
/**
* A Jenkins notification.
*
* This handles the JSON payloads sent by the following plugin:
* https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin
*/
class JenkinsNotification extends Notification {
/**
* @var \Nasqueron\Notifications\Analyzers\Jenkins\JenkinsPayloadAnalyzer
*/
private $analyzer = null;
/**
* Initializes a new instance of the JenkinsNotification class.
*
* @param string $project The project this message is for
- * @param mixed $payload The message fired by Jenkins notification plugin
+ * @param mixed $payload The message fired by Jenkins notification plugin
*/
- public function __construct ($project, $payload) {
+ public function __construct (string $project, mixed $payload) {
// Straightforward properties
$this->service = "Jenkins";
$this->project = $project;
$this->rawContent = $payload;
// Properties from the payload
$this->group = $this->getGroup();
$this->text = $this->getText();
$this->link = $payload->build->full_url;
$this->type = $this->getType();
}
/**
* Gets the notification type.
*
* @return string
*/
public function getType () : string {
$build = $this->rawContent->build;
$type = strtolower($build->phase);
if (property_exists($build, 'status')) {
$type .= '.';
$type .= $build->status;
}
return strtolower($type);
}
/**
* Gets the notification text. Intended to convey a short message
* (thing Twitter or IRC).
*
* @return string
*/
public function getText () : string {
$name = $this->rawContent->name;
$build = $this->rawContent->build;
$phase = strtolower($build->phase);
$text = "Jenkins job $name has been $phase";
if (property_exists($build, 'status')) {
$status = strtolower($build->status);
$text .= ": $status";
}
return $text;
}
/**
* Gets analyzer
*
* @return \Nasqueron\Notifications\Analyzers\Jenkins\JenkinsPayloadAnalyzer
*/
private function getAnalyzer () : JenkinsPayloadAnalyzer {
if ($this->analyzer === null) {
$this->analyzer = new JenkinsPayloadAnalyzer(
$this->project,
$this->rawContent
);
}
return $this->analyzer;
}
/**
* Gets the notification group.
*
* @return string
*/
public function getGroup () : string {
return $this->getAnalyzer()->getGroup();
}
/**
* Indicates if we should handle this payload to trigger a notification.
*
* @return bool if false, this payload is to be ignored for notifications
*/
public function shouldNotify () : bool {
return $this->getAnalyzer()->shouldNotify();
}
}
diff --git a/app/Phabricator/PhabricatorAPI.php b/app/Phabricator/PhabricatorAPI.php
index 49270ed..139352b 100644
--- a/app/Phabricator/PhabricatorAPI.php
+++ b/app/Phabricator/PhabricatorAPI.php
@@ -1,163 +1,164 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
use Nasqueron\Notifications\Contracts\APIClient;
use Nasqueron\Notifications\Facades\Services;
class PhabricatorAPI implements APIClient {
///
/// Private members
///
/**
* The Phabricator main URL
*
* @var string
*/
private $endPoint;
/**
* The token generated at /settings/panel/apitokens/ to query the API
*
* @var string
*/
private $apiToken;
///
/// Constructors
///
/**
* Initializes a new instance of the Phabricator API class
*
* @param string $endPoint The Phabricator main URL, without trailing slash
* @param string $apiToken The token generated at /settings/panel/apitokens/
*/
- public function __construct ($endPoint, $apiToken) {
+ public function __construct (string $endPoint, string $apiToken) {
$this->endPoint = $endPoint;
$this->apiToken = $apiToken;
}
/**
* @throws \RuntimeException when the service isn't in credentials.json
*/
public static function forInstance ($instance) : PhabricatorAPI {
$service = Services::findServiceByProperty(
'Phabricator',
'instance',
$instance
);
if ($service === null) {
throw new \RuntimeException(
"No credentials for Phabricator instance $instance."
);
}
return new self($service->instance, $service->secret);
}
/**
* @throws \RuntimeException when the service isn't in credentials.json
*/
public static function forProject ($project) {
$service = Services::findServiceByDoor('Phabricator', $project);
if ($service === null) {
throw new \RuntimeException(
"No credentials for Phabricator project $project."
);
}
return new self($service->instance, $service->secret);
}
///
/// APIClient implementation
///
/**
* Sets API end point
*
* @param string $url The API end point URL
*/
- public function setEndPoint ($url) {
+ public function setEndPoint (string $url) {
$this->endPoint = $url;
}
/**
* Calls a Conduit API method
*
- * @param string $method The method to call (e.g. repository.create)
- * @param array $arguments The arguments to use
+ * @param string $method The method to call (e.g. repository.create)
+ * @param array $arguments The arguments to use
+ *
* @return mixed The API result
*/
- public function call ($method, $arguments = []) {
+ public function call (string $method, array $arguments = []) {
$url = $this->endPoint . '/api/' . $method;
$arguments['api.token'] = $this->apiToken;
$reply = json_decode(static::post($url, $arguments));
if ($reply->error_code !== null) {
throw new PhabricatorAPIException(
$reply->error_code,
$reply->error_info
);
}
return $reply->result;
}
///
/// Helper methods
///
/**
* Gets the first result of an API reply.
*
* @param iterable $reply
* @return mixed
*/
- public static function getFirstResult ($reply) {
+ public static function getFirstResult (iterable $reply) {
if (is_object($reply) && property_exists($reply, 'data')) {
$reply = $reply->data;
}
foreach ($reply as $value) {
return $value;
}
}
///
/// CURL session
///
protected static function getPostFields ($arguments) {
$items = [];
foreach ($arguments as $key => $value) {
$items[] = urlencode($key) . '=' . urlencode($value);
}
return implode('&', $items);
}
protected static function post ($url, $arguments) {
$options = [
CURLOPT_URL => $url,
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => 1,
CURLOPT_POSTFIELDS => static::getPostFields($arguments),
];
$ch = curl_init();
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
curl_close($ch);
if ($result === false) {
throw new \RuntimeException(
"Can't reach Phabricator API endpoint: $url"
);
}
return $result;
}
}
diff --git a/app/Phabricator/PhabricatorAPIFactory.php b/app/Phabricator/PhabricatorAPIFactory.php
index 193d06d..8127f21 100644
--- a/app/Phabricator/PhabricatorAPIFactory.php
+++ b/app/Phabricator/PhabricatorAPIFactory.php
@@ -1,28 +1,28 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
use Nasqueron\Notifications\Contracts\APIFactory;
class PhabricatorAPIFactory implements APIFactory {
/**
* Gets an instance of the Phabricator API client class.
*
* @param string $instance The Phabricator instance
* @return \Nasqueron\Notifications\Phabricator\PhabricatorAPI
*/
- public function get ($instance) {
+ public function get (string $instance) {
return PhabricatorAPI::forInstance($instance);
}
/**
* Gets an instance of the Phabricator API client class for a project.
*
* @param string $project The Phabricator project name
* @return PhabricatorAPI
*/
- public function getForProject ($project) {
+ public function getForProject (string $project) {
return PhabricatorAPI::forProject($project);
}
}
diff --git a/app/Phabricator/PhabricatorStory.php b/app/Phabricator/PhabricatorStory.php
index 33822bb..2bf1837 100644
--- a/app/Phabricator/PhabricatorStory.php
+++ b/app/Phabricator/PhabricatorStory.php
@@ -1,331 +1,331 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
use InvalidArgumentException;
class PhabricatorStory {
///
/// Properties
///
/**
* The Phabricator instance name
*
* @var string
*/
public $instanceName;
/**
* The unique identifier Phabricator assigns to each story
*
* @var int
*/
public $id;
/**
* Type of story (e.g. PhabricatorApplicationTransactionFeedStory)
*
* @var string
*/
public $type;
/**
* @var array|null
*/
public $data;
/**
* The person logged to Phabricator and triggering the event
*
* @var string
*/
public $authorPHID;
/**
* A short English textual description of the event
*
* @var string
*/
public $text;
/**
* The unixtime the event occured
*
* @var int
*/
public $epoch;
/**
* The projects attached to this story.
*
* When there is no project, [].
* When not yet queried, null.
*
* @var string[]|null
*/
private $projects = null;
///
/// Constructors
///
/**
* Initializes a new instance of the Phabricator story class
*
* @param string $instanceName The Phabricator instance name
*/
- public function __construct ($instanceName) {
+ public function __construct (string $instanceName) {
$this->instanceName = $instanceName;
}
/**
* Initializes a new instance of PhabricatorStory from an iterable.
*
* This is intended to parse the feed.hooks payloads.
*
* @param string $instanceName The Phabricator instance name
* @param iterable $payload The data submitted by Phabricator
* @return PhabricatorStory
*/
public static function loadFromIterable (
string $instanceName, iterable $payload
) {
$instance = new self($instanceName);
foreach ($payload as $key => $value) {
$property = self::mapPhabricatorFeedKey($key);
$instance->$property = $value;
}
return $instance;
}
public static function loadFromJson (
$instanceName,
$payload
) {
$array = json_decode($payload, true);
if (!is_array($array)) {
throw new InvalidArgumentException(<<<MSG
Payload should be deserializable as an array.
MSG
);
}
return self::loadFromIterable($instanceName, $array);
}
///
/// Helper methods
///
private function hasVoidObjectType () : bool {
return $this->data === null || !isset($this->data['objectPHID']);
}
/**
* Gets object type (e.g. TASK for PHID-TASK-l34fw5wievp6n6rnvpuk)
*
* @return string The object type, as a 4 letters string (e.g. 'TASK')
*/
public function getObjectType () {
if ($this->hasVoidObjectType()) {
return 'VOID';
}
return substr($this->data['objectPHID'], 5, 4);
}
/**
* Gets the identifier of the projets related to this task
*
* return string[] The list of project PHIDs
*/
public function getProjectsPHIDs () {
if (!array_key_exists('objectPHID', $this->data)) {
return [];
}
$objectPHID = $this->data['objectPHID'];
$objectType = $this->getObjectType();
switch ($objectType) {
case 'DREV':
return $this->getItemProjectsPHIDs(
'repository.query',
$this->getRepositoryPHID('differential.query')
);
case 'TASK':
return $this->getItemProjectsPHIDs(
'maniphest.query',
$objectPHID
);
case 'CMIT':
return $this->getItemProjectsPHIDs(
'repository.query',
$this->getRepositoryPHID('diffusion.querycommits')
);
case 'PSTE':
return $this->getItemProjectsPHIDsThroughApplicationSearch(
'paste.search',
$objectPHID
);
default:
return [];
}
}
/**
* Gets the PHID of a repository
*
* @param string $method The API method to call (e.g. differential.query)
* @return string The repository PHID or "" if not found
*/
- public function getRepositoryPHID ($method) {
+ public function getRepositoryPHID (string $method) {
$objectPHID = $this->data['objectPHID'];
$api = PhabricatorAPI::forProject($this->instanceName);
$reply = $api->call(
$method,
[ 'phids[0]' => $objectPHID ]
);
if ($reply === []) {
return "";
}
$apiResult = PhabricatorAPI::getFirstResult($reply);
if ($apiResult === null) {
// Repository information can't be fetched (T1136).
// This occurs when the bot account used to fetch information
// doesn't have access to the repository, for example if it's
// in a private space or restricted to a group it doesn't belong to.
return "";
}
return $apiResult->repositoryPHID;
}
/**
* Gets the projects for a specific item
*
- * @param string $method The API method to call (e.g. differential.query)
+ * @param string $method The API method to call (e.g. differential.query)
* @param string $objectPHID The object PHID to pass as method parameter
* @return string[] The list of project PHIDs
*/
- public function getItemProjectsPHIDs ($method, $objectPHID) {
+ public function getItemProjectsPHIDs (string $method, string $objectPHID) {
if (!$objectPHID) {
return [];
}
$api = PhabricatorAPI::forProject($this->instanceName);
$reply = $api->call(
$method,
[ 'phids[0]' => $objectPHID ]
);
if ($reply === []) {
return [];
}
return PhabricatorAPI::getFirstResult($reply)->projectPHIDs;
}
/**
* Gets the project for a specific item, using the new ApplicationSearch.
*
* This is a transitional method: when every Phabricator will have been
* migrated from info (generation 1) or query (generation 2) to search
* (generation 3), we'll rename it to getItemProjectsPHIDs and overwrite it.
*/
protected function getItemProjectsPHIDsThroughApplicationSearch (
$method,
$objectPHID
) {
if (!$objectPHID) {
return [];
}
$api = PhabricatorAPI::forProject($this->instanceName);
$reply = $api->call(
$method,
[
'constraints[phids][0]' => $objectPHID,
'attachments[projects]' => 1
]
);
$apiResult = PhabricatorAPI::getFirstResult($reply);
if ($apiResult === null) {
// Object information (e.g. a paste) can't be fetched (T1138).
// This occurs when the bot account used to fetch information
// doesn't have access to the object, for example if it's
// in a private space or restricted to a group it doesn't belong to.
return [];
}
return $apiResult->attachments->projects->projectPHIDs;
}
/**
* Gets the list of the projects associated to the story
*
* @return string[] The list of project PHIDs
*/
public function getProjects () {
if ($this->projects === null) {
$this->attachProjects();
}
return $this->projects;
}
/**
* Queries the list of the projects associated to the story
* and attached it to the projects property.
*/
public function attachProjects () {
$this->projects = [];
$PHIDs = $this->getProjectsPHIDs();
if (count($PHIDs) == 0) {
// No project is attached to the story's object
return;
}
$map = ProjectsMap::load($this->instanceName);
foreach ($PHIDs as $PHID) {
$this->projects[] = $map->getProjectName($PHID);
}
}
///
/// Static helper methods
///
/**
* Maps a field of the API reply to a property of the PhabricatorStory class
*
* @param string $key The field of the API reply
* @return string The property's name
*/
- public static function mapPhabricatorFeedKey ($key) {
+ public static function mapPhabricatorFeedKey (string $key) {
if ($key == "storyID") {
return "id";
}
if (str_starts_with($key, "story") && strlen($key) > 5) {
return lcfirst(substr($key, 5));
}
return $key;
}
}
diff --git a/app/Phabricator/ProjectsMap.php b/app/Phabricator/ProjectsMap.php
index 0596b70..2af59d0 100644
--- a/app/Phabricator/ProjectsMap.php
+++ b/app/Phabricator/ProjectsMap.php
@@ -1,285 +1,285 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
use Nasqueron\Notifications\Contracts\APIClient as APIClient;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
class ProjectsMap implements \IteratorAggregate, \ArrayAccess {
///
/// Private properties and constants
///
/**
* The maximum number of projects to fetch
*/
const LIMIT = 1000;
/**
* The projects as an array with phid as keys, project names as $value
*
* @var string[]
*/
private $map = [];
/**
* The Phabricator instance name for this projects map
*
* @var string
*/
private $instanceName;
/**
*
* @var \Nasqueron\Notifications\Contracts\APIClient
*/
private $apiClient;
/**
* The source of the map
*
* @var string
*/
private $source = 'unloaded';
///
/// Constructor
///
/**
* Initializes a new instance of ProjectsMap.
*
* @param string $instanceName The Phabricator instance name
*/
- public function __construct ($instanceName) {
+ public function __construct (string $instanceName) {
$this->instanceName = $instanceName;
}
///
/// IteratorAggregate interface implementation
///
/**
* Gets iterator.
*
* @return \Traversable
*/
public function getIterator () {
return new \ArrayIterator($this->map);
}
///
/// ArrayAccess interface implementation
///
/**
* Determines whether an offset exists.
*
* @param mixed $offset The offset
* @return bool
*/
- public function offsetExists ($offset) {
+ public function offsetExists (mixed $offset) {
return array_key_exists($offset, $this->map);
}
/**
* Gets the value at the specified offset.
*
* @param mixed $offset The offset.
* @return mixed The value
*/
- public function offsetGet ($offset) {
+ public function offsetGet (mixed $offset) {
return $this->map[$offset];
}
/**
* Assigns a value to the specified offset.
*
* @param mixed $offset The offset
* @param mixed $value The value to assign
*/
- public function offsetSet ($offset, $value) {
+ public function offsetSet (mixed $offset, mixed $value) {
$this->map[$offset] = $value;
}
/**
* Unsets a value at the specified offset.
*
* @param mixed $offset The offset where to remove the value
*/
- public function offsetUnset ($offset) {
+ public function offsetUnset (mixed $offset) {
unset($this->map[$offset]);
}
///
/// Static constructors
///
/**
* Gets a new ProjectsMap instance from cache or API when not cached.
*
* @param string $phabricatorInstanceName The Phabricator instance name
* @return ProjectsMap
*/
- public static function load ($phabricatorInstanceName) {
+ public static function load (string $phabricatorInstanceName) {
$instance = new self($phabricatorInstanceName);
if ($instance->isCached()) {
$instance->loadFromCache();
} else {
$instance->fetchFromAPI();
}
return $instance;
}
/**
* Gets a new ProjectsMap instance and queries Phabricator API to fill it.
*/
public static function fetch (
string $phabricatorInstanceName,
?APIClient $apiClient = null
) {
$instance = new self($phabricatorInstanceName);
$instance->setAPIClient($apiClient);
$instance->fetchFromAPI();
return $instance;
}
///
/// API
///
/**
* @return \Nasqueron\Notifications\Contracts\APIClient
*/
public function getAPIClient () {
if ($this->apiClient === null) {
$factory = App::make('phabricator-api');
$this->apiClient = $factory->getForProject($this->instanceName);
}
return $this->apiClient;
}
/**
* @param \Nasqueron\Notifications\Contracts\APIClient|null $apiClient
*/
public function setAPIClient (?APIClient $apiClient = null) {
$this->apiClient = $apiClient;
}
/**
* Fetches the projects' map from the Phabricator API.
*
* @throws \Exception when API reply is empty or invalid.
*/
private function fetchFromAPI () {
$reply = $this->getAPIClient()->call(
'project.query',
[ 'limit' => self::LIMIT ]
);
if (!$reply) {
throw new \Exception(<<<MSG
Empty reply calling project.query at $this->instanceName Conduit API.
MSG
);
}
if (!property_exists($reply, 'data')) {
throw new \Exception(<<<MSG
Invalid reply calling project.query at $this->instanceName Conduit API.
MSG
);
}
foreach ($reply->data as $phid => $projectInfo) {
$this->offsetSet($phid, $projectInfo->name);
}
$this->source = 'api';
}
///
/// Cache
///
/**
* Gets cache key.
*
* @return string The cache key for the current projects map
*/
private function getCacheKey () {
return class_basename(get_class($this))
. '-'
. md5($this->instanceName);
}
/**
* Determines if the instance is cached
*
* @return bool true if cached; otherwise, false.
*/
public function isCached () {
return Cache::has($this->getCacheKey());
}
/**
* Saves data to cache
*/
public function saveToCache () {
Cache::forever($this->getCacheKey(), $this->map);
}
/**
* Loads data from cache
*
* Populates 'map' and 'source' properties
*/
public function loadFromCache () {
$cachedMap = Cache::get($this->getCacheKey());
if ($cachedMap !== null) {
$this->map = $cachedMap;
$this->source = 'cache';
}
}
///
/// Output
///
/**
* Gets project name, refreshing the cache if needed.
*
* @param string $projectPHID the PHID of the project to query the name
* @return string The name of the poject, or an empty string if not found
*/
- public function getProjectName ($projectPHID) {
+ public function getProjectName (string $projectPHID) {
if ($this->offsetExists($projectPHID)) {
return $this->offsetGet($projectPHID);
}
if ($this->source !== 'api') {
$this->fetchFromAPI();
return $this->getProjectName($projectPHID);
}
return "";
}
/**
* Returns the projects map as an array.
*
* @return array An array, each row containing ['PHID', 'project name']
*/
public function toArray () {
$array = [];
foreach ($this->map as $phid => $projectName) {
$array[] = [$phid, $projectName];
}
return $array;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Nov 24, 22:22 (8 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2259032
Default Alt Text
(40 KB)
Attached To
Mode
rNOTIF Notifications center
Attached
Detach File
Event Timeline
Log In to Comment