SysUtility   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 155
c 1
b 0
f 0
dl 0
loc 395
rs 5.5199
wmc 56

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getEditor() 0 32 5
A metaKeywords() 0 9 3
A getInstance() 0 8 2
A metaDescription() 0 9 3
A selectSorting() 0 26 4
A enumerate() 0 18 2
A blockAddCatSelect() 0 13 4
F truncateHtml() 0 90 19
A prepareFolder() 0 9 5
A cloneRecord() 0 26 4
A queryAndCheck() 0 10 2
A fieldExists() 0 7 1
A queryFAndCheck() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like SysUtility often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SysUtility, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
namespace XoopsModules\Marquee\Common;
4
5
/*
6
 Utility Class Definition
7
8
 You may not change or alter any portion of this comment or credits of
9
 supporting developers from this source code or any supporting source code
10
 which is considered copyrighted (c) material of the original comment or credit
11
 authors.
12
13
 This program is distributed in the hope that it will be useful, but
14
 WITHOUT ANY WARRANTY; without even the implied warranty of
15
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
 */
17
18
/**
19
 * @license      GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html)
20
 * @copyright    https://xoops.org 2000-2020 &copy; XOOPS Project
21
 * @author       ZySpec <[email protected]>
22
 * @author       Mamba <[email protected]>
23
 */
24
25
use RuntimeException;
26
use Xmf\Request;
27
28
use XoopsModules\Marquee\Helper;
29
30
/**
31
 * Class SysUtility
32
 */
