Completed
Push — work-fleets ( 21e5a3...e0f70d )
by SuperNova.WS
05:07
created

template_compile::minify()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 25
rs 8.5806
cc 4
eloc 14
nc 3
nop 1
1
<?php
2
/**
3
*
4
* @package phpBB3
5
* @version $Id$
6
* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc
7
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
8
*
9
* Modified by Gorlum to work within http://supernova.ws
10
*
11
*/
12
13
/**
14
* @ignore
15
*/
16
17
defined('INSIDE') || die();
18
19
/**
20
* Extension of template class - Functions needed for compiling templates only.
21
*
22
* psoTFX, phpBB Development Team - Completion of file caching, decompilation
23
* routines and implementation of conditionals/keywords and associated changes
24
*
25
* The interface was inspired by PHPLib templates,  and the template file (formats are
26
* quite similar)
27
*
28
* The keyword/conditional implementation is currently based on sections of code from
29
* the Smarty templating engine (c) 2001 ispi of Lincoln, Inc. which is released
30
* (on its own and in whole) under the LGPL. Section 3 of the LGPL states that any code
31
* derived from an LGPL application may be relicenced under the GPL, this applies
32
* to this source
33
*
34
* DEFINE directive inspired by a request by Cyberalien
35
*
36
* @package phpBB3
37
*/
38
class template_compile
39
{
40
  var $template;
41
42
  // Various storage arrays
43
  var $block_names = array();
44
  var $block_else_level = array();
45
46
  /**
47
  * constuctor
48
  */
49
  function template_compile(&$template)
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...
50
  {
51
    $this->template = &$template;
52
  }
53
54
  /**
55
  * Load template source from file
56
  * @access private
57
  */
58
  function _tpl_load_file($handle, $store_in_db = false)
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...
59
  {
60
    // Try and open template for read
61
    if (!file_exists($this->template->files[$handle]))
62
    {
63
      if (!file_exists($this->template->files_inherit[$handle]))
64
      {
65
        return;
66
        trigger_error("template->_tpl_load_file(): File {$this->template->files[$handle]} does not exist or is empty", E_USER_ERROR);
0 ignored issues
show
Unused Code introduced by
trigger_error("template-... empty", E_USER_ERROR); does not seem to be reachable.

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

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

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

    return false;
}

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

Loading history...
67
      }
68
      else
69
      {
70
        $this->template->files[$handle] = $this->template->files_inherit[$handle];
71
      }
72
    }
73
74
    $html = $this->minify(@file_get_contents($this->template->files[$handle]));
75
76
    $this->template->compiled_code[$handle] = $this->compile(trim($html));
77
78
    // Actually compile the code now.
79
    $this->compile_write($handle, $this->template->compiled_code[$handle]);
80
81
    // Store in database if required...
82
    if ($store_in_db)
83
    {
84
      global $db, $user;
85
86
      $sql_ary = array(
87
        'template_id'     => $this->template->files_template[$handle],
88
        'template_filename'   => $this->template->filename[$handle],
89
        'template_included'   => '',
90
        'template_mtime'    => time(),
91
        'template_data'     => trim(@file_get_contents($this->template->files[$handle])),
92
      );
93
94
      $sql = 'INSERT INTO ' . STYLES_TEMPLATE_DATA_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
95
      $db->sql_query($sql);
96
    }
97
  }
98
99
  /**
100
  * Remove any PHP tags that do not belong, these regular expressions are derived from
101
  * the ones that exist in zend_language_scanner.l
102
  * @access private
103
  */
104
  function remove_php_tags(&$code)
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...
105
  {
106
    // This matches the information gathered from the internal PHP lexer
107
    $match = array(
108
      '#<([\?%])=?.*?\1>#s',
109
      '#<script\s+language\s*=\s*(["\']?)php\1\s*>.*?</script\s*>#s',
110
      '#<\?php(?:\r\n?|[ \n\t]).*?\?>#s'
111
    );
112
113
    $code = preg_replace($match, '', $code);
114
  }
115
116
  /**
117
  * The all seeing all doing compile method. Parts are inspired by or directly from Smarty
118
  * @access private
119
  */
