Passed
Push — master ( fc199c...9e7def )
by Andreas
21:10
created

midcom_application   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Test Coverage

Coverage 48.98%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 87
dl 0
loc 298
ccs 48
cts 98
cp 0.4898
rs 9.6
c 1
b 0
f 0
wmc 35

19 Methods

Rating   Name   Duplication   Size   Complexity  
A disable_limits() 0 9 3
A relocate() 0 5 1
A header() 0 4 1
A codeinit() 0 9 2
A get_page_prefix() 0 3 1
A __construct() 0 4 1
A get_host_name() 0 3 1
A buildContainer() 0 5 1
A __set() 0 6 2
A registerContainerConfiguration() 0 9 4
A initializeContainer() 0 4 1
A registerBundles() 0 3 1
A __get() 0 6 2
A get_request() 0 3 1
A dynamic_load() 0 42 4
A finish() 0 3 1
A getProjectDir() 0 11 3
A getCacheDir() 0 3 2
A get_host_prefix() 0 10 3
1
<?php
2
/**
3
 * @package midcom
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\HttpKernel\HttpKernelInterface;
11
use Symfony\Component\HttpKernel\Kernel;
12
use Symfony\Component\Config\Loader\LoaderInterface;
13
use Symfony\Component\DependencyInjection\ContainerBuilder;
14
use midcom\bundle\midcomBundle;
15
use Symfony\Component\HttpFoundation\Response;
16
17
/**
18
 * Main controlling instance of the MidCOM Framework
19
 *
20
 * @property midcom_services_i18n $i18n
21
 * @property midcom_helper__componentloader $componentloader
22
 * @property midcom_services_dbclassloader $dbclassloader
23
 * @property midcom_helper__dbfactory $dbfactory
24
 * @property midcom_helper_head $head
25
 * @property midcom_helper_style $style
26
 * @property midcom_services_auth $auth
27
 * @property midcom_services_permalinks $permalinks
28
 * @property midcom_services_toolbars $toolbars
29
 * @property midcom_services_uimessages $uimessages
30
 * @property midcom_services_metadata $metadata
31
 * @property midcom_services_rcs $rcs
32
 * @property midcom_services__sessioning $session
33
 * @property midcom_services_indexer $indexer
34
 * @property midcom_config $config
35
 * @property midcom_services_cache $cache
36
 * @property Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
37
 * @property midcom_debug $debug
38
 * @package midcom
39
 */
