Plugin.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. <?php
  2. /**
  3. * @package Grav\Common
  4. *
  5. * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common;
  9. use Grav\Common\Data\Blueprint;
  10. use Grav\Common\Data\Data;
  11. use Grav\Common\Page\Interfaces\PageInterface;
  12. use Grav\Common\Config\Config;
  13. use RocketTheme\Toolbox\Event\EventDispatcher;
  14. use RocketTheme\Toolbox\Event\EventSubscriberInterface;
  15. use RocketTheme\Toolbox\File\YamlFile;
  16. class Plugin implements EventSubscriberInterface, \ArrayAccess
  17. {
  18. /**
  19. * @var string
  20. */
  21. public $name;
  22. /**
  23. * @var array
  24. */
  25. public $features = [];
  26. /**
  27. * @var Grav
  28. */
  29. protected $grav;
  30. /**
  31. * @var Config
  32. */
  33. protected $config;
  34. protected $active = true;
  35. protected $blueprint;
  36. /**
  37. * By default assign all methods as listeners using the default priority.
  38. *
  39. * @return array
  40. */
  41. public static function getSubscribedEvents()
  42. {
  43. $methods = get_class_methods(get_called_class());
  44. $list = [];
  45. foreach ($methods as $method) {
  46. if (strpos($method, 'on') === 0) {
  47. $list[$method] = [$method, 0];
  48. }
  49. }
  50. return $list;
  51. }
  52. /**
  53. * Constructor.
  54. *
  55. * @param string $name
  56. * @param Grav $grav
  57. * @param Config $config
  58. */
  59. public function __construct($name, Grav $grav, Config $config = null)
  60. {
  61. $this->name = $name;
  62. $this->grav = $grav;
  63. if ($config) {
  64. $this->setConfig($config);
  65. }
  66. }
  67. /**
  68. * @param Config $config
  69. * @return $this
  70. */
  71. public function setConfig(Config $config)
  72. {
  73. $this->config = $config;
  74. return $this;
  75. }
  76. /**
  77. * Get configuration of the plugin.
  78. *
  79. * @return array
  80. */
  81. public function config()
  82. {
  83. return $this->config["plugins.{$this->name}"];
  84. }
  85. /**
  86. * Determine if plugin is running under the admin
  87. *
  88. * @return bool
  89. */
  90. public function isAdmin()
  91. {
  92. return Utils::isAdminPlugin();
  93. }
  94. /**
  95. * Determine if plugin is running under the CLI
  96. *
  97. * @return bool
  98. */
  99. public function isCli()
  100. {
  101. return \defined('GRAV_CLI');
  102. }
  103. /**
  104. * Determine if this route is in Admin and active for the plugin
  105. *
  106. * @param string $plugin_route
  107. * @return bool
  108. */
  109. protected function isPluginActiveAdmin($plugin_route)
  110. {
  111. $should_run = false;
  112. $uri = $this->grav['uri'];
  113. if (strpos($uri->path(), $this->config->get('plugins.admin.route') . '/' . $plugin_route) === false) {
  114. $should_run = false;
  115. } elseif (isset($uri->paths()[1]) && $uri->paths()[1] === $plugin_route) {
  116. $should_run = true;
  117. }
  118. return $should_run;
  119. }
  120. /**
  121. * @param array $events
  122. */
  123. protected function enable(array $events)
  124. {
  125. /** @var EventDispatcher $dispatcher */
  126. $dispatcher = $this->grav['events'];
  127. foreach ($events as $eventName => $params) {
  128. if (\is_string($params)) {
  129. $dispatcher->addListener($eventName, [$this, $params]);
  130. } elseif (\is_string($params[0])) {
  131. $dispatcher->addListener($eventName, [$this, $params[0]], $this->getPriority($params, $eventName));
  132. } else {
  133. foreach ($params as $listener) {
  134. $dispatcher->addListener($eventName, [$this, $listener[0]], $this->getPriority($listener, $eventName));
  135. }
  136. }
  137. }
  138. }
  139. /**
  140. * @param array $params
  141. * @param string $eventName
  142. */
  143. private function getPriority($params, $eventName)
  144. {
  145. $grav = Grav::instance();
  146. $override = implode('.', ["priorities", $this->name, $eventName, $params[0]]);
  147. if ($grav['config']->get($override) !== null)
  148. {
  149. return $grav['config']->get($override);
  150. } elseif (isset($params[1])) {
  151. return $params[1];
  152. }
  153. return 0;
  154. }
  155. /**
  156. * @param array $events
  157. */
  158. protected function disable(array $events)
  159. {
  160. /** @var EventDispatcher $dispatcher */
  161. $dispatcher = $this->grav['events'];
  162. foreach ($events as $eventName => $params) {
  163. if (\is_string($params)) {
  164. $dispatcher->removeListener($eventName, [$this, $params]);
  165. } elseif (\is_string($params[0])) {
  166. $dispatcher->removeListener($eventName, [$this, $params[0]]);
  167. } else {
  168. foreach ($params as $listener) {
  169. $dispatcher->removeListener($eventName, [$this, $listener[0]]);
  170. }
  171. }
  172. }
  173. }
  174. /**
  175. * Whether or not an offset exists.
  176. *
  177. * @param string $offset An offset to check for.
  178. * @return bool Returns TRUE on success or FALSE on failure.
  179. */
  180. public function offsetExists($offset)
  181. {
  182. $this->loadBlueprint();
  183. if ($offset === 'title') {
  184. $offset = 'name';
  185. }
  186. return isset($this->blueprint[$offset]);
  187. }
  188. /**
  189. * Returns the value at specified offset.
  190. *
  191. * @param string $offset The offset to retrieve.
  192. * @return mixed Can return all value types.
  193. */
  194. public function offsetGet($offset)
  195. {
  196. $this->loadBlueprint();
  197. if ($offset === 'title') {
  198. $offset = 'name';
  199. }
  200. return $this->blueprint[$offset] ?? null;
  201. }
  202. /**
  203. * Assigns a value to the specified offset.
  204. *
  205. * @param string $offset The offset to assign the value to.
  206. * @param mixed $value The value to set.
  207. * @throws \LogicException
  208. */
  209. public function offsetSet($offset, $value)
  210. {
  211. throw new \LogicException(__CLASS__ . ' blueprints cannot be modified.');
  212. }
  213. /**
  214. * Unsets an offset.
  215. *
  216. * @param string $offset The offset to unset.
  217. * @throws \LogicException
  218. */
  219. public function offsetUnset($offset)
  220. {
  221. throw new \LogicException(__CLASS__ . ' blueprints cannot be modified.');
  222. }
  223. /**
  224. * This function will search a string for markdown links in a specific format. The link value can be
  225. * optionally compared against via the $internal_regex and operated on by the callback $function
  226. * provided.
  227. *
  228. * format: [plugin:myplugin_name](function_data)
  229. *
  230. * @param string $content The string to perform operations upon
  231. * @param callable $function The anonymous callback function
  232. * @param string $internal_regex Optional internal regex to extra data from
  233. *
  234. * @return string
  235. */
  236. protected function parseLinks($content, $function, $internal_regex = '(.*)')
  237. {
  238. $regex = '/\[plugin:(?:' . $this->name . ')\]\(' . $internal_regex . '\)/i';
  239. return preg_replace_callback($regex, $function, $content);
  240. }
  241. /**
  242. * Merge global and page configurations.
  243. *
  244. * @param PageInterface $page The page to merge the configurations with the
  245. * plugin settings.
  246. * @param mixed $deep false = shallow|true = recursive|merge = recursive+unique
  247. * @param array $params Array of additional configuration options to
  248. * merge with the plugin settings.
  249. * @param string $type Is this 'plugins' or 'themes'
  250. *
  251. * @return Data
  252. */
  253. protected function mergeConfig(PageInterface $page, $deep = false, $params = [], $type = 'plugins')
  254. {
  255. $class_name = $this->name;
  256. $class_name_merged = $class_name . '.merged';
  257. $defaults = $this->config->get($type . '.' . $class_name, []);
  258. $page_header = $page->header();
  259. $header = [];
  260. if (!isset($page_header->{$class_name_merged}) && isset($page_header->{$class_name})) {
  261. // Get default plugin configurations and retrieve page header configuration
  262. $config = $page_header->{$class_name};
  263. if (\is_bool($config)) {
  264. // Overwrite enabled option with boolean value in page header
  265. $config = ['enabled' => $config];
  266. }
  267. // Merge page header settings using deep or shallow merging technique
  268. $header = $this->mergeArrays($deep, $defaults, $config);
  269. // Create new config object and set it on the page object so it's cached for next time
  270. $page->modifyHeader($class_name_merged, new Data($header));
  271. } else if (isset($page_header->{$class_name_merged})) {
  272. $merged = $page_header->{$class_name_merged};
  273. $header = $merged->toArray();
  274. }
  275. if (empty($header)) {
  276. $header = $defaults;
  277. }
  278. // Merge additional parameter with configuration options
  279. $header = $this->mergeArrays($deep, $header, $params);
  280. // Return configurations as a new data config class
  281. return new Data($header);
  282. }
  283. /**
  284. * Merge arrays based on deepness
  285. *
  286. * @param string|bool $deep
  287. * @param array $array1
  288. * @param array $array2
  289. * @return array
  290. */
  291. private function mergeArrays($deep, $array1, $array2)
  292. {
  293. if ($deep === 'merge') {
  294. return Utils::arrayMergeRecursiveUnique($array1, $array2);
  295. }
  296. if ($deep === true) {
  297. return array_replace_recursive($array1, $array2);
  298. }
  299. return array_merge($array1, $array2);
  300. }
  301. /**
  302. * Persists to disk the plugin parameters currently stored in the Grav Config object
  303. *
  304. * @param string $plugin_name The name of the plugin whose config it should store.
  305. *
  306. * @return bool
  307. */
  308. public static function saveConfig($plugin_name)
  309. {
  310. if (!$plugin_name) {
  311. return false;
  312. }
  313. $grav = Grav::instance();
  314. $locator = $grav['locator'];
  315. $filename = 'config://plugins/' . $plugin_name . '.yaml';
  316. $file = YamlFile::instance($locator->findResource($filename, true, true));
  317. $content = $grav['config']->get('plugins.' . $plugin_name);
  318. $file->save($content);
  319. $file->free();
  320. return true;
  321. }
  322. /**
  323. * Simpler getter for the plugin blueprint
  324. *
  325. * @return Blueprint
  326. */
  327. public function getBlueprint()
  328. {
  329. if (!$this->blueprint) {
  330. $this->loadBlueprint();
  331. }
  332. return $this->blueprint;
  333. }
  334. /**
  335. * Load blueprints.
  336. */
  337. protected function loadBlueprint()
  338. {
  339. if (!$this->blueprint) {
  340. $grav = Grav::instance();
  341. $plugins = $grav['plugins'];
  342. $this->blueprint = $plugins->get($this->name)->blueprints();
  343. }
  344. }
  345. }