33
class SysUtility
34
{
35
    use VersionChecks;
0 ignored issues
show
introduced by
The trait XoopsModules\Marquee\Common\VersionChecks requires some properties which are not provided by XoopsModules\Marquee\Common\SysUtility: $tag_name, $prerelease
Loading history...
36
37
    //checkVerXoops, checkVerPhp Traits
38
39
    use ServerStats;
40
41
    // getServerStats Trait
42
43
    use FilesManagement;
44
45
    // Files Management Trait
46
47
    //--------------- Common module methods -----------------------------
48
49
    /**
50
     * Access the only instance of this class
51
     *
52
     * @return \XoopsModules\Marquee\Common\SysUtility
53
     */
54
    public static function getInstance()
55
    {
56
        static $instance;
57
        if (null === $instance) {
58
            $instance = new static();
59
        }
60
61
        return $instance;
62
    }
63
64
    /**
65
     * @param $text
66
     * @param $form_sort
67
     * @return string
68
     */
69
    public static function selectSorting($text, $form_sort)
70
    {
71
        global $start, $order, $file_cat, $sort, $xoopsModule;
72
73
        $select_view   = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $select_view is dead and can be removed.
Loading history...
74
        $moduleDirName = \basename(\dirname(__DIR__));
0 ignored issues
show
Unused Code introduced by
The assignment to $moduleDirName is dead and can be removed.
Loading history...
75
        /** @var Helper $helper */
76
        $helper = Helper::getInstance();
77
78
        //$pathModIcon16 = XOOPS_URL . '/modules/' . $moduleDirName . '/' . $helper->getConfig('modicons16');
79
        $pathModIcon16 = $helper->url($helper->getModule()->getInfo('modicons16'));
0 ignored issues
show
Bug introduced by
It seems like $helper->getModule()->getInfo('modicons16') can also be of type array; however, parameter $url of Xmf\Module\Helper\GenericHelper::url() 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

79
        $pathModIcon16 = $helper->url(/** @scrutinizer ignore-type */ $helper->getModule()->getInfo('modicons16'));
Loading history...
80
81
        $select_view = '<form name="form_switch" id="form_switch" action="' . Request::getString('REQUEST_URI', '', 'SERVER') . '" method="post"><span style="font-weight: bold;">' . $text . '</span>';
82
        //$sorts =  $sort ==  'asc' ? 'desc' : 'asc';
83
        if ($form_sort == $sort) {
84
            $sel1 = 'asc' === $order ? 'selasc.png' : 'asc.png';
85
            $sel2 = 'desc' === $order ? 'seldesc.png' : 'desc.png';
86
        } else {
87
            $sel1 = 'asc.png';
88
            $sel2 = 'desc.png';
89
        }
90
        $select_view .= '  <a href="' . Request::getString('SCRIPT_NAME', '', 'SERVER') . '?start=' . $start . '&sort=' . $form_sort . '&order=asc"><img src="' . $pathModIcon16 . '/' . $sel1 . '" title="ASC" alt="ASC"></a>';
91
        $select_view .= '<a href="' . Request::getString('SCRIPT_NAME', '', 'SERVER') . '?start=' . $start . '&sort=' . $form_sort . '&order=desc"><img src="' . $pathModIcon16 . '/' . $sel2 . '" title="DESC" alt="DESC"></a>';
92
        $select_view .= '</form>';
93
94
        return $select_view;
95
    }
96
97
    /***************Blocks***************/
98
99
    /**
100
     * @param array $cats
101
     * @return string
102
     */
103
    public static function blockAddCatSelect($cats)
104
    {
105
        $cat_sql = '';
106
        if (\is_array($cats) && !empty($cats)) {
107
            $cat_sql = '(' . \current($cats);
108
            \array_shift($cats);
109
            foreach ($cats as $cat) {
110
                $cat_sql .= ',' . $cat;
111
            }
112
            $cat_sql .= ')';
113
        }
114
115
        return $cat_sql;
116
    }
117
118
    /**
119
     * @param $content
120
     */
121
    public static function metaKeywords($content): void
122
    {
123
        global $xoopsTpl, $xoTheme;
124
        $myts    = \MyTextSanitizer::getInstance();
125
        $content = $myts->undoHtmlSpecialChars($myts->displayTarea($content));
126
        if (null !== $xoTheme && \is_object($xoTheme)) {
127
            $xoTheme->addMeta('meta', 'keywords', \strip_tags($content));
128
        } else {    // Compatibility for old Xoops versions
129
            $xoopsTpl->assign('xoops_metaKeywords', \strip_tags($content));
130
        }
131
    }
132
133
    /**
134
     * @param $content
135
     */
136
    public static function metaDescription($content): void
137
    {
138
        global $xoopsTpl, $xoTheme;
139
        $myts    = \MyTextSanitizer::getInstance();
140
        $content = $myts->undoHtmlSpecialChars($myts->displayTarea($content));
141
        if (null !== $xoTheme && \is_object($xoTheme)) {
142
            $xoTheme->addMeta('meta', 'description', \strip_tags($content));
143
        } else {    // Compatibility for old Xoops versions
144
            $xoopsTpl->assign('xoops_metaDescription', \strip_tags($content));
145
        }
146
    }
147
148
    /**
149
     * @param $tableName
150
     * @param $columnName
151
     *
152
     * @return array
153
     */
154
    public static function enumerate($tableName, $columnName)
155
    {
156
        $table = $GLOBALS['xoopsDB']->prefix($tableName);
157
158
        //    $result = $GLOBALS['xoopsDB']->query("SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS
159
        //        WHERE TABLE_NAME = '" . $table . "' AND COLUMN_NAME = '" . $columnName . "'")
160
        //    || exit ($GLOBALS['xoopsDB']->error());
161
162
        $sql    = 'SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = "' . $table . '" AND COLUMN_NAME = "' . $columnName . '"';
163
        $result = $GLOBALS['xoopsDB']->query($sql);
164
        if (!$GLOBALS['xoopsDB']->isResultSet($result)) {
165
            \trigger_error(\sprintf(\_DB_QUERY_ERROR, $sql) . $GLOBALS['xoopsDB']->error(), \E_USER_ERROR);
166
        }
167
168
        $row      = $GLOBALS['xoopsDB']->fetchBoth($result);
169
        $enumList = \explode(',', \str_replace("'", '', \mb_substr($row['COLUMN_TYPE'], 5, -6)));
170
171
        return $enumList;
172
    }
173
174
    /**
175
     * @param array|string $tableName
176
     * @param string       $id_field
177
     * @param int          $id
178
     *
179
     * @return mixed
180
     */
181
    public static function cloneRecord($tableName, $id_field, $id)
182
    {
183
        $new_id = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $new_id is dead and can be removed.
Loading history...
184
        $table  = $GLOBALS['xoopsDB']->prefix($tableName);
185
        // copy content of the record you wish to clone
186
        $sql    = "SELECT * FROM $table WHERE $id_field='" . $id . "' ";
187
        $result = $GLOBALS['xoopsDB']->query($sql);
188
        if ($GLOBALS['xoopsDB']->isResultSet($result)) {
189
            $tempTable = $GLOBALS['xoopsDB']->fetchArray($result, \MYSQLI_ASSOC);
190
        }
191
        if (!$tempTable) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tempTable does not seem to be defined for all execution paths leading up to this point.
Loading history...
192
            \trigger_error($GLOBALS['xoopsDB']->error());
193
        }
194
        // set the auto-incremented id's value to blank.
195
        unset($tempTable[$id_field]);
196
        // insert cloned copy of the original  record
197
        $sql    = "INSERT INTO $table (" . \implode(', ', \array_keys($tempTable)) . ") VALUES ('" . \implode("', '", \array_values($tempTable)) . "')";
198
        $result = $GLOBALS['xoopsDB']->queryF($sql);
199
if (!$GLOBALS['xoopsDB']->isResultSet($result)) {
200
   \trigger_error(\sprintf(\_DB_QUERY_ERROR, $sql) . $GLOBALS['xoopsDB']->error(), \E_USER_ERROR);
201
202
}
203
        // Return the new id
204
        $new_id = $GLOBALS['xoopsDB']->getInsertId();
205
206
        return $new_id;
207
    }
