1 <?php
2 3 4 5 6 7
8
9 namespace Hawk;
10
11 12 13 14 15
16 class Plugin{
17 18 19
20 const TABLE = 'Plugin';
21
22 23 24
25 const MANIFEST_BASENAME = 'manifest.json';
26
27 28 29
30 const NAME_PATTERN = '[a-zA-Z0-9\-_.]+';
31
32 33 34 35 36
37 private $name,
38
39 40 41 42 43
44 $definition = array(),
45
46
47 48 49 50 51
52 $options = array();
53
54 55 56 57 58
59 private $rootDir,
60
61 62 63 64 65
66 $removable = true,
67
68 69 70 71 72
73 $active;
74
75
76 77 78 79 80
81 public static $mainPlugins = array('main', 'install', 'admin');
82
83
84 85 86 87 88
89 private static $instances = array();
90
91
92 93 94 95 96
97 public static $forbiddenNames = array('custom');
98
99
100 101 102 103 104 105
106 private static $filePlugins = array();
107
108
109 110 111 112 113
114 private function __construct($name){
115 $this->name = $name;
116 $this->rootDir = ($this->isMainPlugin() ? MAIN_PLUGINS_DIR : PLUGINS_DIR) . $this->name . '/';
117
118 if(!is_dir($this->rootDir)) {
119 throw new \Exception('The plugin does not exists');
120 }
121
122 if(!$this->isMainPlugin()) {
123 if(!is_file($this->rootDir . self::MANIFEST_BASENAME)) {
124 throw new \Exception('The plugin must have a file manifest.json');
125 }
126 $this->definition = json_decode(file_get_contents($this->rootDir . self::MANIFEST_BASENAME), true);
127 }
128 else{
129 $this->active = true;
130 $this->removable = false;
131 $this->definition = array(
132 'title' => Lang::get($this->name . '.plugin-name'),
133 );
134 }
135 }
136
137
138 139 140 141 142 143 144
145 public static function get($name){
146 try{
147 if(!isset(self::$instances[$name])) {
148 self::$instances[$name] = new self($name);
149 }
150
151 return self::$instances[$name];
152 }
153 catch(\Exception $e){
154 return null;
155 }
156 }
157
158
159 160 161 162 163
164 public static function current(){
165 $callingFile = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'];
166
167 return self::getFilePlugin($callingFile);
168 }
169
170
171 172 173 174 175 176 177
178 public static function getFilePlugin($file){
179
180 if(isset(self::$filePlugins[$file])) {
181 return self::$filePlugins[$file];
182 }
183
184 if(strpos($file, PLUGINS_DIR) !== false) {
185 $dir = str_replace(PLUGINS_DIR, '', $file);
186 }
187 elseif(strpos($file, MAIN_PLUGINS_DIR) !== false) {
188 $dir = str_replace(MAIN_PLUGINS_DIR, '', $file);
189 }
190 else{
191 return null;
192 }
193 list($name) = explode(DIRECTORY_SEPARATOR, $dir);
194
195
196 $plugin = self::get($name);
197
198
199 self::$filePlugins[$file] = &$plugin;
200
201
202 return $plugin;
203 }
204
205
206 207 208 209 210 211 212 213
214 public static function getAll($includeMain = true, $loadConf = false){
215 $plugins = array();
216 $dirs = $includeMain ? array(MAIN_PLUGINS_DIR, PLUGINS_DIR) : array(PLUGINS_DIR);
217
218 if($loadConf && App::conf()->has('db')) {
219 $configs = App::db()->select(
220 array(
221 'from' => DB::getFullTablename(self::TABLE),
222 'index' => 'name',
223 'return' => DB::RETURN_OBJECT
224 )
225 );
226 }
227 else{
228 $configs = array();
229 }
230
231 foreach($dirs as $dir){
232 foreach(glob($dir . '*', GLOB_ONLYDIR) as $dir){
233 $name = basename($dir);
234 $config = isset($configs[$name]) ? $configs[$name] : null;
235
236 $plugin = self::get($name);
237 if(!$plugin->isMainPlugin()) {
238 $plugin->active = isset($config->active) ? $config->active : false;
239 }
240 $plugins[$name] = $plugin;
241 }
242 }
243
244 return $plugins;
245 }
246
247
248 249 250 251 252 253 254
255 public static function getActivePlugins($includeMain = true){
256 $plugins = self::getAll($includeMain, true);
257
258 return array_filter(
259 $plugins, function ($plugin) {
260 return $plugin->active;
261 }
262 );
263 }
264
265
266 267 268 269 270
271 public static function getMainPlugins(){
272 return array_map(
273 function ($name) {
274 return new self($name);
275 }, self::$mainPlugins
276 );
277 }
278
279
280
281 282 283 284 285
286 public function getName(){
287 return $this->name;
288 }
289
290
291 292 293 294 295
296 public function isMainPlugin(){
297 return in_array($this->name, self::$mainPlugins);
298 }
299
300 301 302 303 304
305 public function getOptions(){
306 if(!isset($this->options)) {
307 $this->options = Option::getPluginOptions($this->name);
308 }
309
310 return $this->options;
311 }
312
313
314 315 316 317 318 319 320 321 322
323 public function getDefinition($prop = null){
324 if($prop) {
325 return isset($this->definition[$prop]) ? $this->definition[$prop]: null;
326 }
327 return $this->definition;
328 }
329
330
331 332 333 334 335
336 public function getRootDir(){
337 return $this->rootDir;
338 }
339
340
341 342 343 344 345 346 347
348 public function getStartFile(){
349 return $this->getRootDir() . 'start.php';
350 }
351
352
353 354 355 356 357
358 public function getControllersDir(){
359 return $this->getRootDir() . 'controllers/';
360 }
361
362
363 364 365 366 367
368 public function getLangDir(){
369 return $this->getRootDir() . 'lang/';
370 }
371
372 373 374 375 376
377 public function getModelsDir(){
378 return $this->getRootDir() . 'models/';
379 }
380
381
382 383 384 385 386
387 public function getWidgetsDir(){
388 return $this->getRootDir() . 'widgets/';
389 }
390
391
392 393 394 395 396
397 public function getViewsDir(){
398 return $this->getRootDir() . 'views/';
399 }
400
401
402 403 404 405 406 407 408
409 public function getView($view){
410
411 $file= Theme::getSelected()->getView('plugins/' . $this->name . '/' . $view);
412 if(is_file($file)) {
413
414 return $file;
415 }
416
417
418 return $this->getViewsDir() . $view;
419 }
420
421
422 423 424 425 426
427 public function getStaticDir(){
428 return $this->getRootDir() . 'static/';
429 }
430
431 432 433 434 435
436 public function getPublicStaticDir(){
437 return STATIC_PLUGINS_DIR . $this->name . '/';
438 }
439
440
441 442 443 444 445 446 447
448 public function getStaticUrl($basename = ''){
449 $baseUrl = PLUGINS_ROOT_URL . $this->name . '/';
450 if(empty($basename)) {
451 return $baseUrl;
452 }
453 else{
454 $privateFilename = $this->getStaticDir() . $basename;
455 $publicFilename = $this->getPublicStaticDir() . $basename;
456
457 if(is_file($privateFilename) && (!is_file($publicFilename) || filemtime($privateFilename) > filemtime($publicFilename))) {
458 if(!is_dir(dirname($publicFilename))) {
459 mkdir(dirname($publicFilename), 0755, true);
460 }
461
462 copy($privateFilename, $publicFilename);
463 }
464
465 return $baseUrl . $basename . '?' . filemtime($publicFilename);
466 }
467 }
468
469
470
471
472
473 474 475 476 477
478 public function getJsDir(){
479 return $this->getStaticDir() . 'js/';
480 }
481
482
483 484 485 486 487
488 public function getPublicJsDir(){
489 return $this->getPublicStaticDir() . 'js/';
490 }
491
492 493 494 495 496 497 498 499
500 public function getJsUrl($basename = ''){
501 if(empty($basename)) {
502 return $this->getStaticUrl() . 'js/';
503 }
504 else{
505 return $this->getStaticUrl('js/' . $basename);
506 }
507 }
508
509
510
511
512
513
514 515 516 517 518
519 public function getLessDir(){
520 return $this->getStaticDir() . 'less/';
521 }
522
523
524 525 526 527 528
529 public function getPublicCssDir(){
530 return $this->getPublicStaticDir() . 'css/';
531 }
532
533
534 535 536 537 538 539 540 541
542 public function getCssUrl($basename = ""){
543 $cssUrl = $this->getStaticUrl() . 'css/';
544 if(empty($basename)) {
545 return $cssUrl;
546 }
547 else{
548 $privateFilename = $this->getLessDir() . $basename;
549 $cssBasename = preg_replace('/\.less$/', '.css', $basename);
550 $publicFilename = $this->getPublicCssDir() . $cssBasename;
551
552 if(is_file($privateFilename)) {
553
554 Event::on(
555 'built-less', function (Event $event) use ($privateFilename) {
556 if($event->getData('source') === $privateFilename) {
557
558 foreach(glob($this->getStaticDir() . '*') as $elt){
559 if(! in_array(basename($elt), array('less', 'js'))) {
560 App::fs()->copy($elt, $this->getPublicStaticDir());
561 }
562 }
563 }
564 }
565 );
566
567 Less::compile($privateFilename, $publicFilename);
568 }
569
570 return $cssUrl . $cssBasename . '?' . filemtime($publicFilename);
571 }
572 }
573
574
575
576
577 578 579 580 581
582 public function getUserfilesDir(){
583 return USERFILES_PLUGINS_DIR . $this->name . '/';
584 }
585
586
587 588 589 590 591
592 public function getPublicUserfilesDir(){
593 return $this->getPublicStaticDir() . 'userfiles/';
594 }
595
596
597 598 599 600 601 602 603 604
605 public function getUserfilesUrl($basename = ''){
606 $baseUrl = $this->getStaticUrl() . 'userfiles/';
607 if(empty($basename)) {
608 return $baseUrl;
609 }
610 else{
611 return $baseUrl . $basename . '?' . filemtime($this->getPublicUserfilesDir() . $basename);
612 }
613 }
614
615
616 617 618 619 620
621 public function isInstalled(){
622 return (bool) App::db()->count(DB::getFullTablename(self::TABLE), 'name = :name', array('name' => $this->name));
623 }
624
625
626 627 628 629 630
631 public static function getNamespaceByName($name){
632 $namespace = preg_replace_callback(
633 '/(^|\W|_)(\w?)/', function ($m) {
634 return strtoupper($m[2]);
635 }, $name
636 );
637
638 return 'Hawk\\Plugins\\' . $namespace;
639 }
640
641
642 643 644 645 646
647 public function getNamespace(){
648 return self::getNamespaceByName($this->name);
649 }
650
651 652 653 654 655
656 public function getInstallerInstance(){
657 if(isset($this->manager)) {
658 return $this->manager;
659 }
660
661 $class = '\\' . $this->getNamespace() . '\\Installer';
662 if(!empty($class)) {
663 $this->manager = new $class($this);
664 return $this->manager;
665 }
666 else{
667 return null;
668 }
669 }
670
671 672 673
674 public function install(){
675 App::db()->insert(
676 DB::getFullTablename(self::TABLE), array(
677 'name' => $this->name,
678 'active' => 0
679 ), 'IGNORE'
680 );
681
682 try{
683 $this->getInstallerInstance()->install();
684 App::logger()->notice('The plugin ' . $this->name . ' has been installed');
685 }
686 catch(\Exception $e){
687 App::db()->delete(DB::getFullTablename(self::TABLE), new DBExample(array('name' => $this->name)));
688
689 App::logger()->error('En error occured while installing plugin ' . $this->name . ' : ' . $e->getMessage());
690 throw $e;
691 }
692 }
693
694
695 696 697
698 public function uninstall(){
699 App::db()->delete(DB::getFullTablename(self::TABLE), new DBExample(array('name' => $this->name)));
700
701 try{
702 $this->getInstallerInstance()->uninstall();
703 App::logger()->notice('The plugin ' . $this->name . ' has been uninstalled');
704 }
705 catch(\Exception $e){
706 App::db()->insert(
707 DB::getFullTablename(self::TABLE), array(
708 'name' => $this->name,
709 'active' => 0
710 ), 'IGNORE'
711 );
712
713 App::logger()->error('En error occured while uninstalling plugin ' . $this->name . ' : ' . $e->getMessage());
714 throw $e;
715 }
716 }
717
718 719 720 721 722
723 public function isActive(){
724 return $this->active;
725 }
726
727
728 729 730
731 public function activate(){
732
733 $this->active = 1;
734 App::db()->update(DB::getFullTablename(self::TABLE), new DBExample(array('name' => $this->name)), array('active' => 1));
735
736 try{
737 $this->getInstallerInstance()->activate();
738 App::logger()->notice('The plugin ' . $this->name . ' has been activated');
739 }
740 catch(\Exception $e){
741 App::db()->update(DB::getFullTablename(self::TABLE), new DBExample(array('name' => $this->name)), array('active' => 0));
742
743 App::logger()->error('En error occured while activating plugin ' . $this->name . ' : ' . $e->getMessage());
744 throw $e;
745 }
746 }
747
748
749 750 751
752 public function deactivate(){
753
754 $this->active = 0;
755 App::db()->update(DB::getFullTablename(self::TABLE), new DBExample(array('name' => $this->name)), array('active' => 0));
756
757 try{
758 $this->getInstallerInstance()->deactivate();
759 App::logger()->notice('The plugin ' . $this->name . ' has been deactivated');
760 }
761 catch(\Exception $e){
762 App::db()->update(DB::getFullTablename(self::TABLE), new DBExample(array('name' => $this->name)), array('active' => 1));
763
764 App::logger()->error('En error occured while deactivating plugin ' . $this->name . ' : ' . $e->getMessage());
765 throw $e;
766 }
767 }
768
769
770 771 772 773 774
775 public function update($version){
776 $updater = $this->getInstallerInstance();
777
778 $method = 'v' . str_replace('.', '_', $version);
779 if(method_exists($updater, $method)) {
780 $updater->$method();
781 }
782 }
783
784 785 786
787 public function delete(){
788 if($this->removable) {
789 $directory = $this->getRootDir();
790
791 App::fs()->remove($directory);
792 }
793 }
794 }
795