40
class midcom_application extends Kernel
41
{
42
    private ?Request $request = null;
43
44
    /**
45
     * Set this variable to true during the handle phase of your component to
46
     * not show the site's style around the component output. This is mainly
47
     * targeted at XML output like RSS feeds and similar things. The output
48
     * handler of the site, excluding the style-init/-finish tags will be executed
49
     * immediately after the handle phase
50
     *
51
     * Changing this flag after the handle phase or for dynamically loaded
52
     * components won't change anything.
53
     */
54
    public bool $skip_page_style = false;
55
56
    private ?string $project_dir = null;
57
58
    private midcom_config $cfg;
59
60
    public function __construct(string $environment, bool $debug)
61
    {
62
        $this->cfg = new midcom_config;
63
        parent::__construct($environment, $debug);
64
    }
65
66 44
    private function get_request() : Request
67
    {
68 44
        return $this->request ??= Request::createFromGlobals();
69
    }
70
71
    public function registerContainerConfiguration(LoaderInterface $loader)
72
    {
73
        if (file_exists($this->getProjectDir() . '/config/services.yml')) {
74
            $loader->load($this->getProjectDir() . '/config/services.yml');
75
        }
76
        if ($classes = midcom::get_registered_service_classes()) {
77
            $loader->load(function (ContainerBuilder $container) use ($classes) {
78
                foreach ($classes as $id => $class) {
79
                    $container->findDefinition($id)->setClass($class);
80
                }
81
            });
82
        }
83
    }
84
85
    protected function initializeContainer() : void
86
    {
87
        parent::initializeContainer();
88
        $this->container->set('config', $this->cfg);
89
    }
90
91
    protected function buildContainer() : ContainerBuilder
92
    {
93
        $container = parent::buildContainer();
94
        $this->cfg->export_to($container);
95
        return $container;
96
    }
97
98
    public function registerBundles() : iterable
99
    {
100
        return [new midcomBundle];
101
    }
102
103 2
    public function getProjectDir() : string
104
    {
105 2
        if ($this->project_dir === null) {
106
            if (basename(dirname(__DIR__, 4)) === 'vendor') {
107
                // this is the case where we're installed as a dependency
108
                $this->project_dir = dirname(__DIR__, 5);
109
            } else {
110
                $this->project_dir = dirname(__DIR__, 2);
111
            }
112
        }
113 2
        return $this->project_dir;
114
    }
115
116 363
    public function getCacheDir() : string
117
    {
118 363
        return $this->cfg->get('cache_base_directory') ?: parent::getCacheDir();
119
    }
120
121
    /**
122
     * Magic getter for service loading
123
     */
124 730
    public function __get($key)
125
    {
126 730
        if (!$this->booted) {
127
            $this->boot();
128
        }
129 730
        return $this->getContainer()->get($key);
130
    }
131
132
    /**
133
     * Magic setter
134
     */
135
    public function __set($key, $value)
136
    {
137
        if (!$this->booted) {
138
            $this->boot();
139
        }
140
        $this->getContainer()->set($key, $value);
141
    }
142
143
    /* *************************************************************************
144
     * Control framework:
145
     * codeinit      - Handle the current request
146
     * dynamic_load   - Dynamically load and execute a URL
147
     * finish         - Cleanup Work
148
     */
149
150
    /**
151
     * Initialize the URL parser and process the request.
152
     *
153
     * This function must be called before any output starts.
154
     */
155
    public function codeinit()
156
    {
157
        $request = $this->get_request();
158
        try {
159
            $response = $this->handle($request);
160
            $response->send();
161
            $this->terminate($request, $response);
162
        } catch (Error $e) {
163
            $this->getHttpKernel()->terminateWithException($e);
0 ignored issues
show
Bug introduced by
The method terminateWithException() does not exist on Symfony\Component\HttpKernel\HttpKernelInterface. It seems like you code against a sub-type of Symfony\Component\HttpKernel\HttpKernelInterface such as Symfony\Component\HttpKernel\HttpKernel. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

163
            $this->getHttpKernel()->/** @scrutinizer ignore-call */ terminateWithException($e);
Loading history...
164
        }
165
    }
166
167
    /**
168
     * Dynamically execute a subrequest and insert its output in place of the
169
     * function call.
170
     *
171
     * It tries to load the component referenced with the URL $url and executes
172
     * it as if it was used as primary component.
173
     *
174
     * This is only possible if the system is in the Page-Style output phase. It
175
     * cannot be used within code-init or during the output phase of another
176
     * component.
177
     *
178
     * Example code, executed on a site's homepage, it will load the news listing from
179
     * the given URL and display it using a substyle of the node style that is assigned
180
     * to the loaded one:
181
     *
182
     * <code>
183
     * $blog = '/blog/latest/3/';
184
     * $substyle = 'homepage';
185
     * midcom::get()->dynamic_load("/midcom-substyle-{$substyle}/{$blog}");
186
     * </code>
187
     *
188
     * Results of dynamic_loads are cached with the system cache strategy
189
     *
190
     * @param string $url                The URL, relative to the Midgard Page, that is to be requested.
191
     */
192 17
    public function dynamic_load(string $url, string $substyle = '')
193
    {
194 17
        debug_add("Dynamic load of URL {$url}");
195 17
        $url = midcom_connection::get_url('prefix') . $url;
196
197
        // Determine new Context ID and set current context,
198
        // enter that context and prepare its data structure.
199 17
        $oldcontext = midcom_core_context::get();
200 17
        $context = midcom_core_context::enter($url, $oldcontext->get_key(MIDCOM_CONTEXT_ROOTTOPIC));
0 ignored issues
show
Bug introduced by
It seems like $oldcontext->get_key(MIDCOM_CONTEXT_ROOTTOPIC) can also be of type false; however, parameter $topic of midcom_core_context::enter() does only seem to accept midcom_db_topic|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

200
        $context = midcom_core_context::enter($url, /** @scrutinizer ignore-type */ $oldcontext->get_key(MIDCOM_CONTEXT_ROOTTOPIC));
Loading history...
201 17
        if ($substyle) {
202
            $context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $substyle);
203
        }
204
205 17
        $request = $this->get_request()->duplicate([], attributes: []);
206 17
        $request->attributes->set('context', $context);
207
208 17
        $cached = $this->cache->content->check_dl_hit($request);
209 17
        if ($cached !== false) {
210
            echo $cached;
211
            midcom_core_context::leave();
212
            return;
213
        }
214
215 17
        $backup = $this->skip_page_style;
216 17
        $this->skip_page_style = true;
217
        try {
218 17
            $response = $this->handle($request, HttpKernelInterface::SUB_REQUEST, false);
219 15
        } catch (midcom_error_notfound | midcom_error_forbidden $e) {
220 15
            $e->log();
221 15
            midcom_core_context::leave();
222 15
            return;
223
        } finally {
224 17
            $this->skip_page_style = $backup;
225
        }
226
227 4
        $dl_cache_data = $response->getContent();
228 4
        echo $dl_cache_data;
229
230
        /* Cache DL the content */
231 4
        $this->cache->content->store_dl_content($context->id, $dl_cache_data, $request);
232
233 4
        midcom_core_context::leave();
234
    }