208
209
    /**
210
     * truncateHtml can truncate a string up to a number of characters while preserving whole words and HTML tags
211
     * www.gsdesign.ro/blog/cut-html-string-without-breaking-the-tags
212
     * www.cakephp.org
213
     *
214
     * @param string $text         String to truncate.
215
     * @param int    $length       Length of returned string, including ellipsis.
216
     * @param string $ending       Ending to be appended to the trimmed string.
217
     * @param bool   $exact        If false, $text will not be cut mid-word
218
     * @param bool   $considerHtml If true, HTML tags would be handled correctly
219
     *
220
     * @return string Trimmed string.
221
     */
222
    public static function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true)
223
    {
224
        $openTags = [];
225
        if ($considerHtml) {
226
            // if the plain text is shorter than the maximum length, return the whole text
227
            if (\mb_strlen(\preg_replace('/<.*?' . '>/', '', $text)) <= $length) {
228
                return $text;
229
            }
230
            // splits all html-tags to scanable lines
231
            \preg_match_all('/(<.+?' . '>)?([^<>]*)/s', $text, $lines, \PREG_SET_ORDER);
232
            $total_length = \mb_strlen($ending);
233
            //$openTags    = [];
234
            $truncate = '';
235
            foreach ($lines as $lineMatchings) {
236
                // if there is any html-tag in this line, handle it and add it (uncounted) to the output
237
                if (!empty($lineMatchings[1])) {
238
                    // if it's an "empty element" with or without xhtml-conform closing slash
239
                    if (\preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $lineMatchings[1])) {
240
                        // do nothing
241
                        // if tag is a closing tag
242
                    } elseif (\preg_match('/^<\s*\/(\S+?)\s*>$/s', $lineMatchings[1], $tag_matchings)) {
243
                        // delete tag from $openTags list
244
                        $pos = \array_search($tag_matchings[1], $openTags, true);
245
                        if (false !== $pos) {
246
                            unset($openTags[$pos]);
247
                        }
248
                        // if tag is an opening tag
249
                    } elseif (\preg_match('/^<\s*([^\s>!]+).*?' . '>$/s', $lineMatchings[1], $tag_matchings)) {
250
                        // add tag to the beginning of $openTags list
251
                        \array_unshift($openTags, \mb_strtolower($tag_matchings[1]));
252
                    }
253
                    // add html-tag to $truncate'd text
254
                    $truncate .= $lineMatchings[1];
255
                }
256
                // calculate the length of the plain text part of the line; handle entities as one character
257
                $content_length = \mb_strlen(\preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $lineMatchings[2]));
258
                if ($total_length + $content_length > $length) {
259
                    // the number of characters which are left
260
                    $left            = $length - $total_length;
261
                    $entities_length = 0;
262
                    // search for html entities
263
                    if (\preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $lineMatchings[2], $entities, \PREG_OFFSET_CAPTURE)) {
264
                        // calculate the real length of all entities in the legal range
265
                        foreach ($entities[0] as $entity) {
266
                            if ($entity[1] + 1 - $entities_length <= $left) {
267
                                $left--;
268
                                $entities_length += \mb_strlen($entity[0]);
269
                            } else {
270
                                // no more characters left
271
                                break;
272
                            }
273
                        }
274
                    }
275
                    $truncate .= \mb_substr($lineMatchings[2], 0, $left + $entities_length);
276
                    // maximum lenght is reached, so get off the loop
277
                    break;
278
                }
279
                $truncate     .= $lineMatchings[2];
280
                $total_length += $content_length;
281
282
                // if the maximum length is reached, get off the loop
283
                if ($total_length >= $length) {
284
                    break;
285
                }
286
            }
287
        } elseif (\mb_strlen($text) <= $length) {
288
            return $text;
289
        } else {
290
            $truncate = \mb_substr($text, 0, $length - \mb_strlen($ending));
291
        }
292
293
        // if the words shouldn't be cut in the middle...
294
        if (!$exact) {
295
            // ...search the last occurance of a space...
296
            $spacepos = mb_strrpos($truncate, ' ');
297
            if (isset($spacepos)) {
298
                // ...and cut the text in this position
299
                $truncate = \mb_substr($truncate, 0, $spacepos);
300
            }
301
        }
302
        // add the defined ending to the text
303
        $truncate .= $ending;
304
        if ($considerHtml) {
305
            // close all unclosed html-tags
306
            foreach ($openTags as $tag) {
307
                $truncate .= '</' . $tag . '>';
308
            }
309
        }
310
311
        return $truncate;
312
    }
