AbstractPicoPlugin.php 7.8 KB

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