Completed
Push — master ( 0eb774...254a9f )
by Romans
02:34 queued 02:29
created

App_Web::execute()   C

Complexity

Conditions 7
Paths 17

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 33
rs 6.7272
cc 7
eloc 22
nc 17
nop 0
1
<?php
2
/**
3
 * App_Web extends an APP of CommandLine applications with knowledge of HTML
4
 * templates, understanding of pages and routing.
5
*/
6
class App_Web extends App_CLI
7
{
8
    /**
9
     * Cleaned up name of the currently requested page.
10
     */
11
    public $page = null;
12
13
    /**
14
     * Root page where URL will send when ('/') is encountered.
15
     *
16
     * @todo: make this work properly
17
     */
18
    public $index_page = 'index';
19
20
    /**
21
     * Recorded time when execution has started.
22
     */
23
    public $start_time = null;
24
25
    /**
26
     * Skin for web application templates.
27
     */
28
    public $skin;
29
30
    /**
31
     * Set a title of your application, will appear in <title> tag.
32
     */
33
    public $title;
34
35
    /**
36
     * Authentication object
37
     *
38
     * @see Auth_Basic::init()
39
     * @var Auth_Basic
40
     */
41
    public $auth;
42
43
    /**
44
     * jQuery object. Initializes only if you add jQuery in your app.
45
     *
46
     * @see jQuery::init()
47
     * @var jQuery
48
     */
49
    public $jquery;
50
51
    /** @var App_Web */
52
    public $app;
53
    /** @var array For internal use */
54
    protected $included;
55
    /** @var array For internal use */
56
    protected $rendered;
57
58
59
60
    // {{{ Start-up
61
    public function __construct($realm = null, $skin = 'default', $options = array())
62
    {
63
        $this->start_time = time() + microtime();
64
65
        $this->skin = $skin;
66
        try {
67
            parent::__construct($realm, $options);
68
        } catch (Exception $e) {
69
            // This exception is used to abort initialisation of the objects,
70
            // but when normal rendering is still required
71
            if ($e instanceof Exception_Stop) {
72
                return;
73
            }
74
75
            $this->caughtException($e);
76
        }
77
    }
78
79
    /**
80
     * Executed before init, this method will initialize PageManager and
81
     * pathfinder.
82
     */
83
    public function _beforeInit()
84
    {
85
        $this->pm = $this->add($this->pagemanager_class, $this->pagemanager_options);
86
        $this->pm->parseRequestedURL();
0 ignored issues
show
Documentation Bug introduced by
The method parseRequestedURL does not exist on object<AbstractObject>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
87
        parent::_beforeInit();
88
    }
89
90
    /**
91
     * Redefine this function instead of default constructor.
92
     */
93
    public function init()
94
    {
95
        $this->getLogger();
96
97
        // Verify Licensing
98
        //$this->licenseCheck('atk4');
99
100
        // send headers, no caching
101
        $this->sendHeaders();
102
103
        $this->cleanMagicQuotes();
104
105
        parent::init();
106
107
        // in addition to default initialization, set up logger and template
108
        $this->initializeTemplate();
109
110
        if (get_class($this) == 'App_Web') {
111
            $this->setConfig(array('url_postfix' => '.php', 'url_prefix' => ''));
112
        }
113
    }
114
115
    /**
116
     * Magic Quotes were a design error. Let's strip them if they are enabled.
117
     */
118
    public function cleanMagicQuotes()
119
    {
120
        if (!function_exists('stripslashes_array')) {
121
            function stripslashes_array(&$array, $iterations = 0)
122
            {
123
                if ($iterations < 3) {
124
                    foreach ($array as $key => $value) {
125
                        if (is_array($value)) {
126
                            stripslashes_array($array[$key], $iterations + 1);
127
                        } else {
128
                            $array[$key] = stripslashes($array[$key]);
129
                        }
130
                    }
131
                }
132
            }
133
        }
134
135
        if (get_magic_quotes_gpc()) {
136
            stripslashes_array($_GET);
137
            stripslashes_array($_POST);
138
            stripslashes_array($_COOKIE);
139
        }
140
    }
141
142
    /**
143
     * Sends default headers. Re-define to send your own headers.
144
     */
145
    public function sendHeaders()
146
    {
147
        header('Content-Type: text/html; charset=utf-8');
148
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');               // Date in the past
149
        header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');  // always modified
150
        header('Cache-Control: no-store, no-cache, must-revalidate');   // HTTP/1.1
151
        header('Cache-Control: post-check=0, pre-check=0', false);
152
        header('Pragma: no-cache');                                     // HTTP/1.0
153
    }
154
155
    /**
156
     * Call this method if you want to see execution time on the bottom of your pages.
157
     */
158
    public function showExecutionTime()
159
    {
160
        $this->addHook('post-render-output', array($this, '_showExecutionTime'));
161
        $this->addHook('post-js-execute', array($this, '_showExecutionTimeJS'));
162
    }
163
164
    /** @ignore */
165
    public function _showExecutionTime()
166
    {
167
        echo 'Took '.(time() + microtime() - $this->start_time).'s';
168
    }
169
170
    /** @ignore */
171
    public function _showExecutionTimeJS()
172
    {
173
        echo "\n\n/* Took ".number_format(time() + microtime() - $this->start_time, 5).'s */';
174
    }
175
    // }}}
176
177
    // {{{ Obsolete
178
    /**
179
     * This method is called when exception was caught in the application.
180
     *
181
     * @param Exception $e
182
     */
183
    public function caughtException($e)
184
    {
185
        $this->hook('caught-exception', array($e));
186
        throw $e;
187
        echo '<span style="color:red">Problem with your request.</span>';
0 ignored issues
show
Unused Code introduced by
echo '<span style="color... your request.</span>'; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
188
        echo "<p>Please use 'Logger' class for more sophisticated output<br>\$app-&gt;add('Logger');</p>";
189
        exit;
190
    }
191
192
    /**
193
     * @todo Description
194
     *
195
     * @param string $msg
196
     * @param int    $shift
197
     *
198
     * @return bool|void
199
     */
200 View Code Duplication
    public function outputWarning($msg, $shift = 0)
201
    {
202
        if ($this->hook('output-warning', array($msg, $shift))) {
203
            return true;
204
        }
205
        echo '<span style="color:red">', $msg, '</span>';
206
    }
207
208
    /**
209
     * @todo Description
210
     *
211
     * @param string $msg
212
     * @param int    $shift
213
     *
214
     * @return bool|void
215
     */
216 View Code Duplication
    public function outputDebug($msg, $shift = 0)
217
    {
218
        if ($this->hook('output-debug', array($msg, $shift))) {
219
            return true;
220
        }
221
        echo '<span style="color:blue">', $msg, '</font><br />';
222
    }
223
224
    // }}}
225
226
    // {{{ Sessions
227
    /**
228
     * Initializes existing or new session.
229
     *
230
     * Attempts to re-initialize session. If session is not found,
231
     * new one will be created, unless $create is set to false. Avoiding
232
     * session creation and placing cookies is to enhance user privacy.
233
     * Call to memorize() / recall() will automatically create session
234
     *
235
     * @param bool $create
236
     */
237
    public $_is_session_initialized = false;
238
    public function initializeSession($create = true)
239
    {
240
        if ($this->_is_session_initialized || session_id()) {
241
            return;
242
        }
243
244
        // Change settings if defined in settings file
245
        $params = session_get_cookie_params();
246
247
        $params['httponly'] = true;   // true by default
248
249
        foreach ($params as $key => $default) {
250
            $params[$key] = $this->app->getConfig('session/'.$key, $default);
251
        }
252
253
        if ($create === false && !isset($_COOKIE[$this->name])) {
254
            return;
255
        }
256
        $this->_is_session_initialized = true;
257
        session_set_cookie_params(
258
            $params['lifetime'],
259
            $params['path'],
260
            $params['domain'],
261
            $params['secure'],
262
            $params['httponly']
263
        );
264
        session_name($this->name);
265
        session_start();
266
    }
267
268
    /**
269
     * Completely destroy existing session.
270
     */
271
    public function destroySession()
272
    {
273
        if ($this->_is_session_initialized) {
274
            $_SESSION = array();
275
            if (isset($_COOKIE[$this->name])) {
276
                setcookie($this->name/*session_name()*/, '', time() - 42000, '/');
277
            }
278
            session_destroy();
279
            $this->_is_session_initialized = false;
280
        }
281
    }
282
    // }}}
283
284
    // {{{ Sticky GET Argument implementation. Register stickyGET to have it appended to all generated URLs
285
    public $sticky_get_arguments = array();
286
    /**
287
     * Make current get argument with specified name automatically appended to all generated URLs.
288
     *
289
     * @param string $name
290
     *
291
     * @return string
292
     */
293
    public function stickyGet($name)
294
    {
295
        $this->sticky_get_arguments[$name] = @$_GET[$name];
296
297
        return $_GET[$name];
298
    }
299
300
    /**
301
     * Remove sticky GET which was set by stickyGET.
302
     *
303
     * @param string $name
304
     */
305
    public function stickyForget($name)
306
    {
307
        unset($this->sticky_get_arguments[$name]);
308
    }
309
310
    /** @ignore - used by URL class */
311
    public function getStickyArguments()
312
    {
313
        return $this->sticky_get_arguments;
314
    }
315
316
    /**
317
     * @todo Description
318
     *
319
     * @param string $name
320
     *
321
     * @return string
322
     */
323
    public function get($name)
324
    {
325
        return $_GET[$name];
326
    }
327
328
    /**
329
     * @todo Description
330
     *
331
     * @param string $file
332
     * @param string $ext
333
     * @param string $locate
334
     *
335
     * @return $this
336
     */
337 View Code Duplication
    public function addStylesheet($file, $ext = '.css', $locate = 'css')
338
    {
339
        //$file = $this->app->locateURL('css', $file . $ext);
340
        if (@$this->included[$locate.'-'.$file.$ext]++) {
341
            return;
342
        }
343
344
        if (strpos($file, 'http') !== 0 && $file[0] != '/') {
345
            $url = $this->locateURL($locate, $file.$ext);
346
        } else {
347
            $url = $file;
348
        }
349
350
        $this->template->appendHTML(
351
            'js_include',
352
            '<link type="text/css" href="'.$url.'" rel="stylesheet" />'."\n"
353
        );
354
355
        return $this;
356
    }
357
    // }}}
358
359
    // {{{ Very Important Methods
360
    /**
361
     * Call this method from your index file. It is the main method of Agile Toolkit.
362
     */
363
    public function main()
364
    {
365
        try {
366
            // Initialize page and all elements from here
367
            $this->initLayout();
368
        } catch (Exception $e) {
369
            if (!($e instanceof Exception_Stop)) {
370
                return $this->caughtException($e);
371
            }
372
            //$this->caughtException($e);
373
        }
374
375
        try {
376
            $this->hook('post-init');
377
            $this->hook('afterInit');
378
379
            $this->hook('pre-exec');
380
            $this->hook('beforeExec');
381
382
            if (isset($_GET['submit']) && $_POST) {
383
                $this->hook('submitted');
384
            }
385
386
            $this->hook('post-submit');
387
            $this->hook('afterSubmit');
388
389
            $this->execute();
390
        } catch (Exception $e) {
391
            $this->caughtException($e);
392
        }
393
        $this->hook('saveDelayedModels');
394
    }
395
396
    /**
397
     * Main execution loop.
398
     */
399
    public function execute()
400
    {
401
        $this->rendered['sub-elements'] = array();
402
        try {
403
            $this->hook('pre-render');
404
            $this->hook('beforeRender');
405
            $this->recursiveRender();
406
            if (isset($_GET['cut_object'])) {
407
                throw new BaseException("Unable to cut object with name='".$_GET['cut_object']."'. ".
408
                    "It wasn't initialized");
409
            }
410
            if (isset($_GET['cut_region'])) {
411
                // @todo Imants: This looks something obsolete. At least property cut_region_result is never defined.
412
                if (!$this->cut_region_result) {
0 ignored issues
show
Bug introduced by
The property cut_region_result does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
413
                    throw new BaseException("Unable to cut region with name='".$_GET['cut_region']."'");
414
                }
415
                echo $this->cut_region_result;
416
417
                return;
418
            }
419
        } catch (Exception $e) {
420
            if ($e instanceof Exception_Stop) {
421
                $this->hook('cut-output');
422
                if (isset($e->result)) {
423
                    echo $e->result;
424
                }
425
                $this->hook('post-render-output');
426
427
                return;
428
            }
429
            throw $e;
430
        }
431
    }
432
433
    /**
434
     * Renders all objects inside applications and echo all output to the browser.
435
     */
436
    public function render()
437
    {
438
        $this->hook('pre-js-collection');
439
        if (isset($this->app->jquery) && $this->app->jquery) {
440
            $this->app->jquery->getJS($this);
441
        }
442
443
        if (!($this->template)) {
444
            throw new BaseException('You should specify template for APP object');
445
        }
446
447
        $this->hook('pre-render-output');
448
        if (headers_sent($file, $line)) {
449
            echo "<br />Direct output (echo or print) detected on $file:$line. <a target='_blank' "
450
                ."href='http://agiletoolkit.org/error/direct_output'>Use \$this->add('Text') instead</a>.<br />";
451
        }
452
        echo $this->template->render();
453
        $this->hook('post-render-output');
454
    }
455
    // }}}
456
457
    // {{{ Miscelanious Functions
458
    /**
459
     * Render only specified object or object with specified name.
460
     *
461
     * @param mixed $object
462
     *
463
     * @return $this
464
     */
465
    public function cut($object)
466
    {
467
        $_GET['cut_object'] = is_object($object) ? $object->name : $object;
468
469
        return $this;
470
    }
471
472
    /**
473
     * Perform instant redirect to another page.
474
     *
475
     * @param string $page
476
     * @param array  $args
477
     */
478
    public function redirect($page = null, $args = array())
479
    {
480
        /*
481
         * Redirect to specified page. $args are $_GET arguments.
482
         * Use this function instead of issuing header("Location") stuff
483
         */
484
        $url = $this->url($page, $args);
485
        if ($this->app->isAjaxOutput()) {
486
            if ($_GET['cut_page']) {
487
                echo '<script>'.$this->app->js()->redirect($url).'</script>Redirecting page...';
488
                exit;
489
            } else {
490
                $this->app->js()->redirect($url)->execute();
491
            }
492
        }
493
        header('Location: '.$url);
494
        exit;
495
    }
496
497
    /**
498
     * Called on all templates in the system, populates some system-wide tags.
499
     *
500
     * @param Template $t
501
     */
502
    public function setTags($t)
503
    {
504
        // Determine Location to atk_public
505
        if ($this->app->pathfinder && $this->app->pathfinder->atk_public) {
506
            $q = $this->app->pathfinder->atk_public->getURL();
507
        } else {
508
            $q = 'http://www.agiletoolkit.org/';
509
        }
510
511
        $t->trySet('atk_path', $q.'/');
512
        $t->trySet('base_path', $q = $this->app->pm->base_path);
513
514
        // We are using new capability of SMlite to process tags individually
515
        try {
516
            $t->eachTag($tag = 'template', array($this, '_locateTemplate'));
517
            $t->eachTag($tag = 'public', array($this, '_locatePublic'));
518
            $t->eachTag($tag = 'js', array($this, '_locateJS'));
519
            $t->eachTag($tag = 'css', array($this, '_locateCSS'));
520
            $t->eachTag($tag = 'page', array($this, '_locatePage'));
521
        } catch (BaseException $e) {
522
            throw $e
523
                ->addMoreInfo('processing_tag', $tag)
524
                ->addMoreInfo('template', $t->template_file)
525
                ;
526
        }
527
528
        $this->hook('set-tags', array($t));
529
    }
530
531
    /**
532
     * Returns true if browser is going to EVAL output.
533
     *
534
     * @todo rename into isJSOutput();
535
     *
536
     * @return bool
537
     */
538
    public function isAjaxOutput()
539
    {
540
        return isset($_POST['ajax_submit']) || ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');
541
    }
542
543
    /** @private - NO PRIVATE !!! */
544
    public function _locateTemplate($path)
545
    {
546
        return $this->locateURL('public', $path);
547
    }
548
    public function _locatePublic($path)
549
    {
550
        return $this->locateURL('public', $path);
551
    }
552
    public function _locateJS($path)
553
    {
554
        return $this->locateURL('js', $path);
555
    }
556
    public function _locateCSS($path)
557
    {
558
        return $this->locateURL('css', $path);
559
    }
560
    public function _locatePage($path)
561
    {
562
        return $this->url($path);
563
    }
564
565
    /**
566
     * Only show $object in the final rendering.
567
     *
568
     * @deprecated 4.4
569
     */
570
    public function renderOnly($object)
571
    {
572
        return $this->cut($object);
573
    }
574
    // }}}
575
576
    // {{{ Layout implementation
577
    private $layout_initialized = false;
578
    /**
579
     * Implements Layouts.
580
     * Layout is region in shared template which may be replaced by object.
581
     */
582
    public function initLayout()
583
    {
584
        if ($this->layout_initialized) {
585
            throw $this->exception('Please do not call initLayout() directly from init()', 'Obsolete');
586
        }
587
        $this->layout_initialized = true;
588
    }
589
590
    // TODO: layouts need to be simplified and obsolete, because we have have other layouts now.
591
    // doc/layouts
592
    //
593
    /**
594
     * Register new layout, which, if has method and tag in the template, will be rendered.
595
     *
596
     * @param string $name
597
     *
598
     * @return $this
599
     */
600
    public function addLayout($name)
601
    {
602
        if (!$this->template) {
603
            return;
604
        }
605
        // TODO: change to functionExists()
606
        if (method_exists($this, $lfunc = 'layout_'.$name)) {
607
            if ($this->template->is_set($name)) {
608
                $this->$lfunc();
609
            }
610
        }
611
612
        return $this;
613
    }
614
615
    /**
616
     * Default handling of Content page. To be replaced by App_Frontend
617
     * This function initializes content. Content is page-dependant.
618
     */
619
    public function layout_Content()
620
    {
621
        $page = str_replace('/', '_', $this->page);
622
623
        if (method_exists($this, $pagefunc = 'page_'.$page)) {
624
            $p = $this->add('Page', $this->page, 'Content');
625
            $this->$pagefunc($p);
626
        } else {
627
            $this->app->locate('page', str_replace('_', '/', $this->page).'.php');
628
            $this->add('page_'.$page, $page, 'Content');
629
            //throw new BaseException("No such page: ".$this->page);
630
        }
631
    }
632
633
    /**
634
     * Default template for the application. Redefine to add your own rules.
635
     *
636
     * @return array|string
637
     */
638
    public function defaultTemplate()
639
    {
640
        return array('html');
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('html'); (string[]) is incompatible with the return type of the parent method AbstractView::defaultTemplate of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
641
    }
642
    // }}}
643
}
644