120
  function compile($code, $no_echo = false, $echo_var = '')
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...
121
  {
122
    if ($echo_var)
123
    {
124
      global $$echo_var;
125
    }
126
127
    // Remove any "loose" php ... we want to give admins the ability
128
    // to switch on/off PHP for a given template. Allowing unchecked
129
    // php is a no-no. There is a potential issue here in that non-php
130
    // content may be removed ... however designers should use entities
131
    // if they wish to display < and >
132
    $this->remove_php_tags($code);
133
134
    // Pull out all block/statement level elements and separate plain text
135
    preg_match_all('#<!-- PHP -->(.*?)<!-- ENDPHP -->#s', $code, $matches);
136
    $php_blocks = $matches[1];
137
    $code = preg_replace('#<!-- PHP -->.*?<!-- ENDPHP -->#s', '<!-- PHP -->', $code);
138
139
    preg_match_all('#<!-- INCLUDE (\{\$?[A-Z0-9\-_]+\}|[a-zA-Z0-9\_\-\+\./]+) -->#', $code, $matches);
140
    $include_blocks = $matches[1];
141
    if($include_blocks)
142
    {
143
      foreach($include_blocks as &$included_file)
144
      {
145
        $included_file .= '.tpl.html';
146
      }
147
    }
148
    $code = preg_replace('#<!-- INCLUDE (?:\{\$?[A-Z0-9\-_]+\}|[a-zA-Z0-9\_\-\+\./]+) -->#', '<!-- INCLUDE -->', $code);
149
150
    preg_match_all('#<!-- INCLUDEPHP ([a-zA-Z0-9\_\-\+\./]+) -->#', $code, $matches);
151
    $includephp_blocks = $matches[1];
152
    $code = preg_replace('#<!-- INCLUDEPHP [a-zA-Z0-9\_\-\+\./]+ -->#', '<!-- INCLUDEPHP -->', $code);
153
154
    preg_match_all('#<!-- ([^<].*?) (.*?)? ?-->#', $code, $blocks, PREG_SET_ORDER);
155
156
    $text_blocks = preg_split('#<!-- [^<].*? (?:.*?)? ?-->#', $code);
157
158
    for ($i = 0, $j = sizeof($text_blocks); $i < $j; $i++)
159
    {
160
      $this->compile_var_tags($text_blocks[$i]);
161
    }
162
    $compile_blocks = array();
163
164
    for ($curr_tb = 0, $tb_size = sizeof($blocks); $curr_tb < $tb_size; $curr_tb++)
165
    {
166
      $block_val = &$blocks[$curr_tb];
167
168
      switch ($block_val[1])
169
      {
170
        case 'BEGIN':
171
          $this->block_else_level[] = false;
172
          $compile_blocks[] = '<?php ' . $this->compile_tag_block($block_val[2]) . ' ?>';
173
        break;
174
175
        case 'BEGINELSE':
176
          $this->block_else_level[sizeof($this->block_else_level) - 1] = true;
177
          $compile_blocks[] = '<?php }} else { ?>';
178
        break;
179
180
        case 'END':
181
          array_pop($this->block_names);
182
          $compile_blocks[] = '<?php ' . ((array_pop($this->block_else_level)) ? '}' : '}}') . ' ?>';
183
        break;
184
185
        case 'IF':
186
          $compile_blocks[] = '<?php ' . $this->compile_tag_if($block_val[2], false) . ' ?>';
187
        break;
188
189
        case 'ELSE':
190
          $compile_blocks[] = '<?php } else { ?>';
191
        break;
192
193
        case 'ELSEIF':
194
          $compile_blocks[] = '<?php ' . $this->compile_tag_if($block_val[2], true) . ' ?>';
195
        break;
196
197
        case 'ENDIF':
198
          $compile_blocks[] = '<?php } ?>';
199
        break;
200
201
        case 'DEFINE':
202
          $compile_blocks[] = '<?php ' . $this->compile_tag_define($block_val[2], true) . ' ?>';
203
        break;
204
205
        case 'UNDEFINE':
206
          $compile_blocks[] = '<?php ' . $this->compile_tag_define($block_val[2], false) . ' ?>';
207
        break;
208
209
        case 'INCLUDE':
210
          $temp = array_shift($include_blocks);
211
212
          // Dynamic includes
213
          // Cheap match rather than a full blown regexp, we already know
214
          // the format of the input so just use string manipulation.
215
          if ($temp[0] == '{')
216
          {
217
            $file = false;
218
219
            if ($temp[1] == '$')
220
            {
221
              $var = substr($temp, 2, -1);
222
              //$file = $this->template->_tpldata['DEFINE']['.'][$var];
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% 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...
223
              $temp = "\$this->_tpldata['DEFINE']['.']['$var']";
224
            }
225
            else
226
            {
227
              $var = substr($temp, 1, -1);
228
              //$file = $this->template->_rootref[$var];
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% 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...
229
              $temp = "\$this->_rootref['$var']";
230
            }
231
          }
232
          else
233
          {
234
            $file = $temp;
235
          }
236
237
          $compile_blocks[] = '<?php ' . $this->compile_tag_include($temp) . ' ?>';
238
239
          // No point in checking variable includes
240
          if ($file)
241
          {
242
            $this->template->_tpl_include($file, false);
243
          }
244
        break;
245
246
        case 'INCLUDEPHP':
247
          $compile_blocks[] = (classSupernova::$config->tpl_allow_php) ? '<?php ' . $this->compile_tag_include_php(array_shift($includephp_blocks)) . ' ?>' : '';
0 ignored issues
show
Documentation introduced by
The property tpl_allow_php does not exist on object<classConfig>. 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...
248
        break;
249
250
        case 'PHP':
251
          $compile_blocks[] = (classSupernova::$config->tpl_allow_php) ? '<?php ' . array_shift($php_blocks) . ' ?>' : '';
0 ignored issues
show
Documentation introduced by
The property tpl_allow_php does not exist on object<classConfig>. 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...
252
        break;
253
254
        default:
255
          $this->compile_var_tags($block_val[0]);
256
          $trim_check = trim($block_val[0]);
257
          $compile_blocks[] = (!$no_echo) ? ((!empty($trim_check)) ? $block_val[0] : '') : ((!empty($trim_check)) ? $block_val[0] : '');
258
        break;
259
      }
260
    }
261
262
    $template_php = '';
263
    for ($i = 0, $size = sizeof($text_blocks); $i < $size; $i++)
264
    {
265
      $trim_check_text = trim($text_blocks[$i]);
266
      $template_php .= (!$no_echo) ? (($trim_check_text != '') ? $text_blocks[$i] : '') . ((isset($compile_blocks[$i])) ? $compile_blocks[$i] : '') : (($trim_check_text != '') ? $text_blocks[$i] : '') . ((isset($compile_blocks[$i])) ? $compile_blocks[$i] : '');
267
    }
268
269
    // Remove unused opening/closing tags
270
    $template_php = str_replace(' ?><?php ', ' ', $template_php);
271
272
    // Now add a newline after each php closing tag which already has a newline
273
    // PHP itself strips a newline if a closing tag is used (this is documented behaviour) and it is mostly not intended by style authors to remove newlines
274
    $template_php = preg_replace('#\?\>([\r\n])#', '?>\1\1', $template_php);
275
276
    // There will be a number of occasions where we switch into and out of
277
    // PHP mode instantaneously. Rather than "burden" the parser with this
278
    // we'll strip out such occurences, minimising such switching
279
    if ($no_echo)
280
    {
281
      return "\$$echo_var .= '" . $template_php . "'";
282
    }
283
284
    return $template_php;
285
  }