313
314
    /**
315
     * @param \Xmf\Module\Helper $helper
316
     * @param array|null         $options
317
     * @return \XoopsFormDhtmlTextArea|\XoopsFormEditor
318
     */
319
    public static function getEditor($helper = null, $options = null)
320
    {
321
        /** @var Helper $helper */
322
        if (null === $options) {
323
            $options           = [];
324
            $options['name']   = 'Editor';
325
            $options['value']  = 'Editor';
326
            $options['rows']   = 10;
327
            $options['cols']   = '100%';
328
            $options['width']  = '100%';
329
            $options['height'] = '400px';
330
        }
331
332
        if (null === $helper) {
333
            $helper = Helper::getInstance();
334
        }
335
336
        $isAdmin = $helper->isUserAdmin();
337
338
        if (\class_exists('XoopsFormEditor')) {
339
            if ($isAdmin) {
340
                $descEditor = new \XoopsFormEditor(\ucfirst((string) $options['name']), $helper->getConfig('editorAdmin'), $options, $nohtml = false, $onfailure = 'textarea');
341
            } else {
342
                $descEditor = new \XoopsFormEditor(\ucfirst((string) $options['name']), $helper->getConfig('editorUser'), $options, $nohtml = false, $onfailure = 'textarea');
343
            }
344
        } else {
345
            $descEditor = new \XoopsFormDhtmlTextArea(\ucfirst((string) $options['name']), $options['name'], $options['value']);
346
        }
347
348
        //        $form->addElement($descEditor);
349
350
        return $descEditor;
351
    }
352
353
    /**
354
     * @param string $fieldname
355
     * @param string $table
356
     *
357
     * @return bool
358
     */
359
    public static function fieldExists(string $fieldname, string $table): bool
360
    {
361
        global $xoopsDB;
362
        $sql ="SHOW COLUMNS FROM   $table LIKE '$fieldname'";
363
        $result = self::queryFAndCheck($xoopsDB, $sql);
364
365
        return ($xoopsDB->getRowsNum($result) > 0);
366
    }
367
368
    /**
369
     * Function responsible for checking if a directory exists, we can also write in and create an index.html file
370
     *
371
     * @param string $folder The full path of the directory to check
372
     */
373
    public static function prepareFolder($folder): void
374
    {
375
        try {
376
            if (!\is_dir($folder) && !\mkdir($folder) && !\is_dir($folder)) {
377
                throw new RuntimeException(\sprintf('Unable to create the %s directory', $folder));
378
            }
379
            file_put_contents($folder . '/index.html', '<script>history.go(-1);</script>');
380
        } catch (\Throwable $e) {
381
            echo 'Caught exception: ', $e->getMessage(), "\n", '<br>';
382
        }
383
    }
384
385
386
    /**
387
     * Query and check if the result is a valid result set
388
     *
389
     * @param \XoopsMySQLDatabase $xoopsDB XOOPS Database
390
     * @param string              $sql     a valid MySQL query
391
     * @param int                 $limit   number of records to return
392
     * @param int                 $start   offset of first record to return
393
     *
394
     * @return \mysqli_result query result
395
     */
396
    public static function queryAndCheck(\XoopsMySQLDatabase $xoopsDB, string $sql, $limit = 0, $start = 0): \mysqli_result
397
    {
398
        $result = $xoopsDB->query($sql, $limit, $start);
399
400
        if (!$xoopsDB->isResultSet($result)) {
401
            throw new \RuntimeException(
402
                \sprintf(\_DB_QUERY_ERROR, $sql) . $xoopsDB->error(), \E_USER_ERROR);
403
        }
404
405
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type boolean which is incompatible with the type-hinted return mysqli_result. Consider adding an additional type-check to rule them out.
Loading history...
406
    }
407
408
    /**
409
     * QueryF and check if the result is a valid result set
410
     *
411
     * @param \XoopsMySQLDatabase $xoopsDB XOOPS Database
412
     * @param string              $sql     a valid MySQL query
413
     * @param int                 $limit   number of records to return
414
     * @param int                 $start   offset of first record to return
415
     *
416
     * @return \mysqli_result query result
417
     */
418
    public static function queryFAndCheck(\XoopsMySQLDatabase $xoopsDB, string $sql, $limit = 0, $start = 0): \mysqli_result
419
    {
420
        $result = $xoopsDB->queryF($sql, $limit, $start);
421
422
        if (!$xoopsDB->isResultSet($result)) {
423
            throw new \RuntimeException(
424
                \sprintf(\_DB_QUERY_ERROR, $sql) . $xoopsDB->error(), \E_USER_ERROR);
425
        }
426
427
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type boolean which is incompatible with the type-hinted return mysqli_result. Consider adding an additional type-check to rule them out.
Loading history...
428
    }
429
}
430