Completed
Push — trunk ( d79498...d7aafa )
by SuperNova.WS
04:22
created

sn_module::isInstalled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Modules;
4
5
use Common\Hooker\Pimp;
6
use Exception;
7
use SN;
8
use template;
9
10
class sn_module {
11
  // Manifest params
12
  const M_REQUIRE = 'require';
13
  const M_ROOT_RELATIVE = 'root_relative';
14
  const M_LOAD_ORDER = 'load_order';
15
16
  /**
17
   * SN version in which module was committed. Can be treated as version in which module guaranteed to work
18
   * @var string $versionCommitted
19
   */
20
  public $versionCommitted = '#43a15.29#';
21
  /**
22
   * Is module currently active?
23
   *
24
   * @var bool $active
25
   */
26
  protected $active = true;
27
  /**
28
   * Is all module DB artifacts presents?
29
   *
30
   * Check for module's tables, settings etc
31
   *
32
   * @var bool $installed
33
   */
34
  protected $installed = true;
35
36
  public $manifest = [
37
    'package'   => 'core',
38
    'name'      => 'Modules\sn_module',
39
    'version'   => '1c0',
40
    'copyright' => 'Project "SuperNova.WS" #43a15.29# copyright © 2009-2018 Gorlum',
41
42
    self::M_LOAD_ORDER => MODULE_LOAD_ORDER_DEFAULT,
43
44
    self::M_REQUIRE       => [],
45
    self::M_ROOT_RELATIVE => '',
46
47
    // 'constants' array - contents of this array would be defined in SN
48
    'constants' => [
49
//      'UNIT_STRUCTURE_NEW' => 999999,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
50
    ],
51
52
    'vars'      => [], // Just a placeholder. vars assigned via special method __assign_vars(). Need 'cause of new constants that can be defined within module. See below
53
54
    // 'functions' array - this functions would be installed as hooks
55
    // Key: overwritable function name to replace
56
    // Value: which method to use. Format: [*][<object_name>][.]<method>
57
    // '*' means that new function would replace old one
58
    // If object_name is ommited but "." is present - hook linked to global function
59
    // If only "method" present - overwritable linked to appropriate method of current object
60
    // Function/Method should be accessible on module init
61
    'functions' => [
62
//      'test_object_test_method' => 'test_object.test_method',
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
63
//      'test_function' => '.my_test_function',
64
//      'this_object_test_method' => 'test_method',
65
    ],
66
67
    // 'menu' array - this menu items would be merged into main game menu
68
    // Array element almost identical to $sn_menu with additional param 'LOCATION'.
69
    // 'LOCATION' => '-news', // Special atrtribute for modules
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
70
    // [-|+][<menu_item_id>]
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
71
    // <menu_item_id> identifies menu item aginst new menu item would be placed. When ommited new item placed against whole menu
72
    // -/+ indicates that new item should be placed before/after identified menu item (or whole menu). If ommited and menu item exists - new item will replace previous one
73
    // Empty or non-existent LOCATION equivalent to '+' - place item at end of menu
74
    // Non-existent menu_item_id treated as ommited
75
    'menu'      => [],
76
77
    // 'page' array - defines pages which will handle this module and appropriate handlers
78
    'page'      => [],
79
80
    /**
81
     * 'mvc' subarray
82
     * [
83
     *    FIELD_MODEL =>
84
     *    FIELD_VIEW =>
85
     *    MVC_OPTIONS =>
86
     * ]
87
     */
88
    'mvc'       => [],
89
  ];
90
91
  /**
92
   * New way to add functions instead of manifest['functions']
93
   *
94
   * [
95
   *   (string)$functionName => [
96
   *     (callable)$callable,
97
   *     (string)'methodName',                             // Local method name aka $this->methodName
98
   *     (callable array)[$this|objectName, 'methodName'], // Callable array
99
   *   ],
100
   * ]
101
   *
102
   * @var array $functions
103
   */
104
  protected $functions = [];
105
106
  protected $hooks = [];
107
108
  protected $config = array();
109
110
  protected $module_full_class_path = __FILE__;
111
112
  protected $filename = '';
113
114
  /**
115
   * @param string   $functionName
116
   * @param callable $callable
117
   */
118
  public function addFunctionHook($functionName, $callable) {
119
    $this->functions[$functionName][] = $callable;
120
  }
121
122
  /**
123
   * Динамическое назначение переменных
124
   *
125
   * Актуально, когда записываемые данные зависят от статуса игры
126
   * Например - назначаются константы внутри модуля
127
   *
128
   * @return array
129
   */
130
  protected function __assign_vars() {
131
    return array(/*
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
132
      'sn_data' => array(
133
        UNIT_STRUCTURE_NEW => array( // Will honor new constants
134
          'name' => 'robot_factory',
135
          'type' => UNIT_STRUCTURE,
136
          'location' => LOC_PLANET,
137
          'cost' => array(
138
            RES_METAL     => 400,
139
            RES_CRYSTAL   => 120,
140
            RES_DEUTERIUM => 200,
141
            RES_ENERGY    => 0,
142
            'factor' => 2,
143
          ),
144
          'metal' => 400,
145
          'crystal' => 120,
146
          'deuterium' => 200,
147
          'energy' => 0,
148
          'factor' => 2,
149
        ),
150
      ),
151
*/
152
    );
153
  }
154
155
  /**
156
   * sn_module constructor.
157
   *
158
   * @param string $filename
159
   */
160
  public function __construct($filename = __FILE__) {
161
    $this->filename = $filename;
162
163
    // Getting module PHP class name
164
    $class_module_name = get_called_class();
165
166
    // Validating source settings. Some fields are mandatory in each manifest
167
    // Should be removed when manifest would be parsed to separate fields
168
    foreach (['name', 'package', 'version'] as $mandatoryManifest) {
169
      if (!array_key_exists($mandatoryManifest, $this->manifest)) {
170
        throw new Exception('{ There is no mandatory field "' . $mandatoryManifest . '" in manifest of module } "' . $class_module_name . '"');
171
      }
172
    }
173
174
    // Getting module root relative to SN
175
    $this->manifest[static::M_ROOT_RELATIVE] = str_replace(
176
      [SN_ROOT_PHYSICAL, basename($this->filename)],
177
      '',
178
      str_replace('\\', '/', $this->filename)
179
    );
180
181
    // TODO: Load configuration from DB. Manifest setting
182
    // Trying to load configuration from file
183
    $config_exists = false;
184
    // Конфигурация может лежать в config_path в манифеста или в корне модуля
185
    if (isset($this->manifest['config_path']) && file_exists($config_filename = $this->manifest['config_path'] . '/config.php')) {
186
      $config_exists = true;
187
    } elseif (file_exists($config_filename = dirname($filename) . '/config.php')) {
188
      $config_exists = true;
189
    }
190
191
    if ($config_exists) {
192
      include($config_filename);
193
      $module_config_array = $class_module_name . '_config';
194
      $this->config = $$module_config_array;
195
    }
196
197
    // Registering classes with autoloader
198
    \Core\Autoloader::register($this->getRootRelative() . 'classes/');
199
200
    // TODO - currently not possible because each module is not a service
201
    // When it's done - remove double registration from loadModulesFromDirectory()
202
    // Registering module in manager
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
203
//    SN::$gc->modules->registerModule($class_module_name, $this);
204
  }
205
206
  protected function __patch_menu(&$sn_menu_extra, &$menu_patch, $admin = false) {
207
    if (isset($menu_patch) && is_array($menu_patch) && !empty($menu_patch)) {
208
      foreach ($menu_patch as $menu_item_name => $menu_item_data) {
209
        $sn_menu_extra[$menu_item_name] = $menu_item_data;
210
      }
211
    }
212
  }
213
214
215
  /**
216
   *
217
   */
218
  public function initialize() {
219
    // Checking module status - is it installed and active
220
    $this->check_status();
221
    if (!$this->isActive()) {
222
      return;
223
    }
224
225
    // Setting constants - if any
226
    if (isset($this->manifest['constants']) && is_array($this->manifest['constants']) && !empty($this->manifest['constants'])) {
227
      foreach ($this->manifest['constants'] as $constant_name => $constant_value) {
228
        defined($constant_name) or define($constant_name, $constant_value);
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
229
      }
230
    }
231
232
    // Adding vars - if any
233
    // Due to possible introduce of new constants in previous step vars is assigned via special method to honor new constants
234
    // Assignation can work with simple variables and with multidimensional arrays - for ex. 'sn_data[groups][test]'
235
    // New values from module variables will overwrite previous values (for root variables) and array elements with corresponding indexes (for arrays)
236
    // Constants as array indexes are honored - it's make valid such declarations as 'sn_data[ques][QUE_STRUCTURES]'
237
    $this->manifest['vars'] = $this->__assign_vars();
238
    if (!empty($this->manifest['vars'])) {
239
      $vars_assigned = array();
240
      foreach ($this->manifest['vars'] as $var_name => $var_value) {
241
        $sub_vars = explode('[', str_replace(']', '', $var_name));
242
        $var_name = $sub_vars[0];
243
244
        if (!isset($vars_assigned[$var_name])) {
245
          $vars_assigned[$var_name] = true;
246
          global $$var_name;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
247
        }
248
249
        $pointer = &$$var_name;
250
        if (($n = count($sub_vars)) > 1) {
251
          for ($i = 1; $i < $n; $i++) {
252
            if (defined($sub_vars[$i])) {
253
              $sub_vars[$i] = constant($sub_vars[$i]);
254
            }
255
256
            if (!isset($pointer[$sub_vars[$i]]) && $i != $n) {
257
              $pointer[$sub_vars[$i]] = array();
258
            }
259
            $pointer = &$pointer[$sub_vars[$i]];
260
          }
261
        }
262
263
        if (!isset($pointer) || !is_array($pointer)) {
264
          $pointer = $var_value;
265
        } elseif (is_array($$var_name)) {
266
          $pointer = array_merge_recursive_numeric($pointer, $var_value);
267
        }
268
      }
269
    }
270
271
    // Overriding function if any
272
    global $functions;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
273
    sn_sys_handler_add($functions, $this->manifest['functions'], $this);
274
275
    foreach ($this->functions as $functionName => $callableList) {
276
      !is_array($callableList) ? $callableList = [$callableList] : false;
277
      foreach ($callableList as $callable) {
278
        sys_handler_add_one($functions, $functionName, $callable, static::class, '');
279
      }
280
    }
281
282
    $this->registerHooks();
283
284
    // Patching game menu - if any
285
    global $sn_menu_extra, $sn_menu_admin_extra;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
286
    isset($this->manifest['menu']) and $this->__patch_menu($sn_menu_extra, $this->manifest['menu']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
287
    isset($this->manifest['menu_admin']) and $this->__patch_menu($sn_menu_admin_extra, $this->manifest['menu_admin'], true);
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
288
289
    global $sn_mvc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
290
    foreach ($sn_mvc as $handler_type => &$handler_data) {
291
      if ($handler_type == MVC_OPTIONS) {
292
        continue;
293
      }
294
      sn_sys_handler_add($handler_data, $this->manifest['mvc'][$handler_type], $this, $handler_type);
295
    }
296
297
    if (!empty($this->manifest['mvc'][MVC_OPTIONS])) {
298
      foreach ($this->manifest['mvc'][MVC_OPTIONS] as $pageName => $pageOptions) {
299
        if (empty($pageOptions)) {
300
          continue;
301
        }
302
303
        !is_array($sn_mvc['pages'][$pageName][MVC_OPTIONS]) ? $sn_mvc['pages'][$pageName][MVC_OPTIONS] = [] : false;
304
        $sn_mvc['pages'][$pageName][MVC_OPTIONS] = array_merge($sn_mvc['pages'][$pageName][MVC_OPTIONS], $pageOptions);
305
      }
306
    }
307
308
    if (isset($this->manifest['i18n']) && is_array($this->manifest['i18n']) && !empty($this->manifest['i18n'])) {
309
      foreach ($this->manifest['i18n'] as $i18n_page_name => &$i18n_file_list) {
310
        foreach ($i18n_file_list as &$i18n_file_data) {
311
          if (is_array($i18n_file_data) && !$i18n_file_data['path']) {
312
            $i18n_file_data['path'] = $this->getRootRelative();
313
          }
314
        }
315
        if (!isset($sn_mvc['i18n'][$i18n_page_name])) {
316
          $sn_mvc['i18n'][$i18n_page_name] = array();
317
        }
318
        $sn_mvc['i18n'][$i18n_page_name] += $i18n_file_list;
319
      }
320
    }
321
322
    if (!empty($this->manifest['javascript']) && is_array($this->manifest['javascript'])) {
323
      foreach ($this->manifest['javascript'] as $javascript_page_name => &$javascript_list) {
324
        !isset($sn_mvc['javascript'][$javascript_page_name]) ? $sn_mvc['javascript'][$javascript_page_name] = array() : false;
325
        foreach ($javascript_list as $script_name => &$script_content) {
326
          $sn_mvc['javascript'][$javascript_page_name][$script_name] = $script_content;
327
        }
328
      }
329
    }
330
331
    if (!empty($this->manifest['css']) && is_array($this->manifest['css'])) {
332
      foreach ($this->manifest['css'] as $javascript_page_name => &$javascript_list) {
333
        !isset($sn_mvc['css'][$javascript_page_name]) ? $sn_mvc['css'][$javascript_page_name] = array() : false;
334
        foreach ($javascript_list as $script_name => &$script_content) {
335
          $sn_mvc['css'][$javascript_page_name][$script_name] = $script_content;
336
        }
337
      }
338
    }
339
340
    if (!empty($this->manifest['navbar_prefix_button']) && is_array($this->manifest['navbar_prefix_button'])) {
341
      foreach ($this->manifest['navbar_prefix_button'] as $button_image => $button_url_relative) {
342
        $sn_mvc['navbar_prefix_button'][$button_image] = $button_url_relative;
343
      }
344
    }
345
346
    if (!empty($this->manifest['navbar_main_button']) && is_array($this->manifest['navbar_main_button'])) {
347
      foreach ($this->manifest['navbar_main_button'] as $button_image => $button_url_relative) {
348
        $sn_mvc['navbar_main_button'][$button_image] = $button_url_relative;
349
      }
350
    }
351
  }
352
353
  public function check_status() {
354
  }
355
356
  /**
357
   * Checks if module is active
358
   *
359
   * @return bool
360
   */
361
  public function isActive() {
362
    return !empty($this->active) && $this->isInstalled();
363
  }
364
365
  /**
366
   * Checks if module is installed
367
   *
368
   * @return bool
369
   */
370
  public function isInstalled() {
371
    return !empty($this->installed);
372
  }
373
374
  /**
375
   * Register pages in $manifest['mvc']['pages'] for further use
376
   *
377
   * @param string[] $pages - array of records ['pageName' => 'pageFile']. 'pageFile' is currently unused
378
   *
379
   * @deprecated
380
   */
381
  protected function __mvcRegisterPagesOld($pages) {
382
    !is_array($this->manifest['mvc']['pages']) ? $this->manifest['mvc']['pages'] = [] : false;
383
    if (is_array($pages) && !empty($pages)) {
384
      $this->manifest['mvc']['pages'] = array_merge($this->manifest['mvc']['pages'], $pages);
385
    }
386
  }
387
388
  protected function registerHooks() {
389
    foreach ($this->hooks as $hookName => $hookRecord) {
390
      // Priority can be first element of hook array
391
      $priority = Pimp::ORDER_AS_IS;
392
      if (is_array($hookRecord) && count($hookRecord) > 1 && is_numeric(reset($hookRecord))) {
393
        $priority = intval(reset($hookRecord));
394
        array_shift($hookRecord);
395
      }
396
397
      // There is 2 elements in callable array
398
      if (is_array($hookRecord) && 2 == count($hookRecord)) {
399
        // Checking - if first should be replaced with $this
400
        if (THIS_STRING === reset($hookRecord)) {
401
          $hookRecord = [$this, end($hookRecord)];
402
        }
403
      }
404
405
      SN::$gc->pimp->register($hookName, $hookRecord, $priority);
406
    }
407
  }
408
409
  public function getLoadOrder() {
410
    return !empty($this->manifest[self::M_LOAD_ORDER]) ? $this->manifest[self::M_LOAD_ORDER] : MODULE_LOAD_ORDER_DEFAULT;
411
  }
412
413
  public function getRootRelative() {
414
    if (empty($this->manifest[static::M_ROOT_RELATIVE])) {
415
      $this->manifest[static::M_ROOT_RELATIVE] = str_replace([SN_ROOT_PHYSICAL, basename($this->filename)], '', str_replace('\\', '/', $this->filename));
416
    }
417
418
    return $this->manifest[static::M_ROOT_RELATIVE];
419
  }
420
421
  protected function getTemplateRootRelative() {
422
    return $this->getRootRelative() . 'design/templates/';
423
  }
424
425
  /**
426
   *
427
   * Should stay public due using in Festivals (?)
428
   *
429
   * @param string   $templateName
430
   * @param template $template
431
   *
432
   * @return template
433
   */
434
  public function addModuleTemplate($templateName, $template) {
435
    return gettemplate($templateName, $template, $this->getTemplateRootRelative());
436
  }
437
438
}
439