286
287
  /**
288
  * Compile variables
289
  * @access private
290
  */
291
  function compile_var_tags(&$text_blocks)
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...
292
  {
293
    // change template varrefs into PHP varrefs
294
    $varrefs = array();
295
296
    // This one will handle varrefs WITH namespaces
297
    preg_match_all('#\{((?:[a-z0-9\-_]+\.)+)(\$)?([A-Z0-9\-_]+)\}#', $text_blocks, $varrefs, PREG_SET_ORDER);
298
299
    foreach ($varrefs as $var_val)
0 ignored issues
show
Bug introduced by
The expression $varrefs of type null|array<integer,array<integer,string>> 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...
300
    {
301
      $namespace = $var_val[1];
302
      $varname = $var_val[3];
303
      $new = $this->generate_block_varref($namespace, $varname, true, $var_val[2]);
304
305
      $text_blocks = str_replace($var_val[0], $new, $text_blocks);
306
    }
307
308
    // This will handle the remaining root-level varrefs
309
310
    // transform vars prefixed by I_ into skin-specific images with context
311
    if (strpos($text_blocks, '{I_') !== false && is_callable(array('skin', 'image_url')))
312
    {
313
      $text_blocks = preg_replace('#\{I_([a-zA-Z0-9\-_\|\/\.\{\}\[\]\$]+)\}#', "<?php echo skin::image_url('\\1', \$this); ?>", $text_blocks);
314
    }
315
316
    // transform vars prefixed by C_ into global config value
317
    if (strpos($text_blocks, '{C_') !== false)
318
    {
319
      $text_blocks = preg_replace('#\{C_([a-zA-Z0-9\-_]+)\[([a-zA-Z0-9\-_]*?)\]\}#', "<?php echo ((isset(\$this->_rootref['C_\\1']['\\2'])) ? \$this->_rootref['C_\\1']['\\2'] : ((isset(classSupernova::\$config->\\1['\\2'])) ? classSupernova::\$config->\\1['\\2'] : '{ \\1[\\2] }')); ?>", $text_blocks);
320
      $text_blocks = preg_replace('#\{C_([a-zA-Z0-9\-_]+)\}#', "<?php echo ((isset(\$this->_rootref['C_\\1'])) ? \$this->_rootref['C_\\1'] : ((isset(classSupernova::\$config->\\1)) ? classSupernova::\$config->\\1 : '{ C_\\1 }')); ?>", $text_blocks);
321
    }
322
    // transform vars prefixed by D_ into global defined constant
323
    if (strpos($text_blocks, '{D_') !== false)
324
    {
325
      $text_blocks = preg_replace('#\{D_([a-zA-Z0-9\-_]+)\}#', "<?php echo ((isset(\$this->_rootref['D_\\1'])) ? \$this->_rootref['D_\\1'] : ((defined('\\1')) ? \\1 : '{ D_\\1 }')); ?>", $text_blocks);
326
    }
327
    // transform vars prefixed by L_ into their language variable pendant if nothing is set within the tpldata array
328
    if (strpos($text_blocks, '{L_') !== false)
329
    {
330
      $text_blocks = preg_replace('#\{L_([a-zA-Z0-9\-_]+)\[D_([a-zA-Z0-9\-_]*?)\]\}#', "<?php echo ((isset(\$this->_rootref['L_\\1'][\\2])) ? \$this->_rootref['L_\\1'][\\2] : ((isset(classLocale::\$lang['\\1'][\\2])) ? classLocale::\$lang['\\1'][\\2] : '{ \\1[\\2] }')); ?>", $text_blocks);
331
      $text_blocks = preg_replace('#\{L_([a-zA-Z0-9\-_]+)\[([a-zA-Z0-9\-_]*?)\]\}#', "<?php echo ((isset(\$this->_rootref['L_\\1']['\\2'])) ? \$this->_rootref['L_\\1']['\\2'] : ((isset(classLocale::\$lang['\\1']['\\2'])) ? classLocale::\$lang['\\1']['\\2'] : '{ \\1[\\2] }')); ?>", $text_blocks);
332
      $text_blocks = preg_replace('#\{L_([a-zA-Z0-9\-_]+)\}#', "<?php echo ((isset(\$this->_rootref['L_\\1'])) ? \$this->_rootref['L_\\1'] : ((isset(classLocale::\$lang['\\1'])) ? classLocale::\$lang['\\1'] : '{ L_\\1 }')); ?>", $text_blocks);
333
    }
334
335
    // Handle addslashed language variables prefixed with LA_
336
    // If a template variable already exist, it will be used in favor of it...
337
    if (strpos($text_blocks, '{LA_') !== false)
338
    {
339
      $text_blocks = preg_replace('#\{LA_([a-zA-Z0-9\-_]+)\}#', "<?php echo ((isset(\$this->_rootref['LA_\\1'])) ? \$this->_rootref['LA_\\1'] : ((isset(\$this->_rootref['L_\\1'])) ? addslashes(\$this->_rootref['L_\\1']) : ((isset(classLocale::\$lang['\\1'])) ? addslashes(classLocale::\$lang['\\1']) : '{ \\1 }'))); ?>", $text_blocks);
340
    }
341
342
    // Handle remaining varrefs
343
    $text_blocks = preg_replace('#\{([a-zA-Z0-9\-_]+)\}#', "<?php echo (isset(\$this->_rootref['\\1'])) ? \$this->_rootref['\\1'] : ''; ?>", $text_blocks);
344
    $text_blocks = preg_replace('#\{\$([a-zA-Z0-9\-_]+)\}#', "<?php echo (isset(\$this->_tpldata['DEFINE']['.']['\\1'])) ? \$this->_tpldata['DEFINE']['.']['\\1'] : ''; ?>", $text_blocks);
345
346
    return;
347
  }
