AbstractPicoPlugin.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <?php
  2. /**
  3. * Abstract class to extend from when implementing a Pico plugin
  4. *
  5. * @see PicoPluginInterface
  6. *
  7. * @author Daniel Rudolf
  8. * @link http://picocms.org
  9. * @license http://opensource.org/licenses/MIT The MIT License
  10. * @version 1.0
  11. */
  12. abstract class AbstractPicoPlugin implements PicoPluginInterface
  13. {
  14. /**
  15. * Current instance of Pico
  16. *
  17. * @see PicoPluginInterface::getPico()
  18. * @var Pico
  19. */
  20. private $pico;
  21. /**
  22. * Boolean indicating if this plugin is enabled (true) or disabled (false)
  23. *
  24. * @see PicoPluginInterface::isEnabled()
  25. * @see PicoPluginInterface::setEnabled()
  26. * @var boolean
  27. */
  28. protected $enabled = true;
  29. /**
  30. * Boolean indicating if this plugin was ever enabled/disabled manually
  31. *
  32. * @see PicoPluginInterface::isStatusChanged()
  33. * @var boolean
  34. */
  35. protected $statusChanged = false;
  36. /**
  37. * List of plugins which this plugin depends on
  38. *
  39. * @see AbstractPicoPlugin::checkDependencies()
  40. * @see PicoPluginInterface::getDependencies()
  41. * @var string[]
  42. */
  43. protected $dependsOn = array();
  44. /**
  45. * List of plugin which depend on this plugin
  46. *
  47. * @see AbstractPicoPlugin::checkDependants()
  48. * @see PicoPluginInterface::getDependants()
  49. * @var object[]
  50. */
  51. private $dependants;
  52. /**
  53. * @see PicoPluginInterface::__construct()
  54. */
  55. public function __construct(Pico $pico)
  56. {
  57. $this->pico = $pico;
  58. }
  59. /**
  60. * @see PicoPluginInterface::handleEvent()
  61. */
  62. public function handleEvent($eventName, array $params)
  63. {
  64. // plugins can be enabled/disabled using the config
  65. if ($eventName === 'onConfigLoaded') {
  66. $pluginEnabled = $this->getConfig(get_called_class() . '.enabled');
  67. if ($pluginEnabled !== null) {
  68. $this->setEnabled($pluginEnabled);
  69. } else {
  70. $pluginEnabled = $this->getPluginConfig('enabled');
  71. if ($pluginEnabled !== null) {
  72. $this->setEnabled($pluginEnabled);
  73. } elseif ($this->enabled) {
  74. // make sure dependencies are already fulfilled,
  75. // otherwise the plugin needs to be enabled manually
  76. try {
  77. $this->checkDependencies(false);
  78. } catch (RuntimeException $e) {
  79. $this->enabled = false;
  80. }
  81. }
  82. }
  83. }
  84. if ($this->isEnabled() || ($eventName === 'onPluginsLoaded')) {
  85. if (method_exists($this, $eventName)) {
  86. call_user_func_array(array($this, $eventName), $params);
  87. }
  88. }
  89. }
  90. /**
  91. * @see PicoPluginInterface::setEnabled()
  92. */
  93. public function setEnabled($enabled, $recursive = true, $auto = false)
  94. {
  95. $this->statusChanged = (!$this->statusChanged) ? !$auto : true;
  96. $this->enabled = (bool) $enabled;
  97. if ($enabled) {
  98. $this->checkDependencies($recursive);
  99. } else {
  100. $this->checkDependants($recursive);
  101. }
  102. }
  103. /**
  104. * @see PicoPluginInterface::isEnabled()
  105. */
  106. public function isEnabled()
  107. {
  108. return $this->enabled;
  109. }
  110. /**
  111. * @see PicoPluginInterface::isStatusChanged()
  112. */
  113. public function isStatusChanged()
  114. {
  115. return $this->statusChanged;
  116. }
  117. /**
  118. * @see PicoPluginInterface::getPico()
  119. */
  120. public function getPico()
  121. {
  122. return $this->pico;
  123. }
  124. /**
  125. * Returns either the value of the specified plugin config variable or
  126. * the config array
  127. *
  128. * @param string $configName optional name of a config variable
  129. * @return mixed returns either the value of the named plugin
  130. * config variable, null if the config variable doesn't exist or the
  131. * plugin's config array if no config name was supplied
  132. */
  133. protected function getPluginConfig($configName = null)
  134. {
  135. $pluginConfig = $this->getConfig(get_called_class());
  136. if ($pluginConfig) {
  137. if ($configName === null) {
  138. return $pluginConfig;
  139. } elseif (isset($pluginConfig[$configName])) {
  140. return $pluginConfig[$configName];
  141. }
  142. }
  143. return null;
  144. }
  145. /**
  146. * Passes all not satisfiable method calls to Pico
  147. *
  148. * @see Pico
  149. * @param string $methodName name of the method to call
  150. * @param array $params parameters to pass
  151. * @return mixed return value of the called method
  152. */
  153. public function __call($methodName, array $params)
  154. {
  155. if (method_exists($this->getPico(), $methodName)) {
  156. return call_user_func_array(array($this->getPico(), $methodName), $params);
  157. }
  158. throw new BadMethodCallException(
  159. 'Call to undefined method ' . get_class($this->getPico()) . '::' . $methodName . '() '
  160. . 'through ' . get_called_class() . '::__call()'
  161. );
  162. }
  163. /**
  164. * Enables all plugins which this plugin depends on
  165. *
  166. * @see PicoPluginInterface::getDependencies()
  167. * @param boolean $recursive enable required plugins automatically
  168. * @return void
  169. * @throws RuntimeException thrown when a dependency fails
  170. */
  171. protected function checkDependencies($recursive)
  172. {
  173. foreach ($this->getDependencies() as $pluginName) {
  174. try {
  175. $plugin = $this->getPlugin($pluginName);
  176. } catch (RuntimeException $e) {
  177. throw new RuntimeException(
  178. "Unable to enable plugin '" . get_called_class() . "': "
  179. . "Required plugin '" . $pluginName . "' not found"
  180. );
  181. }
  182. // plugins which don't implement PicoPluginInterface are always enabled
  183. if (($plugin instanceof PicoPluginInterface) && !$plugin->isEnabled()) {
  184. if ($recursive) {
  185. if (!$plugin->isStatusChanged()) {
  186. $plugin->setEnabled(true, true, true);
  187. } else {
  188. throw new RuntimeException(
  189. "Unable to enable plugin '" . get_called_class() . "': "
  190. . "Required plugin '" . $pluginName . "' was disabled manually"
  191. );
  192. }
  193. } else {
  194. throw new RuntimeException(
  195. "Unable to enable plugin '" . get_called_class() . "': "
  196. . "Required plugin '" . $pluginName . "' is disabled"
  197. );
  198. }
  199. }
  200. }
  201. }
  202. /**
  203. * @see PicoPluginInterface::getDependencies()
  204. */
  205. public function getDependencies()
  206. {
  207. return (array) $this->dependsOn;
  208. }
  209. /**
  210. * Disables all plugins which depend on this plugin
  211. *
  212. * @see PicoPluginInterface::getDependants()
  213. * @param boolean $recursive disabled dependant plugins automatically
  214. * @return void
  215. * @throws RuntimeException thrown when a dependency fails
  216. */
  217. protected function checkDependants($recursive)
  218. {
  219. $dependants = $this->getDependants();
  220. if (!empty($dependants)) {
  221. if ($recursive) {
  222. foreach ($this->getDependants() as $pluginName => $plugin) {
  223. if ($plugin->isEnabled()) {
  224. if (!$plugin->isStatusChanged()) {
  225. $plugin->setEnabled(false, true, true);
  226. } else {
  227. throw new RuntimeException(
  228. "Unable to disable plugin '" . get_called_class() . "': "
  229. . "Required by manually enabled plugin '" . $pluginName . "'"
  230. );
  231. }
  232. }
  233. }
  234. } else {
  235. $dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' ';
  236. $dependantsList .= "'" . implode("', '", array_keys($dependants)) . "'";
  237. throw new RuntimeException(
  238. "Unable to disable plugin '" . get_called_class() . "': "
  239. . "Required by " . $dependantsList
  240. );
  241. }
  242. }
  243. }
  244. /**
  245. * @see PicoPluginInterface::getDependants()
  246. */
  247. public function getDependants()
  248. {
  249. if ($this->dependants === null) {
  250. $this->dependants = array();
  251. foreach ($this->getPlugins() as $pluginName => $plugin) {
  252. // only plugins which implement PicoPluginInterface support dependencies
  253. if ($plugin instanceof PicoPluginInterface) {
  254. $dependencies = $plugin->getDependencies();
  255. if (in_array(get_called_class(), $dependencies)) {
  256. $this->dependants[$pluginName] = $plugin;
  257. }
  258. }
  259. }
  260. }
  261. return $this->dependants;
  262. }
  263. }