235
236
    /**
237
     * Stop the PHP process
238
     *
239
     * @deprecated
240
     */
241
    public function finish()
242
    {
243
        _midcom_stop_request();
244
    }
245
246
    /* *************************************************************************
247
     * Framework Access Helper functions
248
     */
249
250
    /**
251
     * Retrieves the name of the current host, fully qualified with protocol and
252
     * port (http[s]://www.my.domain.com[:1234])
253
     */
254 29
    function get_host_name() : string
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
255
    {
256 29
        return $this->get_request()->getSchemeAndHttpHost();
257
    }
258
259
    /**
260
     * Return the prefix required to build relative links on the current site.
261
     * This includes the http[s] prefix, the hosts port (if necessary) and the
262
     * base url of the Midgard Page. Be aware, that this does *not* point to the
263
     * base host of the site.
264
     *
265
     * e.g. something like http[s]://www.domain.com[:8080]/host_prefix/page_prefix/
266
     */
267
    function get_page_prefix() : string
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
268
    {
269
        return $this->get_host_name() . midcom_connection::get_url('self');
270
    }
271
272
    /**
273
     * Return the prefix required to build relative links on the current site.
274
     * This includes the http[s] prefix, the hosts port (if necessary) and the
275
     * base url of the main host. This is not necessarily the currently active
276
     * MidCOM Page however, use the get_page_prefix() function for that.
277
     *
278
     * e.g. something like http[s]://www.domain.com[:8080]/host_prefix/
279
     */
280 7
    function get_host_prefix() : string
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
281
    {
282 7
        $host_prefix = midcom_connection::get_url('prefix');
283 7
        if (!str_starts_with($host_prefix, '/')) {
0 ignored issues
show
Bug introduced by
It seems like $host_prefix can also be of type null; however, parameter $haystack of str_starts_with() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

283
        if (!str_starts_with(/** @scrutinizer ignore-type */ $host_prefix, '/')) {
Loading history...
284 7
            $host_prefix = '/' . $host_prefix;
285
        }
286 7
        if (!str_ends_with($host_prefix, '/')) {
287
            $host_prefix .= '/';
288
        }
289 7
        return $this->get_host_name() . $host_prefix;
290
    }
291
292
    /* *************************************************************************
293
     * Generic Helper Functions not directly related with MidCOM:
294
     *
295
     * relocate           - executes a HTTP relocation to the given URL
296
     */
297
298
    /**
299
     * Sends a header out to the client.
300
     *
301
     * This function is syntactically identical to
302
     * the regular PHP header() function, but is integrated into the framework. Every
303
     * Header you sent must go through this function or it might be lost later on;
304
     * this is especially important with caching.
305
     */
306 17
    public function header(string $header, int $response_code = 0)
307
    {
308 17
        $this->cache->content->register_sent_header($header);
309 17
        midcom_compat_environment::header($header, http_response_code: $response_code);
310
    }
311
312
    /**
313
     * Relocate to another URL.
314
     *
315
     * Note, that this function automatically makes the page uncacheable, calls
316
     * midcom_finish and exit, so it will never return. If the headers have already
317
     * been sent, this will leave you with a partially completed page, so beware.
318
     */
319
    public function relocate(string $url, int $response_code = Response::HTTP_FOUND)
320
    {
321
        $response = new midcom_response_relocate($url, $response_code);
322
        $response->send();
323
        $this->finish();
0 ignored issues
show
Deprecated Code introduced by
The function midcom_application::finish() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

323
        /** @scrutinizer ignore-deprecated */ $this->finish();
Loading history...
324
    }
325
326
    /**
327
     * Raise some PHP limits for resource-intensive tasks
328
     */
329 8
    public function disable_limits()
330
    {
331 8
        $stat = ini_set('max_execution_time', $this->config->get('midcom_max_execution_time'));
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('midcom_max_execution_time') can also be of type null; however, parameter $value of ini_set() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

331
        $stat = ini_set('max_execution_time', /** @scrutinizer ignore-type */ $this->config->get('midcom_max_execution_time'));
Loading history...
332 8
        if (false === $stat) {
333
            debug_add('ini_set("max_execution_time", ' . $this->config->get('midcom_max_execution_time') . ') returned false', MIDCOM_LOG_WARN);
334
        }
335 8
        $stat = ini_set('memory_limit', $this->config->get('midcom_max_memory'));
336 8
        if (false === $stat) {
337
            debug_add('ini_set("memory_limit", ' . $this->config->get('midcom_max_memory') . ') returned false', MIDCOM_LOG_WARN);
338
        }
339
    }
340
}
341