348
349
  /**
350
  * Compile blocks
351
  * @access private
352
  */
353
  function compile_tag_block($tag_args)
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...
354
  {
355
    $no_nesting = false;
356
357
    // Is the designer wanting to call another loop in a loop?
358
    if (strpos($tag_args, '!') === 0)
359
    {
360
      // Count the number if ! occurrences (not allowed in vars)
361
      $no_nesting = substr_count($tag_args, '!');
362
      $tag_args = substr($tag_args, $no_nesting);
363
    }
364
365
    // Allow for control of looping (indexes start from zero):
366
    // foo(2)    : Will start the loop on the 3rd entry
367
    // foo(-2)   : Will start the loop two entries from the end
368
    // foo(3,4)  : Will start the loop on the fourth entry and end it on the fifth
369
    // foo(3,-4) : Will start the loop on the fourth entry and end it four from last
370
    if (preg_match('#^([^()]*)\(([\-\d]+)(?:,([\-\d]+))?\)$#', $tag_args, $match))
371
    {
372
      $tag_args = $match[1];
373
374
      if ($match[2] < 0)
375
      {
376
        $loop_start = '($_' . $tag_args . '_count ' . $match[2] . ' < 0 ? 0 : $_' . $tag_args . '_count ' . $match[2] . ')';
377
      }
378
      else
379
      {
380
        $loop_start = '($_' . $tag_args . '_count < ' . $match[2] . ' ? $_' . $tag_args . '_count : ' . $match[2] . ')';
381
      }
382
383
      if (strlen($match[3]) < 1 || $match[3] == -1)
384
      {
385
        $loop_end = '$_' . $tag_args . '_count';
386
      }
387
      else if ($match[3] >= 0)
388
      {
389
        $loop_end = '(' . ($match[3] + 1) . ' > $_' . $tag_args . '_count ? $_' . $tag_args . '_count : ' . ($match[3] + 1) . ')';
390
      }
391
      else //if ($match[3] < -1)
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% 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...
392
      {
393
        $loop_end = '$_' . $tag_args . '_count' . ($match[3] + 1);
394
      }
395
    }
396
    else
397
    {
398
      $loop_start = 0;
399
      $loop_end = '$_' . $tag_args . '_count';
400
    }
401
402
    $tag_template_php = '';
1 ignored issue
show
Unused Code introduced by
$tag_template_php is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
403
    array_push($this->block_names, $tag_args);
404
405
    if ($no_nesting !== false)
406
    {
407
      // We need to implode $no_nesting times from the end...
408
      $block = array_slice($this->block_names, -$no_nesting);
409
    }
410
    else
411
    {
412
      $block = $this->block_names;
413
    }
414
415
    if (sizeof($block) < 2)
416
    {
417
      // Block is not nested.
418
      $tag_template_php = '$_' . $tag_args . "_count = (isset(\$this->_tpldata['$tag_args'])) ? sizeof(\$this->_tpldata['$tag_args']) : 0;";
419
      $varref = "\$this->_tpldata['$tag_args']";
420
    }
421
    else
422
    {
423
      // This block is nested.
424
      // Generate a namespace string for this block.
425
      $namespace = implode('.', $block);
426
427
      // Get a reference to the data array for this block that depends on the
428
      // current indices of all parent blocks.
429
      $varref = $this->generate_block_data_ref($namespace, false);
430
431
      // Create the for loop code to iterate over this block.
432
      $tag_template_php = '$_' . $tag_args . '_count = (isset(' . $varref . ')) ? sizeof(' . $varref . ') : 0;';
433
    }
434
435
    $tag_template_php .= 'if ($_' . $tag_args . '_count) {';
436
437
    /**
438
    * The following uses foreach for iteration instead of a for loop, foreach is faster but requires PHP to make a copy of the contents of the array which uses more memory
439
    * <code>
440
    * if (!$offset)
441
    * {
442
    *   $tag_template_php .= 'foreach (' . $varref . ' as $_' . $tag_args . '_i => $_' . $tag_args . '_val){';
443
    * }
444
    * </code>
445
    */
446
447
    $tag_template_php .= 'for ($_' . $tag_args . '_i = ' . $loop_start . '; $_' . $tag_args . '_i < ' . $loop_end . '; ++$_' . $tag_args . '_i){';
448
//    $tag_template_php .= '$this->_block_counter["'. $tag_args . '"] = $_' . $tag_args . '_i;';
449
    $tag_template_php .= '$_'. $tag_args . '_val = &' . $varref . '[$_'. $tag_args. '_i];';
450
    $tag_template_php .= '$this->_block_value["'. $tag_args . '"] = &' . $varref . '[$_'. $tag_args. '_i];';
451
452
    return $tag_template_php;
453
  }
