webhook.php
344 lines
| 1 | <?php |
| 2 | /** |
| 3 | * @package VikAppointments |
| 4 | * @subpackage core |
| 5 | * @author E4J s.r.l. |
| 6 | * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved. |
| 7 | * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL |
| 8 | * @link https://vikwp.com |
| 9 | */ |
| 10 | |
| 11 | // No direct access |
| 12 | defined('ABSPATH') or die('No script kiddies please!'); |
| 13 | |
| 14 | /** |
| 15 | * Web hook payload encapsulation. |
| 16 | * |
| 17 | * @since 1.7 |
| 18 | */ |
| 19 | class VAPWebHook |
| 20 | { |
| 21 | /** |
| 22 | * A list of include paths. |
| 23 | * |
| 24 | * @var array |
| 25 | */ |
| 26 | protected static $includePaths = array(); |
| 27 | |
| 28 | /** |
| 29 | * The hook name. |
| 30 | * |
| 31 | * @var string |
| 32 | */ |
| 33 | protected $hook; |
| 34 | |
| 35 | /** |
| 36 | * The payload to send to the end-point. |
| 37 | * |
| 38 | * @var mixed |
| 39 | */ |
| 40 | protected $payload; |
| 41 | |
| 42 | /** |
| 43 | * Returns the proper instance able to handle the specified |
| 44 | * web hook action. |
| 45 | * |
| 46 | * @param string $hook The hook name. |
| 47 | * @param mixed $payload The payload to deliver. |
| 48 | * |
| 49 | * @return self |
| 50 | */ |
| 51 | public static function getInstance($hook, $payload = array()) |
| 52 | { |
| 53 | // get rid of initial "on" |
| 54 | $name = preg_replace("/^on/", '', $hook); |
| 55 | $filename = strtolower($name); |
| 56 | |
| 57 | // use the default class name |
| 58 | $classname = 'VAPWebHook' . $name; |
| 59 | |
| 60 | // save time by immediately checking whether the class already exists |
| 61 | if (!class_exists($name)) |
| 62 | { |
| 63 | $found = false; |
| 64 | |
| 65 | // iterate all include paths |
| 66 | foreach (static::getIncludePaths() as $path) |
| 67 | { |
| 68 | // attempt to load file from given include path |
| 69 | $found = $found || VAPLoader::import($classname, $path); |
| 70 | } |
| 71 | |
| 72 | // check whether the file exists or has been already imported |
| 73 | if ($found || VAPLoader::import('libraries.webhook.classes.' . $filename)) |
| 74 | { |
| 75 | if (!class_exists($classname)) |
| 76 | { |
| 77 | // invalid class, throw exception |
| 78 | throw new RuntimeException(sprintf('Web Hook [%s] not found', $classname), 500); |
| 79 | } |
| 80 | } |
| 81 | else |
| 82 | { |
| 83 | // missing handler, use default class |
| 84 | $classname = 'VAPWebHook'; |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | // instantiate matching class |
| 89 | return new $classname($hook, $payload); |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * Returns a list of supported web hook handlers. |
| 94 | * |
| 95 | * @param boolean $object True to return web hook instances, false to include |
| 96 | * only their names. |
| 97 | * |
| 98 | * @return array An associative array containing the hook event (key) and the |
| 99 | * hook name/instance (value). |
| 100 | */ |
| 101 | public static function getSupportedHooks($object = false) |
| 102 | { |
| 103 | $hooks = array(); |
| 104 | |
| 105 | // get all include paths |
| 106 | $paths = static::getIncludePaths(); |
| 107 | // append the default directory |
| 108 | $paths[] = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'classes'; |
| 109 | |
| 110 | foreach ($paths as $dir) |
| 111 | { |
| 112 | // get all PHP files contained within this folder |
| 113 | $files = glob(rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*.php'); |
| 114 | |
| 115 | foreach ($files as $file) |
| 116 | { |
| 117 | // extract hook name from file path |
| 118 | $hookname = preg_replace("/\.php$/i", '', basename($file)); |
| 119 | |
| 120 | try |
| 121 | { |
| 122 | // create webhook instance |
| 123 | $webhook = static::getInstance($hookname); |
| 124 | |
| 125 | if ($object) |
| 126 | { |
| 127 | // save the whole webhook instance |
| 128 | $hooks[$webhook->getHook()] = $webhook; |
| 129 | } |
| 130 | else |
| 131 | { |
| 132 | // register only the webhook name |
| 133 | $hooks[$webhook->getHook()] = $webhook->getName(); |
| 134 | // free space |
| 135 | unset($webhook); |
| 136 | } |
| 137 | } |
| 138 | catch (Exception $e) |
| 139 | { |
| 140 | // catch error and propagate system message without breaking the flow |
| 141 | JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | // sort web hooks by name (preserve association) |
| 147 | uasort($hooks, function($a, $b) |
| 148 | { |
| 149 | if ($a instanceof VAPWebHook) |
| 150 | { |
| 151 | $a = $a->getName(); |
| 152 | } |
| 153 | |
| 154 | if ($b instanceof VAPWebHook) |
| 155 | { |
| 156 | $b = $b->getName(); |
| 157 | } |
| 158 | |
| 159 | return strcmp($a, $b); |
| 160 | }); |
| 161 | |
| 162 | return $hooks; |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Class constructor. |
| 167 | * |
| 168 | * @param string $hook The hook name. |
| 169 | * @param mixed $payload The payload to deliver. |
| 170 | */ |
| 171 | public function __construct($hook, $payload) |
| 172 | { |
| 173 | $this->hook = $hook; |
| 174 | $this->payload = $payload; |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * Returns a readable name of the hook. |
| 179 | * |
| 180 | * @return string |
| 181 | */ |
| 182 | public function getName() |
| 183 | { |
| 184 | // by default use the hook as name |
| 185 | return $this->hook; |
| 186 | } |
| 187 | |
| 188 | /** |
| 189 | * Returns an associative array of supported parameters. |
| 190 | * |
| 191 | * @return array |
| 192 | */ |
| 193 | public function getForm() |
| 194 | { |
| 195 | // by default no supported parameters |
| 196 | return array(); |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Returns the registered hook name. |
| 201 | * |
| 202 | * @return string |
| 203 | */ |
| 204 | public function getHook() |
| 205 | { |
| 206 | return $this->hook; |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * Returns the registered payload. |
| 211 | * |
| 212 | * @param mixed $options Either an array or an object, which should contain |
| 213 | * the value of the specified parameters. |
| 214 | * |
| 215 | * @return mixed |
| 216 | */ |
| 217 | public function getPayload($options = array()) |
| 218 | { |
| 219 | if (is_array($this->payload) && array_keys($this->payload) === range(0, count($this->payload) - 1)) |
| 220 | { |
| 221 | $arr = array(); |
| 222 | |
| 223 | // we have a sequential array, we need to name the properties of the list so that |
| 224 | // the receiver can fetch them in a better way |
| 225 | foreach ($this->payload as $i => $v) |
| 226 | { |
| 227 | $arr['arg' . $i] = $v; |
| 228 | } |
| 229 | |
| 230 | return $arr; |
| 231 | } |
| 232 | |
| 233 | return $this->payload; |
| 234 | } |
| 235 | |
| 236 | /** |
| 237 | * Comparator to check whether 2 instances share the same payload parent. |
| 238 | * |
| 239 | * @return boolean |
| 240 | */ |
| 241 | public function equalsTo($webhook) |
| 242 | { |
| 243 | // always different by default |
| 244 | return false; |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * Extends the current payload with the specified data. |
| 249 | * |
| 250 | * @param mixed $data The additional details to inject. |
| 251 | * |
| 252 | * @return void |
| 253 | */ |
| 254 | public function extend($data) |
| 255 | { |
| 256 | if ($data instanceof VAPWebHook) |
| 257 | { |
| 258 | // extract payload from object |
| 259 | $data = $data->getPayload(); |
| 260 | } |
| 261 | |
| 262 | // convert both the payloads into associative arrays |
| 263 | $tmp = json_decode(json_encode($this->payload), true); |
| 264 | $data = json_decode(json_encode($data), true); |
| 265 | |
| 266 | $tmp = is_array($tmp) ? $tmp : (array) $tmp; |
| 267 | $data = is_array($data) ? $data : (array) $data; |
| 268 | |
| 269 | // merge both the payload details |
| 270 | $this->payload = array_merge($tmp, $data); |
| 271 | } |
| 272 | |
| 273 | /** |
| 274 | * Gets a list of supported include paths. |
| 275 | * |
| 276 | * @return array |
| 277 | */ |
| 278 | public static function getIncludePaths() |
| 279 | { |
| 280 | return static::$includePaths; |
| 281 | } |
| 282 | |
| 283 | /** |
| 284 | * Adds one path to include in driver search. |
| 285 | * Proxy of addIncludePaths(). |
| 286 | * |
| 287 | * @param string $path The path to search for drivers. |
| 288 | * |
| 289 | * @return array A list of include paths. |
| 290 | * |
| 291 | * @uses addIncludePaths() |
| 292 | */ |
| 293 | public static function addIncludePath($path) |
| 294 | { |
| 295 | return static::addIncludePaths($path); |
| 296 | } |
| 297 | |
| 298 | /** |
| 299 | * Adds one or more paths to include in driver search. |
| 300 | * |
| 301 | * @param mixed $paths The path or array of paths to search for drivers. |
| 302 | * |
| 303 | * @return array A list of include paths. |
| 304 | * |
| 305 | * @uses getIncludePaths() |
| 306 | * @uses setIncludePaths() |
| 307 | */ |
| 308 | public static function addIncludePaths($paths) |
| 309 | { |
| 310 | $includePaths = static::getIncludePaths(); |
| 311 | |
| 312 | if (!empty($paths)) |
| 313 | { |
| 314 | // in case the path is an array, merge all the paths and make sure we have no duplicates |
| 315 | if (is_array($paths)) |
| 316 | { |
| 317 | $includePaths = array_unique(array_merge($includePaths, $paths)); |
| 318 | } |
| 319 | // otherwise add the path as first element |
| 320 | else |
| 321 | { |
| 322 | $includePaths[] = $paths; |
| 323 | } |
| 324 | |
| 325 | // update include paths |
| 326 | static::setIncludePaths($includePaths); |
| 327 | } |
| 328 | |
| 329 | return $includePaths; |
| 330 | } |
| 331 | |
| 332 | /** |
| 333 | * Sets the include paths to search for drivers. |
| 334 | * |
| 335 | * @param array $paths Array with paths to search in. |
| 336 | * |
| 337 | * @return void |
| 338 | */ |
| 339 | public static function setIncludePaths($paths) |
| 340 | { |
| 341 | static::$includePaths = (array) $paths; |
| 342 | } |
| 343 | } |
| 344 |