Completed
Push — master ( 791f1f...546eff )
by Thierry
01:43
created

Manager::setTemplateCacheDir()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Manager.php - Jaxon plugin manager
5
 *
6
 * Register Jaxon plugins, generate corresponding code, handle request
7
 * and redirect them to the right plugin.
8
 *
9
 * @package jaxon-core
10
 * @author Jared White
11
 * @author J. Max Wilson
12
 * @author Joseph Woolley
13
 * @author Steffen Konerow
14
 * @author Thierry Feuzeu <[email protected]>
15
 * @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
16
 * @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
17
 * @copyright 2016 Thierry Feuzeu <[email protected]>
18
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
19
 * @link https://github.com/jaxon-php/jaxon-core
20
 */
21
22
namespace Jaxon\Plugin;
23
24
use Jaxon\Jaxon;
25
use Jaxon\Plugin\Package;
26
use RecursiveDirectoryIterator;
27
use RecursiveIteratorIterator;
28
use RegexIterator;
29
use RecursiveRegexIterator;
30
use Closure;
31
32
class Manager
33
{
34
    use \Jaxon\Utils\Traits\Manager;
35
    use \Jaxon\Utils\Traits\Config;
36
    use \Jaxon\Utils\Traits\Cache;
37
    use \Jaxon\Utils\Traits\Event;
38
    use \Jaxon\Utils\Traits\Minifier;
39
    use \Jaxon\Utils\Traits\Template;
40
    use \Jaxon\Utils\Traits\Translator;
41
42
    /**
43
     * The response type.
44
     *
45
     * @var string
46
     */
47
    const RESPONSE_TYPE = 'JSON';
48
49
    /**
50
     * All plugins, indexed by priority
51
     *
52
     * @var array
53
     */
54
    private $aPlugins = [];
55
56
    /**
57
     * Request plugins, indexed by name
58
     *
59
     * @var array
60
     */
61
    private $aRequestPlugins = [];
62
63
    /**
64
     * Response plugins, indexed by name
65
     *
66
     * @var array
67
     */
68
    private $aResponsePlugins = [];
69
70
    /**
71
     * An array of package names
72
     *
73
     * @var array
74
     */
75
    private $aPackages = [];
76
77
    /**
78
     * Javascript confirm function
79
     *
80
     * @var \Jaxon\Request\Interfaces\Confirm
81
     */
82
    private $xConfirm;
83
84
    /**
85
     * Default javascript confirm function
86
     *
87
     * @var \Jaxon\Request\Support\Confirm
88
     */
89
    private $xDefaultConfirm;
90
91
    /**
92
     * Javascript alert function
93
     *
94
     * @var \Jaxon\Request\Interfaces\Alert
95
     */
96
    private $xAlert;
97
98
    /**
99
     * Default javascript alert function
100
     *
101
     * @var \Jaxon\Request\Support\Alert
102
     */
103
    private $xDefaultAlert;
104
105
    /**
106
     * Initialize the Jaxon Plugin Manager
107
     */
108
    public function __construct()
109
    {
110
        // Javascript confirm function
111
        $this->xConfirm = null;
112
        $this->xDefaultConfirm = new \Jaxon\Request\Support\Confirm();
113
114
        // Javascript alert function
115
        $this->xAlert = null;
116
        $this->xDefaultAlert = new \Jaxon\Request\Support\Alert();
117
    }
118
119
    /**
120
     * Set the javascript confirm function
121
     *
122
     * @param \Jaxon\Request\Interfaces\Confirm         $xConfirm     The javascript confirm function
123
     *
124
     * @return void
125
     */
126
    public function setConfirm(\Jaxon\Request\Interfaces\Confirm $xConfirm)
127
    {
128
        $this->xConfirm = $xConfirm;
129
    }
130
131
    /**
132
     * Get the javascript confirm function
133
     *
134
     * @return \Jaxon\Request\Interfaces\Confirm
135
     */
136
    public function getConfirm()
137
    {
138
        return (($this->xConfirm) ? $this->xConfirm : $this->xDefaultConfirm);
139
    }
140
141
    /**
142
     * Get the default javascript confirm function
143
     *
144
     * @return \Jaxon\Request\Support\Confirm
145
     */
146
    public function getDefaultConfirm()
147
    {
148
        return $this->xDefaultConfirm;
149
    }
150
151
    /**
152
     * Set the javascript alert function
153
     *
154
     * @param \Jaxon\Request\Interfaces\Alert           $xAlert       The javascript alert function
155
     *
156
     * @return void
157
     */
158
    public function setAlert(\Jaxon\Request\Interfaces\Alert $xAlert)
159
    {
160
        $this->xAlert = $xAlert;
161
    }
162
163
    /**
164
     * Get the javascript alert function
165
     *
166
     * @return \Jaxon\Request\Interfaces\Alert
167
     */
168
    public function getAlert()
169
    {
170
        return (($this->xAlert) ? $this->xAlert : $this->xDefaultAlert);
171
    }
172
173
    /**
174
     * Get the default javascript alert function
175
     *
176
     * @return \Jaxon\Request\Support\Alert
177
     */
178
    public function getDefaultAlert()
179
    {
180
        return $this->xDefaultAlert;
181
    }
182
183
    /**
184
     * Inserts an entry into an array given the specified priority number
185
     *
186
     * If a plugin already exists with the given priority, the priority is automatically incremented until a free spot is found.
187
     * The plugin is then inserted into the empty spot in the array.
188
     *
189
     * @param Plugin         $xPlugin               An instance of a plugin
190
     * @param integer        $nPriority             The desired priority, used to order the plugins
191
     *
192
     * @return void
193
     */
194
    private function setPluginPriority(Plugin $xPlugin, $nPriority)
195
    {
196
        while (isset($this->aPlugins[$nPriority]))
197
        {
198
            $nPriority++;
199
        }
200
        $this->aPlugins[$nPriority] = $xPlugin;
201
        // Sort the array by ascending keys
202
        ksort($this->aPlugins);
203
    }
204
205
    /**
206
     * Register a plugin
207
     *
208
     * Below is a table for priorities and their description:
209
     * - 0 thru 999: Plugins that are part of or extensions to the jaxon core
210
     * - 1000 thru 8999: User created plugins, typically, these plugins don't care about order
211
     * - 9000 thru 9999: Plugins that generally need to be last or near the end of the plugin list
212
     *
213
     * @param Plugin         $xPlugin               An instance of a plugin
214
     * @param integer        $nPriority             The plugin priority, used to order the plugins
215
     *
216
     * @return void
217
     */
218
    public function registerPlugin(Plugin $xPlugin, $nPriority = 1000)
219
    {
220
        $bIsAlert = ($xPlugin instanceof \Jaxon\Request\Interfaces\Alert);
221
        $bIsConfirm = ($xPlugin instanceof \Jaxon\Request\Interfaces\Confirm);
222
        if($xPlugin instanceof Request)
223
        {
224
            // The name of a request plugin is used as key in the plugin table
225
            $this->aRequestPlugins[$xPlugin->getName()] = $xPlugin;
226
        }
227
        elseif($xPlugin instanceof Response)
228
        {
229
            // The name of a response plugin is used as key in the plugin table
230
            $this->aResponsePlugins[$xPlugin->getName()] = $xPlugin;
231
        }
232
        elseif(!$bIsConfirm && !$bIsAlert)
233
        {
234
            throw new \Jaxon\Exception\Error($this->trans('errors.register.invalid', array('name' => get_class($xPlugin))));
235
        }
236
        // This plugin implements the Alert interface
237
        if($bIsAlert)
238
        {
239
            $this->setAlert($xPlugin);
240
        }
241
        // This plugin implements the Confirm interface
242
        if($bIsConfirm)
243
        {
244
            $this->setConfirm($xPlugin);
245
        }
246
        // Register the plugin as an event listener
247
        if($xPlugin instanceof \Jaxon\Utils\Interfaces\EventListener)
248
        {
249
            $this->addEventListener($xPlugin);
250
        }
251
252
        $this->setPluginPriority($xPlugin, $nPriority);
253
    }
254
255
    /**
256
     * Register a package
257
     *
258
     * @param string         $sPackageClass         The package class name
259
     * @param Closure        $xClosure              A closure to create package instance
260
     *
261
     * @return void
262
     */
263
    public function registerPackage(string $sPackageClass, Closure $xClosure)
264
    {
265
        $this->aPackages[] = $sPackageClass;
266
        jaxon()->di()->set($sPackageClass, $xClosure);
267
    }
268
269
    /**
270
     * Generate a hash for all the javascript code generated by the library
271
     *
272
     * @return string
273
     */
274
    private function generateHash()
275
    {
276
        $sHash = $this->getVersion();
277
        foreach($this->aPlugins as $xPlugin)
278
        {
279
            $sHash .= $xPlugin->generateHash();
280
        }
281
        return md5($sHash);
282
    }
283
284
    /**
285
     * Check if the current request can be processed
286
     *
287
     * Calls each of the request plugins and determines if the current request can be processed by one of them.
288
     * If no processor identifies the current request, then the request must be for the initial page load.
289
     *
290
     * @return boolean
291
     */
292
    public function canProcessRequest()
293
    {
294
        foreach($this->aRequestPlugins as $xPlugin)
295
        {
296
            if($xPlugin->getName() != Jaxon::FILE_UPLOAD && $xPlugin->canProcessRequest())
297
            {
298
                return true;
299
            }
300
        }
301
        return false;
302
    }
303
304
    /**
305
     * Process the current request
306
     *
307
     * Calls each of the request plugins to request that they process the current request.
308
     * If any plugin processes the request, it will return true.
309
     *
310
     * @return boolean
311
     */
312
    public function processRequest()
313
    {
314
        $xUploadPlugin = $this->getRequestPlugin(Jaxon::FILE_UPLOAD);
315
        foreach($this->aRequestPlugins as $xPlugin)
316
        {
317
            if($xPlugin->getName() != Jaxon::FILE_UPLOAD && $xPlugin->canProcessRequest())
318
            {
319
                // Process uploaded files
320
                if($xUploadPlugin != null)
321
                {
322
                    $xUploadPlugin->processRequest();
323
                }
324
                // Process the request
325
                return $xPlugin->processRequest();
326
            }
327
        }
328
        // Todo: throw an exception
329
        return false;
330
    }
331
332
    /**
333
     * Register a function, event or callable object
334
     *
335
     * Call each of the request plugins and give them the opportunity to handle the
336
     * registration of the specified function, event or callable object.
337
     *
338
     * @param array         $aArgs                The registration data
339
     *
340
     * @return mixed
341
     */
342
    public function register($aArgs)
343
    {
344
        foreach($this->aRequestPlugins as $xPlugin)
345
        {
346
            $mResult = $xPlugin->register($aArgs);
347
            if($mResult instanceof \Jaxon\Request\Request || is_array($mResult) || $mResult === true)
348
            {
349
                return $mResult;
350
            }
351
        }
352
        throw new \Jaxon\Exception\Error($this->trans('errors.register.method', array('args' => print_r($aArgs, true))));
353
    }
354
355
    /**
356
     * Get the base URI of the Jaxon library javascript files
357
     *
358
     * @return string
359
     */
360
    private function getJsLibUri()
361
    {
362
        if(!$this->hasOption('js.lib.uri'))
363
        {
364
            // return 'https://cdn.jsdelivr.net/jaxon/1.2.0/';
365
            return 'https://cdn.jsdelivr.net/gh/jaxon-php/[email protected]/dist/';
366
        }
367
        // Todo: check the validity of the URI
368
        return rtrim($this->getOption('js.lib.uri'), '/') . '/';
369
    }
370
371
    /**
372
     * Get the extension of the Jaxon library javascript files
373
     *
374
     * The returned string is '.min.js' if the files are minified.
375
     *
376
     * @return string
377
     */
378
    private function getJsLibExt()
379
    {
380
        // $jsDelivrUri = 'https://cdn.jsdelivr.net';
381
        // $nLen = strlen($jsDelivrUri);
382
        // The jsDelivr CDN only hosts minified files
383
        // if(($this->getOption('js.app.minify')) || substr($this->getJsLibUri(), 0, $nLen) == $jsDelivrUri)
384
        // Starting from version 2.0.0 of the js lib, the jsDelivr CDN also hosts non minified files.
385
        if(($this->getOption('js.app.minify')))
386
        {
387
            return '.min.js';
388
        }
389
        return '.js';
390
    }
391
392
    /**
393
     * Check if the javascript code generated by Jaxon can be exported to an external file
394
     *
395
     * @return boolean
396
     */
397
    public function canExportJavascript()
398
    {
399
        // Check config options
400
        // - The js.app.extern option must be set to true
401
        // - The js.app.uri and js.app.dir options must be set to non null values
402
        if(!$this->getOption('js.app.extern') ||
403
            !$this->getOption('js.app.uri') ||
404
            !$this->getOption('js.app.dir'))
405
        {
406
            return false;
407
        }
408
        // Check dir access
409
        // - The js.app.dir must be writable
410
        $sJsAppDir = $this->getOption('js.app.dir');
411
        if(!is_dir($sJsAppDir) || !is_writable($sJsAppDir))
412
        {
413
            return false;
414
        }
415
        return true;
416
    }
417
418
    /**
419
     * Set the cache directory for the template engine
420
     *
421
     * @return void
422
     */
423
    private function setTemplateCacheDir()
424
    {
425
        if($this->hasOption('core.template.cache_dir'))
426
        {
427
            $this->setCacheDir($this->getOption('core.template.cache_dir'));
428
        }
429
    }
430
431
    /**
432
     * Get the HTML tags to include Jaxon javascript files into the page
433
     *
434
     * @return string
435
     */
436
    public function getJs()
437
    {
438
        $sJsLibUri = $this->getJsLibUri();
439
        $sJsLibExt = $this->getJsLibExt();
440
        $sJsCoreUrl = $sJsLibUri . 'jaxon.core' . $sJsLibExt;
441
        $sJsDebugUrl = $sJsLibUri . 'jaxon.debug' . $sJsLibExt;
442
        // $sJsVerboseUrl = $sJsLibUri . 'jaxon.verbose' . $sJsLibExt;
443
        $sJsLanguageUrl = $sJsLibUri . 'lang/jaxon.' . $this->getOption('core.language') . $sJsLibExt;
444
445
        // Add component files to the javascript file array;
446
        $aJsFiles = array($sJsCoreUrl);
447
        if($this->getOption('core.debug.on'))
448
        {
449
            $aJsFiles[] = $sJsDebugUrl;
450
            $aJsFiles[] = $sJsLanguageUrl;
451
            /*if($this->getOption('core.debug.verbose'))
452
            {
453
                $aJsFiles[] = $sJsVerboseUrl;
454
            }*/
455
        }
456
457
        // Set the template engine cache dir
458
        $this->setTemplateCacheDir();
459
        $sCode = $this->render('jaxon::plugins/includes.js', array(
460
            'sJsOptions' => $this->getOption('js.app.options'),
461
            'aUrls' => $aJsFiles,
462
        ));
463
        foreach($this->aResponsePlugins as $xPlugin)
464
        {
465
            if(($str = trim($xPlugin->getJs())))
466
            {
467
                $sCode .= rtrim($str, " \n") . "\n";
468
            }
469
        }
470
        foreach($this->aPackages as $sClass)
471
        {
472
            $xPackage = jaxon()->di()->get($sClass);
473
            if(($str = trim($xPackage->js())))
474
            {
475
                $sCode .= rtrim($str, " \n") . "\n";
476
            }
477
        }
478
        return $sCode;
479
    }
480
481
    /**
482
     * Get the HTML tags to include Jaxon CSS code and files into the page
483
     *
484
     * @return string
485
     */
486 View Code Duplication
    public function getCss()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
487
    {
488
        // Set the template engine cache dir
489
        $this->setTemplateCacheDir();
490
491
        $sCode = '';
492
        foreach($this->aResponsePlugins as $xPlugin)
493
        {
494
            if(($str = trim($xPlugin->getCss())))
495
            {
496
                $sCode .= rtrim($str, " \n") . "\n";
497
            }
498
        }
499
        foreach($this->aPackages as $sClass)
500
        {
501
            $xPackage = jaxon()->di()->get($sClass);
502
            if(($str = trim($xPackage->css())))
503
            {
504
                $sCode .= rtrim($str, " \n") . "\n";
505
            }
506
        }
507
        return $sCode;
508
    }
509
510
    /**
511
     * Get the correspondances between previous and current config options
512
     *
513
     * They are used to keep the deprecated config options working.
514
     * They will be removed when the deprecated options will lot be supported anymore.
515
     *
516
     * @return array
517
     */
518
    private function getOptionVars()
519
    {
520
        return array(
521
            'sResponseType'             => self::RESPONSE_TYPE,
522
            'sVersion'                  => $this->getOption('core.version'),
523
            'sLanguage'                 => $this->getOption('core.language'),
524
            'bLanguage'                 => $this->hasOption('core.language') ? true : false,
525
            'sRequestURI'               => $this->getOption('core.request.uri'),
526
            'sDefaultMode'              => $this->getOption('core.request.mode'),
527
            'sDefaultMethod'            => $this->getOption('core.request.method'),
528
            'sCsrfMetaName'             => $this->getOption('core.request.csrf_meta'),
529
            'bDebug'                    => $this->getOption('core.debug.on'),
530
            'bVerboseDebug'             => $this->getOption('core.debug.verbose'),
531
            'sDebugOutputID'            => $this->getOption('core.debug.output_id'),
532
            'nResponseQueueSize'        => $this->getOption('js.lib.queue_size'),
533
            'sStatusMessages'           => $this->getOption('js.lib.show_status') ? 'true' : 'false',
534
            'sWaitCursor'               => $this->getOption('js.lib.show_cursor') ? 'true' : 'false',
535
            'sDefer'                    => $this->getOption('js.app.options'),
536
        );
537
    }
538
539
    /**
540
     * Get the javascript code for Jaxon client side configuration
541
     *
542
     * @return string
543
     */
544
    private function getConfigScript()
545
    {
546
        $aVars = $this->getOptionVars();
547
        $sYesScript = 'jaxon.ajax.response.process(command.response)';
548
        $sNoScript = 'jaxon.confirm.skip(command);jaxon.ajax.response.process(command.response)';
549
        $sConfirmScript = $this->getConfirm()->confirm('msg', $sYesScript, $sNoScript);
550
        $aVars['sConfirmScript'] = $this->render('jaxon::plugins/confirm.js', array('sConfirmScript' => $sConfirmScript));
551
552
        return $this->render('jaxon::plugins/config.js', $aVars);
553
    }
554
555
    /**
556
     * Get the javascript code to be run after page load
557
     *
558
     * Also call each of the response plugins giving them the opportunity
559
     * to output some javascript to the page being generated.
560
     *
561
     * @return string
562
     */
563 View Code Duplication
    private function getReadyScript()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
564
    {
565
        $sPluginScript = '';
566
        foreach($this->aResponsePlugins as $xPlugin)
567
        {
568
            if(($str = trim($xPlugin->getScript())))
569
            {
570
                $sPluginScript .= "\n" . trim($str, " \n");
571
            }
572
        }
573
        foreach($this->aPackages as $sClass)
574
        {
575
            $xPackage = jaxon()->di()->get($sClass);
576
            if(($str = trim($xPackage->ready())))
577
            {
578
                $sPluginScript .= "\n" . trim($str, " \n");
579
            }
580
        }
581
582
        return $this->render('jaxon::plugins/ready.js', ['sPluginScript' => $sPluginScript]);
583
    }
584
585
    /**
586
     * Get the javascript code to be sent to the browser
587
     *
588
     * Also call each of the request plugins giving them the opportunity
589
     * to output some javascript to the page being generated.
590
     * This is called only when the page is being loaded initially.
591
     * This is not called when processing a request.
592
     *
593
     * @return string
594
     */
595
    private function getAllScripts()
596
    {
597
        // Get the config and plugins scripts
598
        $sScript = $this->getConfigScript() . "\n" . $this->getReadyScript() . "\n";
599
        foreach($this->aRequestPlugins as $xPlugin)
600
        {
601
            $sScript .= "\n" . trim($xPlugin->getScript(), " \n");
602
        }
603
        return $sScript;
604
    }
605
606
    /**
607
     * Get the javascript code to be sent to the browser
608
     *
609
     * Also call each of the request plugins giving them the opportunity
610
     * to output some javascript to the page being generated.
611
     * This is called only when the page is being loaded initially.
612
     * This is not called when processing a request.
613
     *
614
     * @return string
615
     */
616
    public function getScript()
617
    {
618
        // Set the template engine cache dir
619
        $this->setTemplateCacheDir();
620
621
        if($this->canExportJavascript())
622
        {
623
            $sJsAppURI = rtrim($this->getOption('js.app.uri'), '/') . '/';
624
            $sJsAppDir = rtrim($this->getOption('js.app.dir'), '/') . '/';
625
626
            // The plugins scripts are written into the javascript app dir
627
            $sHash = $this->generateHash();
628
            $sOutFile = $sHash . '.js';
629
            $sMinFile = $sHash . '.min.js';
630
            if(!is_file($sJsAppDir . $sOutFile))
631
            {
632
                file_put_contents($sJsAppDir . $sOutFile, $this->getAllScripts());
633
            }
634
            if(($this->getOption('js.app.minify')) && !is_file($sJsAppDir . $sMinFile))
635
            {
636
                if(($this->minify($sJsAppDir . $sOutFile, $sJsAppDir . $sMinFile)))
637
                {
638
                    $sOutFile = $sMinFile;
639
                }
640
            }
641
642
            // The returned code loads the generated javascript file
643
            $sScript = $this->render('jaxon::plugins/include.js', array(
644
                'sJsOptions' => $this->getOption('js.app.options'),
645
                'sUrl' => $sJsAppURI . $sOutFile,
646
            ));
647
        }
648
        else
649
        {
650
            // The plugins scripts are wrapped with javascript tags
651
            $sScript = $this->render('jaxon::plugins/wrapper.js', array(
652
                'sJsOptions' => $this->getOption('js.app.options'),
653
                'sScript' => $this->getAllScripts(),
654
            ));
655
        }
656
657
        return $sScript;
658
    }
659
660
    /**
661
     * Find the specified response plugin by name and return a reference to it if one exists
662
     *
663
     * @param string        $sName                The name of the plugin
664
     *
665
     * @return \Jaxon\Plugin\Response
666
     */
667
    public function getResponsePlugin($sName)
668
    {
669
        if(array_key_exists($sName, $this->aResponsePlugins))
670
        {
671
            return $this->aResponsePlugins[$sName];
672
        }
673
        return null;
674
    }
675
676
    /**
677
     * Find the specified request plugin by name and return a reference to it if one exists
678
     *
679
     * @param string        $sName                The name of the plugin
680
     *
681
     * @return \Jaxon\Plugin\Request
682
     */
683
    public function getRequestPlugin($sName)
684
    {
685
        if(array_key_exists($sName, $this->aRequestPlugins))
686
        {
687
            return $this->aRequestPlugins[$sName];
688
        }
689
        return null;
690
    }
691
}
692