454
455
  /**
456
  * Compile IF tags - much of this is from Smarty with
457
  * some adaptions for our block level methods
458
  * @access private
459
  */
460
  function compile_tag_if($tag_args, $elseif)
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...
461
  {
462
    // Tokenize args for 'if' tag.
463
    preg_match_all('/(?:
464
      "[^"\\\\]*(?:\\\\.[^"\\\\]*)*"         |
465
      \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'     |
466
      [(),]                                  |
467
      [^\s(),]+)/x', $tag_args, $match);
468
469
    $tokens = $match[0];
470
    $is_arg_stack = array();
471
472
    for ($i = 0, $size = sizeof($tokens); $i < $size; $i++)
473
    {
474
      $token = &$tokens[$i];
475
476
      switch ($token)
477
      {
478
        case '!==':
479
        case '===':
480
        case '<<':
481
        case '>>':
482
        case '|':
483
        case '^':
484
        case '&':
485
        case '~':
486
        case ')':
487
        case ',':
488
        case '+':
489
        case '-':
490
        case '*':
491
        case '/':
492
        case '@':
493
        break;
494
495
        case '==':
496
        case 'eq':
497
          $token = '==';
498
        break;
499
500
        case '!=':
501
        case '<>':
502
        case 'ne':
503
        case 'neq':
504
          $token = '!=';
505
        break;
506
507
        case '<':
508
        case 'lt':
509
          $token = '<';
510
        break;
511
512
        case '<=':
513
        case 'le':
514
        case 'lte':
515
          $token = '<=';
516
        break;
517
518
        case '>':
519
        case 'gt':
520
          $token = '>';
521
        break;
522
523
        case '>=':
524
        case 'ge':
525
        case 'gte':
526
          $token = '>=';
527
        break;
528
529
        case '&&':
530
        case 'and':
531
          $token = '&&';
532
        break;
533
534
        case '||':
535
        case 'or':
536
          $token = '||';
537
        break;
538
539
        case '!':
540
        case 'not':
541
          $token = '!';
542
        break;
543
544
        case '%':
545
        case 'mod':
546
          $token = '%';
547
        break;
548
549
        case '(':
550
          array_push($is_arg_stack, $i);
551
        break;
552
553
        case 'is':
554
          $is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1;
555
          $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
556
557
          $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
558
559
          array_splice($tokens, $is_arg_start, sizeof($tokens), $new_tokens);
560
561
          $i = $is_arg_start;
562
563
        // no break
564
565
        default:
566
          if (preg_match('#^((?:[a-z0-9\-_]+\.)+)?(\$)?(?=[A-Za-z])([A-Za-z0-9\-_]+)#s', $token, $varrefs))
567
          {
568
            $token = (!empty($varrefs[1])) ? $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[2]) . '[\'' . $varrefs[3] . '\']' : (($varrefs[2]) ? '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[3] . '\']' : '$this->_rootref[\'' . $varrefs[3] . '\']');
569
          }
570
          else if (preg_match('#^\.((?:[a-z0-9\-_]+\.?)+)$#s', $token, $varrefs))
571
          {
572
            // Allow checking if loops are set with .loopname
573
            // It is also possible to check the loop count by doing <!-- IF .loopname > 1 --> for example
574
            $blocks = explode('.', $varrefs[1]);
575
576
            // If the block is nested, we have a reference that we can grab.
577
            // If the block is not nested, we just go and grab the block from _tpldata
578
            if (sizeof($blocks) > 1)
579
            {
580
              $block = array_pop($blocks);
581
              $namespace = implode('.', $blocks);
582
              $varref = $this->generate_block_data_ref($namespace, true);
583
584
              // Add the block reference for the last child.
585
              $varref .= "['" . $block . "']";
586
            }
587
            else
588
            {
589
              $varref = '$this->_tpldata';
590
591
              // Add the block reference for the last child.
592
              $varref .= "['" . $blocks[0] . "']";
593
            }
594
            $token = "sizeof($varref)";
595
          }
596
          else if (!empty($token))
597
          {
598
            $token = '(' . $token . ')';
599
          }
600
601
        break;
602
      }
603
    }
604
605
    // If there are no valid tokens left or only control/compare characters left, we do skip this statement
606
    if (!sizeof($tokens) || str_replace(array(' ', '=', '!', '<', '>', '&', '|', '%', '(', ')'), '', implode('', $tokens)) == '')
607
    {
608
      $tokens = array('false');
609
    }
610
    return (($elseif) ? '} else if (' : 'if (') . (implode(' ', $tokens) . ') { ');
611
  }
