Issues (164)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Page/Component.php (30 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace BootPress\Page;
4
5
use Symfony\Component\HttpFoundation\Request;
6
use Symfony\Component\HttpFoundation\Response;
7
use Symfony\Component\HttpFoundation\JsonResponse;
8
use Symfony\Component\HttpFoundation\RedirectResponse;
9
use Symfony\Component\HttpFoundation\Session\Session;
10
use Spartz\TextFormatter\TextFormatter;
11
use ParsedownExtra; // erusev/parsedown-extra
12
use URLify; // jbroadway/urlify
13
use AltoRouter;
14
15
class Component
16
{
17
    
18
    private $request;
19
    private $dir = array();
20
    private $url = array();
21
    private $html = array();
22
    private $data = array(); // meta, ico, apple, css, style, other, js, jquery, script
23
    private $saved = array(); // managed in $this->save($name), and retrieved in $this->info($name) - for filters mainly
24
    private $filters = array(); // managed in $this->filter() (public), and retrieved in $this->process() (private)
25
    private $testing = false; // $this->send() exit's a Symfony Response if this is false
26
    private static $instance;
27
    private static $session;
28
29
    /**
30
     * This returns a singleton instance of the Page class so that you can access it anywhere.  Passing parameters will only make a difference when calling it for the first time, unless you $overthrow it.
31
     * 
32
     * @param array       $url        What you want your website's url to look like, and point to.
33
     * 
34
     * - '**dir**' - The base directory your website exists in.  We recommend that this be a root folder so that it is not publically accessible, but it can be if you're crazy.
35
     * - '**base**' - The root url.  If you specify this, then we will enforce it.  If it starts with https (secured), then your website will be inaccessible via http (insecure).  If you include a subdomain (eg. www) or not, it will be enforced.  This way you don't have duplicate content issues, and know exactly how your website will be accessed.
36
     * - '**suffix**' - What you want to come after all of your url (html) paths.  The options are:  '', '**\/**', '**.htm**', '**.html**', '**.shtml**', '**.phtml**', '**.php**', '**.asp**', '**.jsp**', '**.cgi**', '**.cfm**', and '**.pl**'.
37
     * - '**chars**' - This lets you specify which characters are permitted within your URLs.  You should restrict this to as few characters as possible.  The default is '**a-z0-9~%.:_-**'.
38
     * - '**testing**' - If you include and set this to anything, then any calls to ``$page->send()`` will not ``exit``.  This enables us to unit test responses and not halt the script.
39
     * 
40
     * @param object      $request    A Symfony Request object.
41
     * @param false|mixed $overthrow  If anything but false, then the parameters you pass will overthrow the previous ones submitted.  This is especially useful when unit testing.
42
     * 
43
     * @return object   A singleton Page instance.
44
     */
45 139
    public static function html(array $url = array(), Request $request = null, $overthrow = false)
46
    {
47 139
        if ($overthrow || null === static::$instance) {
0 ignored issues
show
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
48 39
            static::$instance = static::isolated($url, $request);
0 ignored issues
show
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
49 39
        }
50
51 139
        return static::$instance;
0 ignored issues
show
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
52
    }
53
 
54
    /**
55
     * This returns an isolated instance of the Page class so you can use it for whatever.
56
     * 
57
     * @param array  $url      The same as above.
58
     * @param object $request  A Symfony Request object.
59
     * 
60
     * @return object  An isolated instance of the Page class.
61
     */
62 42
    public static function isolated(array $url = array(), Request $request = null)
63
    {
64 42
        extract(array_merge(array(
0 ignored issues
show
array_merge(array('dir' ... 'a-z0-9~%.:_-'), $url) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
65 42
            'dir' => null,
66 42
            'base' => null,
67 42
            'suffix' => null,
68 42
            'chars' => 'a-z0-9~%.:_-',
69 42
        ), $url), EXTR_SKIP);
70 42
        $enforce = (is_string($base)) ? true : false;
71 42
        $page = new static();
72 42
        if (isset($testing)) $page->testing = $testing; // ie. $this->testing = $testing
73 42
        $page->request = (is_null($request)) ? Request::createFromGlobals() : $request;
74 42
        if (false === $folder = realpath($dir)) {
75 1
            $folders = array();
76 1
            $base = realpath('');
77 1
            $dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $dir);
78 1
            if (strstr($base, DIRECTORY_SEPARATOR, true) !== strstr($dir, DIRECTORY_SEPARATOR, true)) {
79 1
                $dir = $base.DIRECTORY_SEPARATOR.$dir;
80 1
            }
81 1
            foreach (array_filter(explode(DIRECTORY_SEPARATOR, $dir), 'strlen') as $folder) {
82 1
                if ($folder == '..') {
83 1
                    array_pop($folders);
84 1
                } elseif ($folder != '.') {
85 1
                    $folders[] = $folder;
86 1
                }
87 1
            }
88 1
            $folder = implode(DIRECTORY_SEPARATOR, $folders);
89 1
        }
90 42
        $page->dir('set', 'base', $folder);
91 42
        $page->dir('set', 'page', $folder);
92 42
        $page->url['full'] = '';
93 42
        $page->url['base'] = (!empty($base)) ? trim($base, '/').'/' : $page->request->getUriForPath('/');
94 42
        if (parse_url($page->url['base'], PHP_URL_SCHEME) === null) {
95 1
            $page->url['base'] = 'http://'.$page->url['base'];
96 1
        }
97 42
        $page->url['path'] = trim($page->request->getPathInfo(), '/'); // excludes leading and trailing slashes
98 42
        if ($page->url['suffix'] = pathinfo($page->url['path'], PATHINFO_EXTENSION)) {
99 34
            $page->url['suffix'] = '.'.$page->url['suffix']; // includes leading dot
100 34
            $page->url['path'] = substr($page->url['path'], 0, -strlen($page->url['suffix'])); // remove suffix from path
101 34
        }
102 42
        $page->url['query'] = (null !== $qs = $page->request->getQueryString()) ? '?'.$qs : '';
103 42
        $page->url['preg'] = preg_quote($page->url['base'], '/');
104 42
        $page->url['chars'] = 'a-z0-9'.preg_quote(str_replace(array('a-z', '0-9', '.', '/', '\\', '?', '#'), '', $chars), '/');
105 42
        $page->url['html'] = array('', '/', '.htm', '.html', '.shtml', '.phtml', '.php', '.asp', '.jsp', '.cgi', '.cfm', '.pl');
106 42
        if (empty($page->url['suffix']) || in_array($page->url['suffix'], $page->url['html'])) {
107 34
            $page->url['format'] = 'html';
108 34
        } else {
109 11
            $page->url['format'] = substr($page->url['suffix'], 1);
110 11
            $page->url['path'] .= $page->url['suffix']; // put it back on since it is relevant now
111
        }
112 42
        $page->url['method'] = $page->request->getMethod(); // eg. GET|POST
113 42
        $page->url['route'] = '/'.$page->url['path']; // includes leading slash and unfiltered path (below)
114 42
        $page->url('set', 'base', $page->url['base']);
115 42
        $page->url('set', 'dir', $page->url['base'].'page/');
116 42
        $page->url['path'] = preg_replace('/[^'.$page->url['chars'].'.\/]/i', '', $page->url['path']);
117 42
        $page->url['suffix'] = (!empty($suffix) && in_array($suffix, $page->url['html'])) ? $suffix : '';
118 42
        $page->url['full'] = $page->formatLocalPath($page->url['base'].$page->url['path'].$page->url['query']);
119 42
        if ($enforce && strcmp($page->url['full'], $page->request->getUri()) !== 0) {
120 1
            $page->eject($page->url['full'], 301);
121 1
        }
122 42
        $page->set(array(), 'reset');
123 42
        return $page;
124
    }
125
126
    /**
127
     * Allows you to set HTML Page properties.
128
     * 
129
     * @param string|array $name   The ``$page->$name`` you would like to set.  You can do this one at a time, or make this an array and set everything at once.
130
     * @param mixed        $value  The value if the $name (above) is a string.
131
     * 
132
     * ```php
133
     * $page->set(array(
134
     *     'title' => 'Sample Page',
135
     *     'description' => 'Snippet of information',
136
     *     'keywords' => 'Comma, Spearated, Tags',
137
     *     'thumb' => $page->url('base', 'image.jpg'),
138
     *     'author' => 'Full Name',
139
     *     'published' => 'Feb 7, 2015',
140
     * ));
141
     * ```
142
     */
143 43
    public function set($name, $value = '')
144
    {
145 43
        $html = (is_array($name)) ? $name : array($name => $value);
146 43
        if (is_array($name) && $value == 'reset') {
147 43
            $this->html = array(
148 43
                'doctype' => '<!doctype html>',
149 43
                'language' => 'en',
150 43
                'charset' => 'utf-8',
151 43
                'title' => '',
152 43
                'description' => '',
153 43
                'keywords' => '',
154 43
                'robots' => true,
155 43
                'body' => '',
156
            );
157 43
        }
158 43
        foreach ($html as $name => $value) {
159 23
            $this->html[strtolower($name)] = $value;
160 43
        }
161 43
    }
162
163
    /**
164
     * This is so that we can use multi-dimensional arrays with HTML Page properties.
165
     * 
166
     * @param string $name 
167
     * 
168
     * @return bool
169
     */
170 23
    public function __isset($name)
171
    {
172 23
        return isset($this->html[$name]);
173
    }
174
175
    /**
176
     * Enables you to set HTML Page properties directly.
177
     * 
178
     * @param string $name   The ``$page->$name`` you would like to set.
179
     * @param mixed  $value  Of the ``$page->$name``.
180
     */
181 14
    public function __set($name, $value)
182
    {
183 14
        $name = strtolower($name);
184 14
        if (is_null($value)) {
185 10
            unset($this->html[$name]);
186 10
        } else {
187 5
            $this->html[$name] = $value;
188
        }
189 14
    }
190
191
    /**
192
     * A magic getter for our private properties:
193
     *
194
     * - '**session**' - The Symfony Session object.
195
     * - '**request**' - The Symfony Request object.
196
     * - '**plugin**' - A PHP callable that you have set up.
197
     * - '**dir**' - An array of dirs with the following keys:
198
     *   - '**base**' - The common dir among all that follow - don't ever rely on this to be anything in particular.
199
     *   - '**page**' - The submitted ``$url['dir']`` when this class was instantiated.
200
     *   - '**$name**' - The directory of the ``$name = $page->dirname(__CLASS__)``.
201
     *   - '**...**' - Whatever other classes you have ``$page->dirname()``ed.
202
     * - '**url**' - Information about your urls that may come in handy:
203
     *   - '**full**' - The complete url base, path, and query as presently constituted.
204
     *   - '**base**' - The base url.
205
     *   - '**path**' - The url path that comes after the base, and before the query.  If this is an html page then it does not include the url suffix - whatever you have set it to.
206
     *   - '**suffix**' - Either the currently constituted url suffix, or the desired ``$url['suffix']`` that was set when this class was instantiated.  Includes the leading dot.  If this is not an html page (eg. .pdf, .jpg, etc.), then this will be empty.
207
     *   - '**query**' - A string beginning with '**?**' if there are any url params, or blank if not.
208
     *   - '**preg**' - The url base ``preg_quote()``ed, and ready to go.
209
     *   - '**chars**' - The submitted ``$url['chars']`` when this class was instantiated, ``preg_quote()``ed, but with dot, slashes, question mark and hash tag removed so that we can include them as desired.
210
     *   - '**html**' - The array of acceptable ``$url['suffix']``'s that correspont to html pages.
211
     *   - '**format**' - The type of page you are currently working with.  Either '**html**' if the ``$page->url['suffix']`` is empty, or the ``$page->url['suffix']`` without the leading dot eg. pdf, jpg, etc.
212
     *   - '**method**' - How the page is being called eg. GET or POST
213
     *   - '**route**' - The ``$page->url['path']`` with a leading slash ie. ``'/'.$page->url['path']``
214
     *   - '**set**' - An ``array($name => $path, ...)`` of the ``$page->url('set', $name, $path)``'s you (and we) have set.
215
     * - '**html**' - The private propery from which every other $name will be retrieved.  You can access and modify these at any time.  The default ones are:
216
     *   - '**doctype**' => '<!doctype html>' - Goes at the top of your HTML page.
217
     *   - '**language**' => 'en' - Gets inserted just beneath the doctype in the html tag.  If your page no speaka any english, then you can change it to ``$page->language = 'sp';``, or any other [two-letter language abbreviation](http://www.loc.gov/standards/iso639-2/langcodes.html).
218
     *   - '**charset**' => 'utf-8' - This is the first meta tag that we insert just before the title ie. ``<meta charset="utf-8">``
219
     *   - '**title**' => '' - Defines the title of the page, and is inserted into the ``<head>`` section within ``<title>`` tags.
220
     *   - '**description**' => '' - Gets inserted into the meta description tag (if it is not empty) where you can give search engines and potential visitors a brief description of the content of your page ie. ``<meta name="description" content="...">``
221
     *   - '**keywords**' => '' - A comma-separated list of keywords that you think are relevant to the page at hand.  If it is not empty we put it in a meta keywords tag ie. ``<meta name="keywords" content="...">``
222
     *   - '**robots**' => true - If left alone this property does nothing, but if you set ``$page->robots = false;`` then we'll put ``<meta name="robots" content="noindex, nofollow">`` which tells the search engines (robots): "Don't add this page to your index" (noindex), and "Don't follow any links that may be here" (nofollow) either.  If you want one or the other, then just leave this property alone and you can spell it all out for them in ``$page->meta('name="robots" content="noindex"');``
223
     *   - '**body**' => '' - This used to be useful for Google Maps, and other ugly hacks before the advent of jQuery.  There are better ways to go about this, but it makes for a handy onload handler or to insert css styles for the body.  Whatever you set here will go inside the ``<body>`` tag.
224
     *
225
     * @param string $name   The ``$page->$name`` whose value you are looking for.
226
     *
227
     * @return mixed
228
     */
229
    
230 76
    public function &__get($name)
231
    {
232
        // This method must return a reference and not use ternary operators for __set()ing multi-dimensional arrays
233
        // http://stackoverflow.com/questions/4310473/using-set-with-arrays-solved-but-why
234
        // http://stackoverflow.com/questions/5966918/return-null-by-reference-via-get
235
        switch ($name) {
236 76
            case 'session':
237 11
                if (is_null(static::$session)) {
0 ignored issues
show
Since $session is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $session to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
238 1
                    static::$session = ($this->request->hasSession()) ? $this->request->getSession() : new Session();
0 ignored issues
show
Since $session is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $session to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
239 1
                    if (!static::$session->isStarted()) {
0 ignored issues
show
Since $session is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $session to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
240 1
                        static::$session->start();
0 ignored issues
show
Since $session is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $session to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
241 1
                    }
242 2
                    if (!$this->request->hasSession()) {
243 2
                        $this->request->setSession(static::$session);
0 ignored issues
show
Since $session is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $session to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
244 2
                    }
245 2
                }
246
247 11
                return static::$session;
0 ignored issues
show
Since $session is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $session to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
248
                break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
249 76
            case 'plugin':
250 76
            case 'request':
251 76
            case 'dir':
252 76
            case 'url':
253 76
            case 'html':
254 69
                return $this->$name;
255
            break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
256 38
            default:
257 38
                return $this->html[strtolower($name)];
258
            break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
259 38
        }
260
    }
261
262
    /**
263
     * If one of your visitors gets lost, or you need to redirect them (eg. after a form has been submitted), the this method will eject theme for you.
264
     * 
265
     * @param string  $url                 Either the full url, or just the path.
266
     * @param integer $http_response_code  The status code (302 by default).
267
     * 
268
     * ```php
269
     * $page->eject('users');
270
     * ```
271
     */
272 6
    public function eject($url = '', $http_response_code = 302)
273
    {
274 5
        $url = (!empty($url)) ? $this->formatLocalPath($url) : $this->url['base'];
275
        
276 6
        return $this->send(RedirectResponse::create(htmlspecialchars_decode($url), $http_response_code));
277
    }
278
279
    /**
280
     * This will ensure that the $url path you want to enforce matches the current path.
281
     * 
282
     * @param string $url       Either the full url, or just the path.
283
     * @param integer $redirect  The status code (301 by default).
284
     * 
285
     * ```php
286
     * echo $page->url['path']; // 'details/former-title-1'
287
     * $page->enforce('details/current-title-1');
288
     * echo $page->url['path']; // 'details/current-title-1'
289
     * ```
290
     */
291 23
    public function enforce($url, $redirect = 301)
292
    {
293 23
        list($url, $path, $suffix, $query) = $this->formatLocalPath($url, 'array');
0 ignored issues
show
'array' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
The assignment to $query is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
294 23
        $compare = $this->url['path'];
295 23
        if (!empty($path)) {
296 21
            $compare .= $this->url['suffix']; // to redirect 'index' to ''
297 22
        }
298 23
        if (!in_array($suffix, $this->url['html'])) { // images, css files, etc.
299 4
            if ($path.$suffix != $this->url['path']) {
300 1
                return $this->eject($this->url['base'].$path.$suffix, $redirect);
301
            }
302 23
        } elseif ($path.$suffix != $compare) {
303 1
            if (strpos($url, $this->url['base']) === 0) {
304 1
                return $this->eject($this->url['base'].$path.$suffix.$this->url['query'], $redirect);
305
            }
306 1
        }
307 23
    }
308
309
    /**
310
     * This takes a class and determines the directory it resides in so that you can refer to it in ``$page->dir()`` and ``$page->url()``.
311
     * 
312
     * @param string $class  The class you want to reference.
313
     * 
314
     * @return string  A slightly modified string of the $class for you to reference.
315
     * 
316
     * ```php
317
     * $name = $page->dirname(__CLASS__);
318
     * echo $page->dir($name); // The directory this file resides in
319
     * ```
320
     */
321 23
    public function dirname($class)
322
    {
323 23
        $class = trim(str_replace('/', '\\', $class), '\\');
324 23
        $name = str_replace('\\', '-', strtolower($class));
325 23
        if (!isset($this->dir[$name]) && class_exists($class)) {
326 23
            $ref = new \ReflectionClass($class);
327 23
            $this->dir('set', $name, dirname($ref->getFileName()));
328 23
            unset($ref);
329 23
        }
330
        
331 23
        return (isset($this->dir[$name])) ? $name : null;
332
    }
333
334
    /**
335
     * @param string $folder  The path after ``$this->dir['page']``.  Every arg you include in the method will be another folder path.  If you want the directory to be relative to ``$name = $page->dirname(__CLASS__)``, then set the first parameter to $name, and the subsequent arguments (folders) relative to it.  Any empty args will be ignored.
336
     * 
337
     * @return string  The directory path, and ensures it has a trailing slash.
338
     * 
339
     * ```php
340
     * $page->dir(); // returns $page->dir['page'] - the one where your website resides
341
     * $page->dir('folder', 'path'); // $page->dir['page'].'folder/path/'
342
     * $page->dir('folder', '', 'path'); // $page->dir['page'].'folder/path/'
343
     * $page->dir('folder/path'); // $page->dir['page'].'folder/path/'
344
     * $page->dir('/folder//path///'); // $page->dir['page'].'folder/path/'
345
     * $page->dir($page->dir['page'].'folder/path'); // $page->dir['page'].'folder/path/'
346
     * $page->dir($page->dir['page'], '/folder/path/'); // $page->dir['page'].'folder/path/'
347
     * $page->dir('page', '/folder', '/path/'); // $page->dir['page'].'folder/path/'
348
     * $page->dir('base', 'folder/path'); // $page->dir['page'].'folder/path/' - 'base' is an alias for 'page'
349
     * 
350
     * $name = $page->dirname(__CLASS__); // $page->dir[$name] is now the directory where the __CLASS__ resides
351
     * $page->dir($name, 'folder'); // the 'folder' relative to __CLASS__ (with trailing slash)
352
     * ```
353
     */
354 65
    public function dir($folder = null)
355
    {
356 65
        $folders = func_get_args();
357 65
        if ($folder == 'set') {
358 43
            list($folder, $name, $dir) = $folders;
0 ignored issues
show
The assignment to $folder is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
359 43
            $this->dir[$name] = rtrim(str_replace('\\', '/', $dir), '/').'/';
360 43
            if (strpos($this->dir[$name], $this->dir['base']) !== 0) {
361 23
                $this->dir['base'] = $this->commonDir(array($this->dir[$name], $this->dir['base']));
362 23
            }
363
            
364 43
            return $this->dir[$name]; // all nicely formatted
365
        }
366 59
        $dir = $this->dir['page'];
367 59
        if ($folder == 'base') {
368 1
            array_shift($folders);
369 59
        } elseif (isset($this->dir[$folder])) {
370 23
            $dir = $this->dir[array_shift($folders)];
371 59
        } elseif (strpos($folder, $this->dir['base']) === 0) {
372 33
            $dir = rtrim(array_shift($folders), '/').'/';
373 33
        }
374 59
        if (empty($folders)) {
375 33
            return $dir;
376
        }
377
        $folders = array_filter(array_map(function($path){
378 59
            return trim($path, '/');
379 59
        }, $folders));
380
381 59
        return $dir.implode('/', $folders).'/';
382
    }
383
    
384
    /**
385
     * @param string $name  Of the folder(s) and file.  Can span multiple arguments.
386
     * 
387
     * @return string  The file path.  Works exactly the same as ``$page->dir(...)``, but this method doesn't include the trailing slash because it should be pointing to a file.
388
     */
389 58
    public function file($name)
0 ignored issues
show
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
390
    {
391 58
        return rtrim(call_user_func_array(array($this, 'dir'), func_get_args()), '/');
392
    }
393
    
394
    /**
395
     * Creates a url path (with trailing slash) that you can add to and work with, as opposed to ``$page->url()`` that always returns the ``$page->url['suffix']`` with it.
396
     * 
397
     * @param string $url  Every argument given becomes part of the path, the same as ``$page->dir()`` only with a url.  The first argument can include the base url, be a ``$page->dirname()``, a reference that you ``$page->url('set', ...)``ed, or just be relative to ``$page->url['base']``.
398
     * 
399
     * @return string  A url path with trailing slash - no suffix!
400
     * 
401
     * ```php
402
     * $page->path('folder'); // $page->url['base'].'folder/'
403
     * $page->path('base', 'folder'); // $page->url['base'].'folder/'
404
     * $page->path('page', 'folder'); // $page->url['base'].'page/folder/'
405
     * $page->path($page->url['base'], 'folder'); // $page->url['base'].'folder/'
406
     * ```
407
     */
408 1
    public function path($url = null)
409
    {
410 1
        $paths = func_get_args();
411 1
        $base_url = $this->url['base'];
412 1
        if ($url == 'base') {
413 1
            array_shift($paths);
414 1
        } elseif (isset($this->url['set'][$url])) {
415 1
            $base_url = $this->url['set'][array_shift($paths)];
416 1
        } elseif (strpos($url, $this->url['base']) === 0) {
417 1
            $base_url = rtrim(array_shift($paths), '/').'/';
418 1
        }
419 1
        if (empty($paths)) {
420 1
            return $base_url;
421
        }
422
        $paths = array_filter(array_map(function($path){
423 1
            return trim($path, '/');
424 1
        }, $paths));
425
        
426 1
        return $base_url.implode('/', $paths).'/';
427
    }
428
    
429
    /**
430
     * Allows you to either create a url, or manipulate it's query string and fragment.
431
     * 
432
     * @param string       $action  What you want this method to do.  The options are:
433
     * 
434
     * - '' (blank) - 
435
     * - '**params**' - To get an associative array of the ``$url`` query string.
436
     * - '**delete**' - To remove a param (or more) from the ``$url`` query string.
437
     * - '**add**' - To add a param (or more) to the ``$url`` query string.
438
     * - '**set**' - To ``$page->url['set'][$url] = $value`` that can be referred to here, and in ``$page->path()``.
439
     * - '**...**' - Anything else you do will create a url string in the same manner as ``$page->path()`` only with the ``$page->url['suffix']`` included.
440
     * 
441
     * @param string       $url     If empty then the ``$page->url['base']`` will be used.
442
     * @param string|array $key     What you would like to add to or take from the ``$url``.  This can be a query string parameter, a '**#**', a '**?**', or an array depending on the type of ``$action`` you are looking for.
443
     * @param string       $value   If ``$action`` equals '**add**', and ``$key`` is not an array, then this is the ``$key``'s value.   Otherwise this argument means nothing.
444
     * 
445
     * @return string|array  The url string if you are creating one, or else:
446
     * 
447
     * - If ``$action`` equals '**params**' then the ``$url`` query string is returned as an associative array.  Otherwise this method always returns a ``$url`` that has been ampified and is ready to be inserted into your html.
448
     * - If ``$action`` equals '**add** or '**delete**':
449
     *   - The ``$key``'s will be added to or deleted from the ``$url``'s query string or fragment if ``$key`` equals '#'.
450
     *   - If ``$key`` is an array, then foreach key and value, the ``$url`` will be added to or deleted from accordingly.
451
     * - If ``$action`` equals '**delete**' and ``$key`` equals '**?**' then the ``$url`` will be returned without any query string at all.
452
     * - If no parameters are given then the ``$page->url['full']`` is returned.
453
     */
454 75
    public function url($action = '', $url = '', $key = '', $value = null)
455
    {
456 75
        if (empty($action)) {
457 41
            return htmlspecialchars($this->url['full']);
458 74
        } elseif ($action == 'set') {
459 46
            return $this->url['set'][$url] = $key;
460 54
        } elseif (!in_array($action, array('params', 'add', 'delete'))) {
461 29
            $base_url = (is_array($action) && isset($action['url'])) ? $action['url'] : implode('/', (array) $action);
462 29
            if (isset($this->url['set'][$base_url])) {
463 29
                $base_url = $this->url['set'][$base_url];
464 29
            } elseif (isset($this->dir[$base_url])) {
465 2
                $base_url = $this->url['base'].$base_url.'/';
466 2
            }
467
            // get an array of all url $segments after the $base_url
468 29
            $segments = array_filter(array_slice(func_get_args(), 1));
469 29
            if (($num = count($segments)) > 0) {
470
                // trim each $segments slashes
471 29
                $segments = array_map('trim', $segments, array_fill(0, $num, '/'));
472 29
            }
473 29
            array_unshift($segments, rtrim($base_url, '/\\'));
474
            
475 29
            return htmlspecialchars($this->formatLocalPath(htmlspecialchars_decode(implode('/', $segments))));
476
        }
477 39
        $url = (!empty($url)) ? htmlspecialchars_decode($url) : $this->url['full'];
478 39
        $base = preg_replace('/[\?#].*$/', '', $url); // just the url and path
479 39
        $url = parse_url($url);
480 39
        if (!isset($url['query'])) {
481 16
            $params = array();
482 16
        } else {
483 27
            parse_str($url['query'], $params);
484
        }
485 39
        $fragment = (!empty($url['fragment'])) ? '#'.$url['fragment'] : '';
486
        switch ($action) {
487 39
            case 'params':
488 25
                return $params;
489
                break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
490 27
            case 'add':
491 24
                if ($key == '#') {
492 1
                    $fragment = '#'.urlencode($value);
493 1
                } else {
494 24
                    $params = array_merge($params, (is_array($key) ? $key : array($key => $value)));
495
                }
496 24
                break;
497 24
            case 'delete':
498 24
                if ($key == '?') {
499 5
                    $params = array();
500 24
                } elseif ($key == '#') {
501 1
                    $fragment = '';
502 1
                } else {
503 21
                    foreach ((array) $key as $value) {
504 21
                        unset($params[$value]);
505 21
                    }
506
                }
507 24
                break;
508
        }
509 27
        $query = (!empty($params)) ? '?'.http_build_query($params) : '';
510
511 27
        return htmlspecialchars($this->formatLocalPath($base.$query.$fragment));
512
    }
513
    
514
    /**
515
     * A shortcut for ``$page->request->query->get($key, $default)``.
516
     * 
517
     * @param string $key  The $_GET[$key].
518
     * @param mixed $default  The default value to return if the $_GET[$key] doesn't exits.
519
     * 
520
     * @return mixed
521
     */
522 2
    public function get($key, $default = null)
523
    {
524 2
        return $this->request->query->get($key, $default);
525
    }
526
    
527
    /**
528
     * A shortcut for ``$page->request->request->get($key, $default)``.
529
     * 
530
     * @param string $key  The $_POST[$key].
531
     * @param mixed $default  The default value to return if the $_POST[$key] doesn't exits.
532
     * 
533
     * @return mixed
534
     */
535 1
    public function post($key, $default = null)
536
    {
537 1
        return $this->request->request->get($key, $default);
538
    }
539
540
    /**
541
     * Takes the current (or custom) ``$page->url['method']``, and maps it to the paths you provide using the (AltoRouter)[http://altorouter.com/].
542
     * 
543
     * @param array $map    An ``array($route => $target, ...)`` or just ``array($route, ...)``, or any combination thereof.  The $target could be a php file, a method name, or whatever you want that will help you to determine what comes next.  A $route is what you are expecting your uri to look like, and mapping them to variables that you can actually work with.
544
     * 
545
     * - **folder** will match 'folder'
546
     * - **users/[register|sign_in|forgot_password:action]** will match 'users/sign_in' with ``$params['action'] = 'sign_in'``
547
     * - **users/[i:id]** will match 'users/12' with ``$params['id'] = 12``
548
     * 
549
     * Notice that the '**i**' in '**[i:id]**' will match an integer and assign the paramter '**id**' to the value of '**i**'.  You can set or override these shortcuts in **$types** below.  The defaults are:
550
     * 
551
     * - __*__ - Match all request URIs
552
     * - __[i]__ - Match an integer
553
     * - __[i:id]__ - Match an integer as 'id'
554
     * - __[a:action]__ - Match alphanumeric characters as 'action'
555
     * - __[h:key]__ - Match hexadecimal characters as 'key'
556
     * - __[:action]__ - Match anything up to the next '__/__', or end of the URI as 'action'
557
     * - __[create|edit:action]__ - Match either 'create' or 'edit' as 'action'
558
     * - __[*]__ - Catch all (lazy)
559
     * - __[*:trailing]__ - Catch all as 'trailing' (lazy)
560
     * - __[**:trailing]__ - Catch all (possessive - will match the rest of the URI)
561
     * - __.[:format]?__ - Match an optional parameter as 'format'
562
     *   - When you put a '__?__' after the block (making it optional), a '__/__' or '__.__' before the block is also optional
563
     * 
564
     * A few more examples for the road:
565
     * 
566
     * - __posts/[*:title]-[i:id]__ - Matches 'posts/this-is-a-title-123'
567
     * - __posts/[create|edit:action]?/[i:id]?__ - Matches 'posts', 'posts/123', 'posts/create', and 'posts/edit/123'
568
     * - __output.[xml|json:format]?__ - Matches 'output', 'output.xml', 'output.json'
569
     * - __@\.(json|csv)$__ - Matches all requests that end with '.json' or '.csv'
570
     * - __!@^admin/__ - Matches all requests that _don't_ start with admin/
571
     * - __api/[*:key]/[*:name]__ - Matches 'api/123/456/gadd' where name = '456/gadd'
572
     * - __[:controller]?/[:action]?__ - Matches the typical controller/action format
573
     * - __[:controller]?/[:method]?/[**:uri]?__ - There's nothing that this won't cover
574
     * 
575
     * @param mixed $route  If your don't want to use ``$page->url['method']``, then set this value to the path you want to match against.
576
     * @param array $types  If you want to add to (or override) the shortcut regex's, then you can add them here.  The defaults are:
577
     * 
578
     * ```php
579
     * $types = array(
580
     *     'i'  => '[0-9]++', // integer
581
     *     'a'  => '[0-9A-Za-z]++', // alphanumeric
582
     *     'h'  => '[0-9A-Fa-f]++', // hexadecimal
583
     *     '*'  => '.+?', // anything (lazy)
584
     *     '**' => '.++', // anything (possessive)
585
     *     ''   => '[^/\.]++' // not a slash (/) or period (.)
586
     * );
587
     * ```
588
     * 
589
     * @return mixed  False if nothing matches in which case you should ``show_404()``, or an array of information with the following keys:
590
     * 
591
     * - '**target**' - The route we successfully matched.  If the route is a key, then this is it's value.  Otherwise it is the route itself.
592
     * - '**params**' - All of the params we matched to the successful route.
593
     * - '**method**' - Either '**POST**' or '**GET**'.
594
     * 
595
     * ```php
596
     * $routes = array(
597
     *   '' => 'index.php',
598
     *   'listings' => 'listings.php',
599
     *   'details/[*:title]-[i:id]' => 'details.php',
600
     * );
601
     * if (is_admin()) $routes['admin/[:action]'] = 'admin.php';
602
     * 
603
     * if ($route = $page->routes($routes)) {
604
     *     include $route['target'];
605
     * } else {
606
     *     $page->send(404);
607
     * }
608
     * ```
609
     */
610 16
    public function routes(array $map, $route = null, array $types = array())
611
    {
612 16
        $path = (is_null($route)) ? $this->url['route'] : $route;
613 16
        $routes = array();
614 16
        foreach ($map as $route => $target) {
615 16
            if (is_numeric($route)) {
616 16
                $route = $target;
617 16
            }
618 16
            $routes[] = array($this->url['method'], ltrim($route, '/'), $target);
619 16
        }
620 16
        $router = new AltoRouter($routes, '', $types);
621 16
        if ($match = $router->match(ltrim($path, '/'), $this->url['method'])) {
622 16
            unset($match['name']);
623 16
        }
624
625 16
        return $match;
626
    }
627
628
    /**
629
     * Generates an html tag programatically.
630
     * 
631
     * @param string $name       The tag's name eg. 'div'
632
     * @param array  $attributes An ``array($key => $value, ...)`` of attributes.  If $value is an array (a good idea for classes) then we remove any duplicate or empty values, and implode them with a space in beween.  If the $value is an empty string we ignore the attribute entirely.  If the $key is numeric (ie. not set) then the attribute is it's $value (eg. '**multiple**' or '**selected**'), and we'll delete any $key of the same name (eg. multiple="multiple" or selected="selected").  If you want an empty attribute to be included, then set the $value to null.
633
     * @param string $content    All args supplied after the $attributes are stripped of any empty values, and ``implode(' ', ...)``ed.
634
     * 
635
     * @return string An opening html tag with attributes.  If $content is supplied then we add that, and a closing html tag.
636
     *
637
     * ```php
638
     * echo $page->tag('meta', array('name'=>'description', 'content'=>'')); // <meta name="description">
639
     * 
640
     * echo $page->tag('p', array('class'=>'lead'), 'Content', 'Coming'); // <p class="lead">Content Coming</p>
641
     * ```
642
     */
643 33
    public function tag($name, array $attributes, $content = null)
0 ignored issues
show
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $content is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
644
    {
645 33
        $args = func_get_args();
646 33
        $tag = array_shift($args);
647 33
        $attributes = array_shift($args);
648 33
        foreach ($attributes as $key => $value) {
649 33
            if (is_array($value)) {
650 2
                $value = implode(' ', array_unique(array_filter($value)));
651 2
            }
652 33
            if ($value === '') {
653 7
                unset($attributes[$key]);
654 33
            } elseif (!is_numeric($key)) {
655 32
                $attributes[$key] = $key.'="'.$value.'"';
656 32
            } elseif (isset($attributes[$value])) {
657 1
                unset($attributes[$key]);
658 1
            }
659 33
        }
660 33
        $attributes = (!empty($attributes)) ? ' '.implode(' ', $attributes) : '';
661 33
        $html = '<'.$tag.$attributes.'>';
662 33
        if (!empty($args)) {
663 24
            $html .= implode(' ', array_filter($args));
664 24
            $html .= '</'.strstr($tag.' ', ' ', true).'>';
665 24
        }
666
667 33
        return $html;
668
    }
669
670
    /**
671
     * Some handy formatters that always come in handy.
672
     * 
673
     * @param string      $type    The type of string you are formatting:  Either '**url**', '**markdown**', or '**title**'.
674
     * @param string      $string  What you would like to format
675
     * @param false|mixed $slashes If anything but false, it will allow your url to have slashes.
676
     * 
677
     * @return string Depending on $type
678
     */
679 8
    public function format($type, $string, $slashes = false)
680
    {
681
        switch ($type) {
682 8
            case 'url':
683 6
                $url = ($slashes !== false) ? explode('/', $string) : array($string);
684 6
                foreach ($url as $key => $value) {
685 6
                    $url[$key] = URLify::filter($value);
686 6
                }
687 6
                $string = implode('/', $url);
688 6
                break;
689 6
            case 'markdown':
690 6
                $parser = new ParsedownExtra();
691 6
                $string = $parser->text($string);
692 6
                break;
693 1
            case 'title':
694 1
                $string = explode(' ', $string);
695 1
                foreach ($string as $key => $value) {
696 1
                    if (!empty($value) && mb_strtoupper($value) == $value) {
697 1
                        $string[$key] = mb_strtolower($value);
698 1
                    }
699 1
                }
700 1
                $string = TextFormatter::titleCase(implode(' ', $string));
701 1
                break;
702
        }
703
        
704 8
        return $string;
705
    }
706
707
    /**
708
     * Allows you to insert any meta tags (at any time) into the head section of your page.  We already take care of the description, keywords, and robots tags.  If there are any more you would like to add, then you may do so here.  You can only enter one meta tag at a time with this method.
709
     * 
710
     * @param mixed $args  If ``$args`` is a string, then we just include the meta tag as is.  If it is an array then we use the key and value pairs to build the meta tag's attributes.
711
     * 
712
     * ```php
713
     * $page->meta('name="author" content="name"'); // or ...
714
     * 
715
     * $page->meta(array('name'=>'author', 'content'=>'name'));
716
     * ```
717
     */
718 1
    public function meta($args)
719
    {
720 1
        if (is_string($args)) {
721 1
            $this->data('meta', $args, false);
722 1
        } else {
723 1
            foreach ($args as $key => $value) {
0 ignored issues
show
The expression $args of type object|integer|double|array|boolean|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
724 1
                $args[$key] = $key.'="'.$value.'"';
725 1
            }
726 1
            $this->data('meta', implode(' ', $args), false);
727
        }
728 1
    }
729
730
    /**
731
     * @param mixed $link     This can be a string, or an array of javascript, css, and / or icon resources that will be added to the head section of your page.
732
     * @param mixed $prepend  If this value is anything other than false (I like to use 'prepend'), then all of the ``$link``'s that you just included will be prepended to the stack, as opposed to being inserted after all of the other links you have included.
733
     * 
734
     * ```php
735
     * $page->link(array(
736
     *     $page->url('images/favicon.ico'),
737
     *     $page->url('css/stylesheet.css'),
738
     *     $page->url('js/functions.js'),
739
     * ));
740
     * ```
741
     */
742 10
    public function link($link, $prepend = false)
743
    {
744 10
        $link = (array) $link;
745 10
        if ($prepend !== false) {
746 5
            $link = array_reverse($link); // so they are added in the correct order
747 5
        }
748 10
        foreach ($link as $file) {
749 10
            $frag = (strpos($file, '<') === false) ? strstr($file, '#') : '';
750 10
            if (!empty($frag)) {
751 2
                $file = substr($file, 0, -strlen($frag));
752 2
            }
753 10
            if (preg_match('/\.(js|css|ico|apple)$/i', $file)) {
754 8
                $split = strrpos($file, '.');
755 8
                $ext = substr($file, $split + 1);
756 8
                $name = substr($file, 0, $split);
757
                switch ($ext) {
758 8
                    case 'js':
759 8
                        $this->data('js', $file.$frag, $prepend);
760 8
                        break;
761 3
                    case 'css':
762 3
                        $this->data('css', $file.$frag, $prepend);
763 3
                        break;
764 1
                    case 'ico':
765 1
                        $this->data['ico'] = $file.$frag;
766 1
                        break;
767 1
                    case 'apple':
768 1
                        $this->data['apple'] = $name.'.png';
769 1
                        break;
770
                }
771 10
            } elseif (substr($file, 1, 5) == 'style') {
772 2
                $this->data('style', $file, $prepend);
773 7
            } elseif (substr($file, 1, 6) == 'script') {
774 6
                $this->data('script', $file, $prepend);
775 6
            } else {
776 1
                $this->data('other', $file, $prepend);
777
            }
778 10
        }
779 10
    }
780
781
    /**
782
     * This will enclose the $css within ``<script>`` tags and place it in the ``<head>`` of your page.
783
     * 
784
     * @param string $code  Your custom css code.
785
     * 
786
     * ```php
787
     * $page->script('body { background-color:red; }');
788
     * ```
789
     */
790 1
    public function style($code)
791
    {
792 1
        if (is_array($code)) {
793 1
            foreach ($code as $css => $rules) {
794 1
                if (is_array($rules)) {
795 1
                    $code[$css] = $css.' { '.implode(' ', $rules).' }';
796 1
                } elseif (!is_numeric($css)) {
797 1
                    $code[$css] = $css.' { '.$rules.' }';
798 1
                }
799 1
            }
800 1
            $code = implode("\n", $code);
801 1
        }
802 1
        $this->link('<style>'.(strpos($code, "\n") ? "\n".$this->indent($code)."\n\t" : trim($code)).'</style>');
803 1
    }
804
805
    /**
806
     * This will enclose the $javascript within ``<style>`` tags and place it at the bottom of your page.
807
     * 
808
     * @param string $code  Your custom javascript code.
809
     * 
810
     * ```php
811
     * $page->script('alert("Hello World");');
812
     * ```
813
     */
814 5
    public function script($code)
815
    {
816 5
        if (is_array($code)) {
817 1
            $code = implode("\n", $code);
818 1
        }
819 5
        $this->link('<script>'.(strpos($code, "\n") ? "\n".$this->indent($code)."\n\t" : trim($code)).'</script>');
820 5
    }
821
822
    /**
823
     * jQuery itself is included automatically if you ever call this method, and is placed before any other included scripts on the page.  The default version is currently v.1.11.4, but you can change that by setting ``$page->jquery`` to the file you want to use.  To include the jQuery UI right after that then call ``$page->jquery('ui', $file)``, and if you leave out the $file then we will use v.1.12.3.
824
     * 
825
     * @param string|array $code     A string of jQuery.  All of the included code is compiled at the end of the page and placed into one ``$(document).ready(function(){...})``.  If this is a file or files (array), then they will be included via ``$page->link()``.
826
     * @param mixed  $prepend  Passed to ``$page->link()`` if including files.
827
     * 
828
     * ```php
829
     * $page->jquery('$("button.continue").html("Next Step...");');
830
     * ```
831
     */
832 6
    public function jquery($code, $prepend = false)
833
    {
834 6
        if ($code == 'ui') {
835 1
            if (!isset($this->data['jquery']['ui'])) {
836 1
                $this->data['jquery']['ui'] = false;
837 1
            }
838 1
            if (!empty($prepend)) {
839 1
                $this->data['jquery']['ui'] = $prepend;
840 1
            }
841
            
842 1
            return;
843
        }
844 6
        foreach ((array) $code as $value) {
845 6
            if (!is_string($value) || strpos($value, 'http') !== 0) {
846 6
                $this->data['jquery'][] = $code;
847
                
848 6
                return;
849
            }
850 1
        }
851 1
        $this->link($code, $prepend);
852 1
        $this->data['jquery'][] = '';
853 1
    }
854
855
    /**
856
     * We use this in the Form component to avoid input name collisions.  We use it in the Bootstrap component for accordions, carousels, and the like.  The problem with just incrementing a number and adding it onto something else is that css and jQuery don't like numbered id's, and so we use roman numerals instead and that solves the problem for us.
857
     * 
858
     * @param string $prefix  What you would like to come before the roman numeral.  This is not really needed, but when you are looking at your source code, it helps to know what you are looking at.
859
     * 
860
     * @return string  A unique id.
861
     * 
862
     * ```php
863
     * // Assuming this method has not been called before:
864
     * echo $page->id('name'); // nameI
865
     * echo $page->id('unique'); // uniqueII
866
     * echo $page->id('unique'); // uniqueIII
867
     * ```
868
     */
869 13
    public function id($prefix = '')
870
    {
871 13
        static $id = 0;
872 13
        ++$id;
873 13
        $result = '';
874 13
        $lookup = array('M' => 1000, 'CM' => 900, 'D' => 500, 'CD' => 400, 'C' => 100, 'XC' => 90, 'L' => 50, 'XL' => 40, 'X' => 10, 'IX' => 9, 'V' => 5, 'IV' => 4, 'I' => 1);
875 13
        $number = $id;
876 13
        if ($number < 100) {
877 13
            $lookup = array_slice($lookup, 4);
878 13
        }
879 13
        foreach ($lookup as $roman => $value) {
880 13
            $matches = intval($number / $value);
881 13
            $result .= str_repeat($roman, $matches);
882 13
            $number = $number % $value;
883 13
        }
884
885 13
        return $prefix.$result;
886
    }
887
888
    /**
889
     * This allows you to map a $path to a folder and $file in $dir so that you can ``$page->load()`` it.  This will essentially make your $file a controller (following the MVC pattern if that means anything to you).
890
     * 
891
     * @param string $dir   The base directory whose folders you want to map to a $path.
892
     * @param string $path  The ``$page->url['path']`` or whatever else you want to use.
893
     * @param string $file  The filename that must be in the folder to make a match.
894
     * 
895
     * @return array|null  If we have a match then we will return an array with the following info:
896
     * 
897
     * - '**file**' - The file path for which we made a match.
898
     * - '**dir**' - The dir in which the file resides (with trailing slash).
899
     * - '**assets**' - The url path (with trailing slash) that corresponds to the dir for including images and other files.
900
     * - '**url**' - The url path for linking to other pages that are relative to this dir.
901
     * - '**folder**' - The portion of your $path that got us to your $file.
902
     * - '**route**' - The remaining portion of your $path that your $file will have to figure out what to do with next.
903
     * 
904
     * ```php
905
     * // Assuming ``$dir = $page->dir('folders')``, and you have a $dir.'users/index.php' file:
906
     * if ($params = $page->folder($dir, 'users/sign_in')) {
907
     *     $html = $page->load($params['file'], $params);
908
     *     // $params = array(
909
     *     //     'file' => $dir.'users/index.php',
910
     *     //     'dir' => $dir.'users/',
911
     *     //     'assets' => $page->url['base'].'page/folders/users/',
912
     *     //     'url' => $page->url['base'].'folders/users/',
913
     *     //     'folder' => 'users/',
914
     *     //     'route' => '/sign_in',
915
     *     // );
916
     * }
917
     * ```
918
     */
919 23
    public function folder($dir, $path, $file = 'index.php')
920
    {
921 23
        $dir = $this->dir($dir);
922 23
        if (strpos($dir, $this->dir['page']) === 0 && is_dir($dir)) {
923 23
            $folder = substr($dir, strlen($this->dir['page']));
924 23
            $paths = array();
925 23
            $path = preg_replace('/[^'.$this->url['chars'].'\.\/]/', '', strtolower($path));
926 23
            foreach (explode('/', $path) as $dir) {
927 23
                if (false !== $extension = strstr($dir, '.')) {
928 1
                    $dir = substr($dir, 0, -strlen($extension)); // remove file extension
929 1
                }
930 23
                if (!empty($dir)) {
931 3
                    $paths[] = $dir; // remove empty $paths
932 3
                }
933 23
            }
934 23
            $paths = array_diff($paths, array('index')); // remove any reference to 'index'
935 23
            $path = '/'.implode('/', $paths); // includes leading slash and corresponds with $paths
936 23
            if ($extension) {
937 1
                $path .= $extension;
0 ignored issues
show
The variable $extension does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
938 1
            }
939 23
            while (!empty($paths)) {
940 3
                $route = implode('/', $paths).'/'; // includes trailing slash
941 3
                if (is_file($this->file($folder, $route, $file))) {
942
                    return array(
943 2
                        'file' => $this->file($folder, $route, $file),
944 2
                        'dir' => $this->dir($folder, $route),
945 2
                        'assets' => $this->url['base'].'page/'.$folder.$route,
946 2
                        'url' => $this->url['base'].$folder.$route,
947 2
                        'folder' => substr($path, 1, strlen($route)), // remove leading slash
948 2
                        'route' => substr($path, strlen($route)), // remove trailing slash
949 2
                    );
950
                }
951 2
                array_pop($paths);
952 2
            }
953 23
            if (is_file($this->file($folder, $file))) {
954
                return array(
955 23
                    'file' => $this->file($folder, $file),
956 23
                    'dir' => $this->dir($folder),
957 23
                    'assets' => $this->url['base'].'page/'.$folder,
958 23
                    'url' => $this->url['base'].$folder,
959 23
                    'folder' => '',
960 23
                    'route' => $path,
961 23
                );
962
            }
963 2
        }
964
965 2
        return;
966
    }
967
968
    /**
969
     * Passes $params to a $file, and returns the output.
970
     * 
971
     * @param string $file    The file you want to ``include``.
972
     * @param array  $params  Variables you would like your file to receive.
973
     * 
974
     * @return mixed  Whatever you ``$export``ed (could be anything), or a string of all that you ``echo``ed.
975
     * 
976
     * ```php
977
     * $file = $page->file('folders/users/index.php');
978
     * 
979
     * // Assuming $file has the following code:
980
     *
981
     * <?php
982
     * extract($params);
983
     * $export = $action.' Users';
984
     * 
985
     * // Loading it like this would return 'Sign In Users'
986
     * 
987
     * echo $page->load($file, array('action'=>'Sign In'));
988
     * ```
989
     */
990 2
    public function load($file, array $params = array())
991
    {
992 2
        if (!is_file($file)) {
993 1
            return;
994
        }
995 2
        foreach ($params as $key => $value) {
996 2
            if (is_numeric($key) && is_string($value)) {
997 1
                $params[$value] = true; // makes it possible to extract(), and easier to check if isset()
998 1
                unset($params[$key]);
999 1
            }
1000 2
        }
1001 2
        $export = '';
1002 2
        ob_start();
1003 2
        include $file;
1004 2
        $html = ob_get_clean();
1005
1006 2
        return ($export !== '') ? $export : $html;
1007
    }
1008
1009
    /**
1010
     * This method exists mainly for plugins.  It allows them to save some information for future use.  The saved information can be retrieved later by calling ``$page->info()``.
1011
     * 
1012
     * @param string $name   There is a potential for collision here.  I recommend using the ``$page->dirname(__CLASS__)`` exclusively.
1013
     * @param mixed  $key    If you don't indicate a ``$value``, then this will be added to the ``$name``'s array which can be retrieved at ``$page->info($name)``.
1014
     * @param mixed  $value  if ``$key`` is a specific value that you want to save then this will be it's value, and it will override any value previously set.  It can be retrieved at ``$page->info($name, $key)``.
1015
     * 
1016
     * ```php
1017
     * $name = $page->dirname(__CLASS__);
1018
     * $page->save($name, 'one');
1019
     * $page->save($name, 'two');
1020
     * $page->save($name, 'skip', 'few');
1021
     * ```
1022
     */
1023 1
    public function save($name, $key, $value = null)
1024
    {
1025 1
        if (func_num_args() == 2 || is_array($key)) {
1026 1
            $this->saved[$name][] = $key;
1027 1
        } else {
1028 1
            $this->saved[$name][$key] = $value;
1029
        }
1030 1
    }
1031
1032
    /**
1033
     * Returns the info saved in ``$page->save($name)``.
1034
     * 
1035
     * @param string $name  The one you indicated in ``$page->save($name)``.
1036
     * @param string $key   The specific value you ``$page->save($name, $key)``ed previously and now want to retrieve.
1037
     * 
1038
     * @return mixed  If you indicate a ``$key`` then we will return the value if it exists, or null if it does not.  Otherwise we will return the whole array of info saved for ``$name`` (if any), or an empty array if not.
1039
     * 
1040
     * ```php
1041
     * $name = $page->dirname(__CLASS__);
1042
     * 
1043
     * $page->info($name); // returns array();
1044
     * 
1045
     * $page->save($name, 'one');
1046
     * $page->save($name, 'two');
1047
     * $page->save($name, 'skip', 'few');
1048
     * 
1049
     * $page->info($name); // returns array('one', 'two');
1050
     * $page->info($name, 'skip'); // returns 'few';
1051
     * $page->info($name, 'set'); // returns null;
1052
     * ```
1053
     */
1054 1
    public function info($name, $key = null)
1055
    {
1056 1
        if (!is_null($key)) {
1057 1
            return (isset($this->saved[$name][$key])) ? $this->saved[$name][$key] : null;
1058
        }
1059 1
        $info = (isset($this->saved[$name])) ? $this->saved[$name] : array();
1060 1
        foreach ($info as $key => $value) {
1061 1
            if (!is_numeric($key)) {
1062 1
                unset($info[$key]);
1063 1
            }
1064 1
        }
1065
1066 1
        return $info;
1067
    }
1068
1069
    /**
1070
     * Enables you to prepend, append, or modify just about anything throughout the creation process of your page.
1071
     * 
1072
     * @param string  $section   Must be one of (in the order we process them at ``$page->display()``):
1073
     * 
1074
     * - '**content**' - This comes first before all.  Presumably, it is the reason you are creating a website.  We create / process the content first, then we put the rest of the page together, which all revolves around the content.  If you have anything else to add (or edit), you may do so now.
1075
     * - '**metadata**' - This contains the title tag and metadata, just before we start inserting all of your css stylesheets.
1076
     * - '**css**' - After we insert any icon image links, we hand over the array of stylesheets we want to include for further processing (if any).
1077
     * - '**styles**' - After all the stylesheets are up, then we check for anything else you would like to add just before the ``</head><body>`` tags.
1078
     * - '**javascript**' - After your content we include jquery first, then all of your javascript files.
1079
     * - '**scripts**' - After listing all of your javascript src files, then we include any scripts, and place the jQuery code last.
1080
     * - '**head**' - Everything between the ``<head>`` ... ``</head>`` tags.
1081
     * - '**body**' - Everything between the ``<body>`` ... ``</body>`` tags.
1082
     * - '**page**' - The entire page from top to bottom.
1083
     * - '**response**' - The final Symfony Response object if you ``$page->send()`` it.
1084
     * 
1085
     * @param mixed   $function  Can be either '**prepend**', '**append**', or a callable function or method.  If filtering the '**response**' then we'll pass the ``$page`` (this class instance), ``$response`` (what you are filtering), and ``$type`` ('html', 'json', 'redirect', or ``$page->url['format']``) of content that you are dealing with.
1086
     * 
1087
     * @param mixed   $params    
1088
     * - If ``$section`` equals '**response**'
1089
     *   - These are the page *type* and response *code* conditions that the response must meet in order to be processed.  It can be an array or string.
1090
     * - Elseif ``$function`` equals '**prepend**' or '**append**' then this must be a string.
1091
     * - Elseif ``$function`` is a callable function or method:
1092
     *   - ``$params`` must be an array of arguments which are passed to the function or method.
1093
     *   - '**this**' must be listed as one of the ``$params``.
1094
     *   - If '**this**' is the only ``$param``, then instead of an array you can just pass the string '**this**'.
1095
     *   - '**this**' is the ``$section`` as currently constituted, and for which your filter would like to operate on.  If you don't return anything, then that section will magically disappear.
1096
     * 
1097
     * @param integer $order     The level of importance (or priority) that this filter should receive.  The default is 10.  All filters are called in the order specified here.
1098
     * 
1099
     * ```php
1100
     * $page->filter('response', function ($page, $response, $type) {
1101
     *     return $response->setContent($type);
1102
     * }, array('html', 200));
1103
     * 
1104
     * $page->filter('response', function ($page, $response) {
1105
     *     return $response->setContent('json');
1106
     * }, 'json');
1107
     * 
1108
     * $page->filter('response', function ($page, $response) {
1109
     *     return $response->setContent(404);
1110
     * }, 404);
1111
     * 
1112
     * function prepend_facebook_like_button ($content) {
1113
     *     return 'facebook_like_button '.$content;
1114
     * }
1115
     * 
1116
     * $page->filter('content', 'prepend_facebook_like_button', array('this')); // or ...
1117
     * 
1118
     * $page->filter('content', 'prepend_facebook_like_button', 'this'); // or ...
1119
     * 
1120
     * $page->filter('content', 'prepend', 'facebook_like_button ');
1121
     * ```
1122
     * 
1123
     * @throws \LogicException  If something was not set up right.
1124
     */
1125 10
    public function filter($section, $function, $params = 'this', $order = 10)
1126
    {
1127 10
        $errors = array();
1128 10
        if ($section == 'response') {
1129 3
            if (!is_callable($function, false, $name)) {
1130 1
                $errors[] = "'{$name}' cannot be called";
1131 1
            }
1132 3
            if (!is_array($params)) {
1133 3
                $params = explode(' ', $params);
1134 3
            }
1135 3
            foreach ($params as $key => $value) {
1136 3
                if (empty($value) || $value == 'this') {
1137 1
                    unset($params[$key]);
1138 1
                }
1139 3
            }
1140 3
            $key = false;
1141 10
        } elseif (!in_array($section, array('metadata', 'css', 'styles', 'head', 'content', 'javascript', 'scripts', 'body', 'page'))) {
1142 1
            $errors[] = "'{$section}' cannot be filtered";
1143 8
        } elseif (in_array($function, array('prepend', 'append'))) {
1144 3
            if (!is_string($params)) {
1145 1
                $errors[] = "When using '{$function}', \$params must be a string";
1146 3
            } elseif (in_array($section, array('css', 'javascript'))) {
1147 1
                $this->filters[$function][$section][] = $params; // [prepend|append][css|javascript][] = (string)
1148 1
                return;
1149
            }
1150 3
            $key = ''; // not applicable here
1151 3
        } else {
1152 5
            $params = (array) $params;
1153 5
            $key = array_search('this', $params);
1154 5
            if ($key === false) {
1155 1
                $errors[] = "'this' must be listed in the \$params so that we can give you something to filter";
1156 1
            }
1157 5
            if (!is_callable($function, false, $name)) {
1158 2
                $errors[] = "'{$name}' cannot be called";
1159 2
            }
1160
        }
1161 10
        if (!empty($errors)) {
1162 5
            throw new \LogicException(implode("\n\n", $errors));
1163
        }
1164 5
        $this->filters[$section][] = array('function' => $function, 'params' => $params, 'order' => $order, 'key' => $key);
0 ignored issues
show
The variable $key does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1165 5
    }
1166
1167
    /**
1168
     * This method fulfills the measure of the whole Page component's existence: to be able to manipulate every part of an HTML page at any time.
1169
     * 
1170
     * @param string $content  Of your page.
1171
     * 
1172
     * @return string  The complete HTML page from top to bottom.
1173
     */
1174 9
    public function display($content)
1175
    {
1176 9
        $content = $this->process('content', $content);
1177
        $head = array(
1178 9
            $this->process('metadata', "\t".implode("\n\t", $this->metadata())),
1179 9
            $this->process('styles', "\t".implode("\n\t", $this->styles())),
1180 9
        );
1181
        $body = array(
1182 9
            $content,
1183 9
            $this->process('scripts', "\t".implode("\n\t", $this->scripts())),
1184 9
        );
1185
        $html = array(
1186 9
            $this->html['doctype'],
1187 9
            '<html lang="'.$this->html['language'].'">',
1188 9
            '<head>',
1189 9
            $this->process('head', implode("\n", $head)),
1190 9
            '</head>',
1191 9
            (!empty($this->html['body'])) ? '<body '.$this->html['body'].'>' : '<body>',
1192 9
            $this->process('body', implode("\n", $body)),
1193 9
            '</body>',
1194 9
            '</html>',
1195 9
        );
1196
1197 9
        return $this->process('page', implode("\n", $html));
1198
    }
1199
1200
    /**
1201
     * Sends a Symfony Response object, and allows you to further process and ``$page->filter()`` it.
1202
     * 
1203
     * @param object|string|integer $response  Either a Symfony Response object, the content of your response, or just a quick status code eg. ``$page->send(404)``
1204
     * @param integer               $status    The status code if your ``$response`` is a content string.
1205
     * @param array                 $headers   A headers array if your ``response`` is a content string.
1206
     * 
1207
     * @return object  If you set ``Page::html(array('testing'=>true))`` then we will return the Symfony Response object so that it doesn't halt your script, otherwise it will send the Response and exit the page.
1208
     * 
1209
     * ```php
1210
     * if ($html = $page->load($page->file('index.php'))) {
1211
     *     $page->send($page->display($html));
1212
     * } else {
1213
     *     $page->send(404);
1214
     * }
1215
     * ```
1216
     */
1217 11
    public function send($response = '', $status = 200, array $headers = array())
1218
    {
1219 11
        if (!$response instanceof Response) {
1220 3
            if (func_num_args() == 1 && is_numeric($response)) {
1221 2
                $status = (int) $response;
1222 2
                $response = '';
1223 2
            }
1224 3
            $response = new Response($response, $status, $headers);
1225 3
        }
1226 11
        $status = $response->getStatusCode();
1227 11
        if ($response instanceof RedirectResponse) {
1228 5
            $type = 'redirect';
1229 11
        } elseif ($response instanceof JsonResponse) {
1230 2
            $type = 'json';
1231 6
        } elseif (null === $type = $response->headers->get('Content-Type')) {
1232 3
            $type = ($status == 304) ? $this->url['format'] : 'html';
1233 4
        } elseif (stripos($type, 'html') !== false) {
1234 1
            $type = 'html';
1235 1
        }
1236 11
        $this->process('response', $response, $status, $type);
1237 11
        $response = $response->prepare($this->request)->send();
1238
        
1239 11
        return ($this->testing === false) ? exit : $response;
0 ignored issues
show
Coding Style Compatibility introduced by
The method send() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1240
    }
1241
    
1242
    /**
1243
     * Creates and sends a Symfony JsonResponse object.
1244
     * 
1245
     * @param mixed   $data    The response data.
1246
     * @param integer $status  The response status code.
1247
     */
1248 2
    public function sendJson($data = '', $status = 200)
1249
    {
1250 2
        return $this->send(JsonResponse::create($data, $status));
1251
    }
1252
1253
    /**
1254
     * An internal method we use that comes in handy elsewhere as well.
1255
     * 
1256
     * @param array $files  An array of files that share a common directory somewhere.
1257
     * 
1258
     * @return  The common directory (with trailing slash) shared amongst your $files.
1259
     */
1260 24
    public function commonDir(array $files)
1261
    {
1262 24
        $files = array_values($files);
1263 24
        $cut = 0;
1264 24
        $count = count($files);
1265 24
        $shortest = min(array_map('mb_strlen', $files));
1266 24
        while ($cut < $shortest) {
1267 24
            $char = $files[0][$cut];
1268 24
            for ($i = 1; $i < $count; ++$i) {
1269 24
                if ($files[$i][$cut] !== $char) {
1270 23
                    break 2;
1271
                }
1272 24
            }
1273 24
            ++$cut;
1274 24
        }
1275 24
        $dir = substr($files[0], 0, $cut);
1276 24
        if (false !== $slash = strrpos($dir, '/')) {
1277 24
            $dir = substr($dir, 0, $slash + 1);
1278 24
        } elseif (false !== $slash = strrpos($dir, '\\')) {
1279 1
            $dir = substr($dir, 0, $slash + 1);
1280 1
        }
1281
        
1282 24
        return $dir; // with trailing slash (if any)
1283
    }
1284
1285 19
    protected function process($section, $param, $code = 0, $type = '')
1286
    {
1287
        // Used in $this->send(), $this->display(), $this->styles(), and $this->scripts()
1288 19
        if (!isset($this->filters[$section])) {
1289 17
            return $param;
1290
        }
1291 5
        usort($this->filters[$section], function ($a, $b) {
1292 1
            return $a["order"] - $b["order"];
1293 5
        });
1294 5
        foreach ($this->filters[$section] as $key => $filter) {
1295 4
            if ($section == 'response') {
1296 3
                foreach ($filter['params'] as $response) {
1297 3
                    if (is_numeric($response)) {
1298 2
                        if ($response != $code) {
1299 1
                            continue 2;
1300
                        }
1301 3
                    } elseif (stripos($type, $response) === false) {
1302 1
                        continue 2;
1303
                    }
1304 3
                }
1305 3
                call_user_func($filter['function'], $this, $param, $type); // $page, $response, $type
1306 4
            } elseif ($filter['function'] == 'prepend') {
1307 1
                $param = $filter['params'].$param;
1308 2
            } elseif ($filter['function'] == 'append') {
1309 1
                $param .= $filter['params'];
1310 1
            } else {
1311 2
                $filter['params'][$filter['key']] = $param;
1312 2
                $param = call_user_func_array($filter['function'], $filter['params']);
1313
            }
1314 4
            unset($this->filters[$section][$key]);
1315 5
        }
1316
1317 5
        return $param;
1318
    }
1319
1320 11
    protected function data($type, $value, $prepend)
1321
    {
1322
        // Used in $this->meta() and $this->link()
1323 11
        if ($prepend !== false) {
1324 5
            if (!isset($this->data[$type])) {
1325 2
                $this->data[$type] = array();
1326 2
            }
1327 5
            array_unshift($this->data[$type], $value);
1328 5
        } else {
1329 11
            $this->data[$type][] = $value;
1330
        }
1331 11
    }
1332
1333 9
    protected function metadata()
1334
    {
1335
        // Used in $this->display()
1336 9
        $metadata = array();
1337 9
        $metadata[] = '<meta charset="'.$this->html['charset'].'">';
1338 9
        $metadata[] = '<title>'.trim($this->html['title']).'</title>';
1339 9
        if (!empty($this->html['description'])) {
1340 7
            $metadata[] = '<meta name="description" content="'.trim($this->html['description']).'">';
1341 7
        }
1342 9
        if (!empty($this->html['keywords'])) {
1343 7
            $metadata[] = '<meta name="keywords" content="'.trim($this->html['keywords']).'">';
1344 7
        }
1345 9
        if ($this->robots !== true) {
0 ignored issues
show
The property robots does not exist on object<BootPress\Page\Component>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1346 7
            $metadata[] = ($this->html['robots']) ? '<meta name="robots" content="'.$this->html['robots'].'">' : '<meta name="robots" content="noindex, nofollow">'; // ie. false or null
1347 7
        }
1348 9
        if (isset($this->data['meta'])) {
1349 7
            foreach ($this->data['meta'] as $tag) {
1350 7
                $metadata[] = '<meta '.$tag.'>';
1351 7
            }
1352 7
        }
1353
1354 9
        return $metadata;
1355
    }
1356
1357 9
    protected function styles()
1358
    {
1359
        // Used in $this->display()
1360 9
        $styles = array();
1361 9
        if (isset($this->data['ico'])) {
1362 6
            $styles[] = '<link rel="shortcut icon" href="'.$this->data['ico'].'">';
1363 6
        }
1364 9
        if (isset($this->data['apple'])) {
1365 6
            $styles[] = '<link rel="apple-touch-icon" href="'.$this->data['apple'].'">';
1366 6
        }
1367 9
        if (isset($this->filters['prepend']['css'])) {
1368 1
            $this->link($this->filters['prepend']['css'], 'prepend');
1369 1
        }
1370 9
        if (isset($this->filters['append']['css'])) {
1371 1
            $this->link($this->filters['append']['css']);
1372 1
        }
1373 9
        unset($this->filters['prepend']['css'], $this->filters['append']['css']);
1374 9
        $css = (isset($this->data['css'])) ? $this->data['css'] : array();
1375 9
        $css = $this->process('css', array_unique($css));
1376 9
        foreach ($css as $url) {
1377 7
            $styles[] = '<link rel="stylesheet" href="'.$url.'">';
1378 9
        }
1379 9
        if (isset($this->data['style'])) {
1380 6
            foreach ($this->data['style'] as $style) {
1381 6
                $styles[] = $style;
1382 6
            }
1383 6
        }
1384 9
        if (isset($this->data['other'])) {
1385 6
            foreach ($this->data['other'] as $other) {
1386 6
                $styles[] = $other;
1387 6
            }
1388 6
        }
1389
1390 9
        return $styles;
1391
    }
1392
1393 9
    protected function scripts()
1394
    {
1395
        // Used in $this->display()
1396 9
        $scripts = array();
1397 9
        if (isset($this->filters['prepend']['javascript'])) {
1398 1
            $this->link($this->filters['prepend']['javascript'], 'prepend');
1399 1
        }
1400 9
        $jquery = (isset($this->html['jquery'])) ? $this->html['jquery'] : false;
1401 9
        $code = (isset($this->data['jquery'])) ? $this->data['jquery'] : false;
1402 9
        if ($jquery || $code) {
1403 4
            if (isset($code['ui'])) {
1404 3
                $this->link($code['ui'] ? $code['ui'] : 'https://cdn.jsdelivr.net/jquery.ui/1.11.4/jquery-ui.min.js', 'prepend');
1405 3
                unset($code['ui']);
1406 3
            }
1407 4
            $this->link($jquery ? $jquery : 'https://cdn.jsdelivr.net/jquery/1.12.3/jquery.min.js', 'prepend');
1408 4
            if ($code) {
1409 4
                $code = array_filter(array_unique($code));
1410 4
            }
1411 4
            if (!empty($code)) {
1412 4
                foreach ($code as $key => $value) {
1413 4
                    $code[$key] = $this->indent($value);
1414 4
                }
1415 4
                $this->script('$(document).ready(function(){'."\n".implode("\n", $code)."\n".'});');
1416 4
            }
1417 4
        }
1418 9
        if (isset($this->filters['append']['javascript'])) {
1419 1
            $this->link($this->filters['append']['javascript']);
1420 1
        }
1421 9
        unset($this->filters['prepend']['javascript'], $this->filters['append']['javascript']);
1422 9
        $javascript = (isset($this->data['js'])) ? $this->data['js'] : array();
1423 9
        $javascript = $this->process('javascript', array_unique($javascript));
1424 9
        foreach ($javascript as $url) {
1425 8
            $scripts[] = '<script src="'.$url.'"></script>';
1426 9
        }
1427 9
        if (isset($this->data['script'])) {
1428 7
            foreach (array_unique($this->data['script']) as $script) {
1429 7
                $scripts[] = $script;
1430 7
            }
1431 7
        }
1432
1433 9
        return $scripts;
1434
    }
1435
1436 5
    protected function indent($string, $tab = "\t")
1437
    {
1438
        // Used in $this->style() and $this->script()
1439 5
        $array = preg_split("/\r\n|\n|\r/", trim($string));
1440 5
        $first = $tab.trim(array_shift($array));
1441 5
        if (empty($array)) {
1442 4
            return $first; // ie. no indentation at all
1443
        }
1444 5
        $spaces = array();
1445 5
        foreach ($array as $value) {
1446 5
            $spaces[] = strspn($value, " \t");
1447 5
        }
1448 5
        $spaces = min($spaces);
1449 5
        foreach ($array as $key => $value) {
1450 5
            $array[$key] = $tab.substr($value, $spaces);
1451 5
        }
1452 5
        array_unshift($array, $first);
1453
1454 5
        return implode("\n", $array);
1455
    }
1456
1457 70
    protected function formatLocalPath($url, $array = false)
1458
    {
1459 70
        if (!preg_match('/^((?!((f|ht)tps?:)?\/\/)|'.$this->url['preg'].'?)(['.$this->url['chars'].'\/]+)?(\.[a-z0-9]*)?(.*)$/i', $url, $matches)) {
1460 8
            return ($array) ? array($url, '', '', '') : $url;
1461
        }
1462 63
        list($full, $url, $not, $applicable, $path, $suffix, $query) = $matches;
0 ignored issues
show
The assignment to $full is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
The assignment to $url is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
The assignment to $not is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
The assignment to $applicable is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1463 63
        $url = $this->url['base'];
1464 63
        $path = trim($path, '/');
1465 63
        if ($path == 'index') {
1466 5
            $path = '';
1467 5
        }
1468 63
        if (in_array($suffix, $this->url['html'])) {
1469 56
            $suffix = (!empty($path)) ? $this->url['suffix'] : '';
1470 56
        }
1471
1472 63
        return ($array) ? array($url, $path, $suffix, $query) : $url.$path.$suffix.$query;
1473
    }
1474
1475 43
    protected function __construct()
1476
    {
1477 43
    }
1478
}
1479