612
613
  /**
614
  * Compile DEFINE tags
615
  * @access private
616
  */
617
  function compile_tag_define($tag_args, $op)
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...
618
  {
619
    preg_match('#^((?:[a-z0-9\-_]+\.)+)?\$(?=[A-Z])([A-Z0-9_\-]*)(?: = (\'?)([^\']*)(\'?))?$#', $tag_args, $match);
620
621
    if (empty($match[2]) || (!isset($match[4]) && $op))
622
    {
623
      return '';
624
    }
625
626
    if (!$op)
627
    {
628
      return 'unset(' . (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ');';
629
    }
630
631
    // Are we a string?
632
    if ($match[3] && $match[5])
633
    {
634
      $match[4] = str_replace(array('\\\'', '\\\\', '\''), array('\'', '\\', '\\\''), $match[4]);
635
636
      // Compile reference, we allow template variables in defines...
637
      $match[4] = $this->compile($match[4]);
638
639
      // Now replace the php code
640
      $match[4] = "'" . str_replace(array('<?php echo ', '; ?>'), array("' . ", " . '"), $match[4]) . "'";
641
    }
642
    else
643
    {
644
      preg_match('#true|false|\.#i', $match[4], $type);
645
646
      switch (strtolower($type[0]))
647
      {
648
        case 'true':
649
        case 'false':
650
          $match[4] = strtoupper($match[4]);
651
        break;
652
653
        case '.':
654
          $match[4] = doubleval($match[4]);
655
        break;
656
657
        default:
658
          $match[4] = intval($match[4]);
659
        break;
660
      }
661
    }
662
663
    return (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ' = ' . $match[4] . ';';
664
  }
665
666
  /**
667
  * Compile INCLUDE tag
668
  * @access private
669
  */
670
  function compile_tag_include($tag_args)
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...
671
  {
672
    // Process dynamic includes
673
    if ($tag_args[0] == '$')
674
    {
675
      return "if (isset($tag_args)) { \$this->_tpl_include($tag_args); }";
676
    }
677
678
    return "\$this->_tpl_include('$tag_args');";
679
  }
680
681
  /**
682
  * Compile INCLUDE_PHP tag
683
  * @access private
684
  */
685
  function compile_tag_include_php($tag_args)
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...
686
  {
687
    return "\$this->_php_include('$tag_args');";
688
  }
689
690
  /**
691
  * parse expression
692
  * This is from Smarty
693
  * @access private
694
  */
695
  function _parse_is_expr($is_arg, $tokens)
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...
696
  {
697
    $expr_end = 0;
698
    $negate_expr = false;
699
700
    if (($first_token = array_shift($tokens)) == 'not')
701
    {
702
      $negate_expr = true;
703
      $expr_type = array_shift($tokens);
704
    }
705
    else
706
    {
707
      $expr_type = $first_token;
708
    }
709
710
    switch ($expr_type)
711
    {
712 View Code Duplication
      case 'even':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
713
        if (@$tokens[$expr_end] == 'by')
714
        {
715
          $expr_end++;
716
          $expr_arg = $tokens[$expr_end++];
717
          $expr = "!(($is_arg / $expr_arg) % $expr_arg)";
718
        }
719
        else
720
        {
721
          $expr = "!($is_arg & 1)";
722
        }
723
      break;
724
725 View Code Duplication
      case 'odd':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
726
        if (@$tokens[$expr_end] == 'by')
727
        {
728
          $expr_end++;
729
          $expr_arg = $tokens[$expr_end++];
730
          $expr = "(($is_arg / $expr_arg) % $expr_arg)";
731
        }
732
        else
733
        {
734
          $expr = "($is_arg & 1)";
735
        }
736
      break;
737
738
      case 'div':
739
        if (@$tokens[$expr_end] == 'by')
740
        {
741
          $expr_end++;
742
          $expr_arg = $tokens[$expr_end++];
743
          $expr = "!($is_arg % $expr_arg)";
744
        }
745
      break;
746
    }
747
748
    if ($negate_expr)
749
    {
750
      $expr = "!($expr)";
0 ignored issues
show
Bug introduced by
The variable $expr 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...
751
    }
752
753
    array_splice($tokens, 0, $expr_end, $expr);
754
755
    return $tokens;
756
  }
757
758
  /**
759
  * Generates a reference to the given variable inside the given (possibly nested)
760
  * block namespace. This is a string of the form:
761
  * ' . $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . '
762
  * It's ready to be inserted into an "echo" line in one of the templates.
763
  * NOTE: expects a trailing "." on the namespace.
764
  * @access private
765
  */
766
  function generate_block_varref($namespace, $varname, $echo = true, $defop = false)
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...
767
  {
768
    // Strip the trailing period.
769
    $namespace = substr($namespace, 0, -1);
770
771
    // Get a reference to the data block for this namespace.
772
    $varref = $this->generate_block_data_ref($namespace, true, $defop);
773
    // Prepend the necessary code to stick this in an echo line.
774
775
    // Append the variable reference.
776
    $varref .= "['$varname']";
777
    $varref = ($echo) ? "<?php echo $varref; ?>" : ((isset($varref)) ? $varref : '');
778
779
    return $varref;
780
  }
781
782
  /**
783
  * Generates a reference to the array of data values for the given
784
  * (possibly nested) block namespace. This is a string of the form:
785
  * $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN']
786
  *
787
  * If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above.
788
  * NOTE: does not expect a trailing "." on the blockname.
789
  * @access private
790
  */
791
  function generate_block_data_ref($blockname, $include_last_iterator, $defop = false)
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...
792
  {
793
    // Get an array of the blocks involved.
794
    $blocks = explode('.', $blockname);
795
    $blockcount = sizeof($blocks) - 1;
796
797
    // DEFINE is not an element of any referenced variable, we must use _tpldata to access it
798
    if ($defop)
799
    {
800
      $varref = '$this->_tpldata[\'DEFINE\']';
801
      // Build up the string with everything but the last child.
802
      for ($i = 0; $i < $blockcount; $i++)
803
      {
804
        $varref .= "['" . $blocks[$i] . "'][\$_" . $blocks[$i] . '_i]';
805
      }
806
      // Add the block reference for the last child.
807
      $varref .= "['" . $blocks[$blockcount] . "']";
808
      // Add the iterator for the last child if requried.
809
      if ($include_last_iterator)
810
      {
811
        $varref .= '[$_' . $blocks[$blockcount] . '_i]';
812
      }
813
      return $varref;
814
    }
815
    else if ($include_last_iterator)
816
    {
817
      return '$_'. $blocks[$blockcount] . '_val';
818
    }
819
    else
820
    {
821
      return '$_'. $blocks[$blockcount - 1] . '_val[\''. $blocks[$blockcount]. '\']';
822
    }
823
  }
824
825
  /**
826
  * Write compiled file to cache directory
827
  * @access private
828
  */
829
  function compile_write($handle, $data)
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...
830
  {
831
    $filename = $this->template->cachepath . str_replace('/', '.', $this->template->filename[$handle]) . DOT_PHP_EX;
832
833
    $data = "<?php if (!defined('INSIDE')) exit;" . ((strpos($data, '<?php') === 0) ? substr($data, 5) : ' ?>' . $data);
834
835
    if ($fp = @fopen($filename, 'wb'))
836
    {
837
      @flock($fp, LOCK_EX);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
838
      @fwrite ($fp, $data);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
839
      @flock($fp, LOCK_UN);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
840
      @fclose($fp);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
841
842
      //phpbb_chmod($filename, CHMOD_READ | CHMOD_WRITE);
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...
843
      chmod($filename, 0710);
844
    }
845
846
    return;
847
  }
848
849
  // Gorlum's minifier BOF
850
  /**
851
  * Minifies template w/i PHP code by removing extra spaces
852
  */
853
  private function minify($html)
854
  {
855
    if(!classSupernova::$config->tpl_minifier)
0 ignored issues
show
Documentation introduced by
The property tpl_minifier does not exist on object<classConfig>. 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...
856
    {
857
      return $html;
858
    }
859
860
    // TODO: Match <code> and <pre> too - in separate arrays
861
    preg_match_all('/(<script[^>]*?>.*?<\/script>)/si', $html, $pre);
862
    $html = preg_replace('/(<script[^>]*?>.*?<\/script>)/si', '#pre#', $html);
863
    $html = preg_replace('/>[\s]*</', '><', $html); // Strip spacechars between tags
864
    $html = preg_replace('/[\s]+/', ' ', $html); // Replace several spacechars with one space
865
    if(!empty($pre[0]))
866
    {
867
      foreach($pre[0] as $tag)
868
      {
869
        $tag = preg_replace('/^\ *\/\/[^\<]*?$/m', ' ', $tag); // Strips comments - except those that contains HTML comment inside
870
        $tag = preg_replace('/[\ \t]{2,}/', ' ', $tag); // Replace several spaces by one
871
        $tag = preg_replace('/\s{2,}/', "\r\n", $tag); // Replace several linefeeds by one
872
        $html = preg_replace('/#pre#/', $tag, $html,1);
873
      }
874
    }
875
876
    return $html;
877
  }
878
  // Gorlum's minifier EOF
879
880
}
881