Passed
Push — master ( 919ce4...d1101e )
by Michael
03:15 queued 11s
created

SysUtility::enumerate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 19
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace XoopsModules\Adslight\Common;
6
7
/*
8
 Utility Class Definition
9
10
 You may not change or alter any portion of this comment or credits of
11
 supporting developers from this source code or any supporting source code
12
 which is considered copyrighted (c) material of the original comment or credit
13
 authors.
14
15
 This program is distributed in the hope that it will be useful, but
16
 WITHOUT ANY WARRANTY; without even the implied warranty of
17
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18
 */
19
20
/**
21
 * @license      https://www.fsf.org/copyleft/gpl.html GNU public license
22
 * @copyright    https://xoops.org 2000-2020 &copy; XOOPS Project
23
 * @author       ZySpec <[email protected]>
24
 * @author       Mamba <[email protected]>
25
 */
26
27
use Xmf\Request;
28
use XoopsFormEditor;
29
use XoopsModules\Adslight\{
30
    Helper
31
};
32
33
/**
34
 * Class SysUtility
35
 */
36
class SysUtility
37
{
38
    use VersionChecks;
0 ignored issues
show
introduced by
The trait XoopsModules\Adslight\Common\VersionChecks requires some properties which are not provided by XoopsModules\Adslight\Common\SysUtility: $tag_name, $prerelease
Loading history...
39
40
    //checkVerXoops, checkVerPhp Traits
41
42
    use ServerStats;
43
44
    // getServerStats Trait
45
46
    use FilesManagement;
47
48
    // Files Management Trait
49
    //    use ModuleStats;    // ModuleStats Trait
50
51
    //--------------- Common module methods -----------------------------
52
53
    /**
54
     * Access the only instance of this class
55
     */
56
    public static function getInstance(): self
57
    {
58
        static $instance;
59
        if (null === $instance) {
60
            $instance = new static();
61
        }
62
63
        return $instance;
64
    }
65
66
    /**
67
     * @param $text
68
     * @param $form_sort
69
     */
70
    public static function selectSorting($text, $form_sort): string
71
    {
72
        global $start, $order, $sort;
73
74
        $select_view   = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $select_view is dead and can be removed.
Loading history...
75
        $moduleDirName = \basename(\dirname(__DIR__));
0 ignored issues
show
Unused Code introduced by
The assignment to $moduleDirName is dead and can be removed.
Loading history...
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
    public static function blockAddCatSelect(array $cats): string
100
    {
101
        $cat_sql = '';
102
        if (\is_array($cats) && !empty($cats)) {
103
            $cat_sql = '(' . \current($cats);
104
            \array_shift($cats);
105
            foreach ($cats as $cat) {
106
                $cat_sql .= ',' . $cat;
107
            }
108
            $cat_sql .= ')';
109
        }
110
111
        return $cat_sql;
112
    }
113
114
    /**
115
     * @param $content
116
     */
117
    public static function metaKeywords($content): void
118
    {
119
        global $xoopsTpl, $xoTheme;
120
        $myts    = \MyTextSanitizer::getInstance();
121
        $content = $myts->undoHtmlSpecialChars($myts->displayTarea($content));
122
        if (\is_object($xoTheme)) {
123
            $xoTheme->addMeta('meta', 'keywords', \strip_tags($content));
124
        } else {    // Compatibility for old Xoops versions
125
            $xoopsTpl->assign('xoops_metaKeywords', \strip_tags($content));
126
        }
127
    }
128
129
    /**
130
     * @param $content
131
     */
132
    public static function metaDescription($content): void
133
    {
134
        global $xoopsTpl, $xoTheme;
135
        $myts    = \MyTextSanitizer::getInstance();
136
        $content = $myts->undoHtmlSpecialChars($myts->displayTarea($content));
137
        if (\is_object($xoTheme)) {
138
            $xoTheme->addMeta('meta', 'description', \strip_tags($content));
139
        } else {    // Compatibility for old Xoops versions
140
            $xoopsTpl->assign('xoops_metaDescription', \strip_tags($content));
141
        }
142
    }
143
144
    /**
145
     * @return array|false
146
     */
147
    public static function enumerate(string $tableName, string $columnName)
148
    {
149
        $table = $GLOBALS['xoopsDB']->prefix($tableName);
150
151
        //    $result = $GLOBALS['xoopsDB']->query("SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS
152
        //        WHERE TABLE_NAME = '" . $table . "' AND COLUMN_NAME = '" . $columnName . "'")
153
        //    || exit ($GLOBALS['xoopsDB']->error());
154
155
        $sql    = 'SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = "' . $table . '" AND COLUMN_NAME = "' . $columnName . '"';
156
        $result = $GLOBALS['xoopsDB']->query($sql);
157
        if (!$result) {
158
            //            exit($GLOBALS['xoopsDB']->error());
159
            $logger = \XoopsLogger::getInstance();
160
            $logger->handleError(\E_USER_WARNING, $sql, __FILE__, __LINE__);
161
            return false;
162
        }
163
164
        $row      = $GLOBALS['xoopsDB']->fetchBoth($result);
165
        return \explode(',', \str_replace("'", '', \mb_substr($row['COLUMN_TYPE'], 5, -6)));
166
    }
167
168
    /**
169
     * Clone a record in a dB
170
     *
171
     * @TODO need to exit more gracefully on error. Should throw/trigger error and then return false
172
     *
173
     * @param string $tableName name of dB table (without prefix)
174
     * @param string $idField   name of field (column) in dB table
175
     * @param int    $id        item id to clone
176
     */
177
    public static function cloneRecord(string $tableName, string $idField, int $id): void
178
    {
179
        $newId = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $newId is dead and can be removed.
Loading history...
180
        $table = $GLOBALS['xoopsDB']->prefix($tableName);
181
        // copy content of the record you wish to clone
182
        $sql       = "SELECT * FROM ${table} WHERE ${idField}='" . $id . "' ";
183
        $tempTable = $GLOBALS['xoopsDB']->fetchArray($GLOBALS['xoopsDB']->query($sql), \MYSQLI_ASSOC);
184
        if (!$tempTable) {
185
            exit($GLOBALS['xoopsDB']->error());
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
186
        }
187
        // set the auto-incremented id's value to blank.
188
        unset($tempTable[$idField]);
189
        // insert cloned copy of the original  record
190
        $sql    = "INSERT INTO ${table} (" . \implode(', ', \array_keys($tempTable)) . ") VALUES ('" . \implode("', '", $tempTable) . "')";
191
        $result = $GLOBALS['xoopsDB']->queryF($sql);
192
        if (!$result) {
193
            exit($GLOBALS['xoopsDB']->error());
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
194
        }
195
        // Return the new id
196
        return $GLOBALS['xoopsDB']->getInsertId();
197
    }
198
199
    /**
200
     * truncateHtml can truncate a string up to a number of characters while preserving whole words and HTML tags
201
     * www.gsdesign.ro/blog/cut-html-string-without-breaking-the-tags
202
     * www.cakephp.org
203
     *
204
     * @TODO: Refactor to consider HTML5 & void (self-closing) elements
205
     * @TODO: Consider using https://github.com/jlgrall/truncateHTML/blob/master/truncateHTML.php
206
     *
207
     * @param string $text         String to truncate.
208
     * @param int|null    $length       Length of returned string, including ellipsis.
209
     * @param string|null $ending       Ending to be appended to the trimmed string.
210
     * @param bool   $exact        If false, $text will not be cut mid-word
211
     * @param bool   $considerHtml If true, HTML tags would be handled correctly
212
     *
213
     * @return string Trimmed string.
214
     */
215
    public static function truncateHtml(
216
        string $text,
217
        ?int $length = 100,
218
        ?string $ending = '...',
219
        ?bool $exact = false,
220
        ?bool $considerHtml = true
221
    ): string {
222
        $openTags = [];
223
        if ($considerHtml) {
224
            // if the plain text is shorter than the maximum length, return the whole text
225
            if (\mb_strlen(\preg_replace('/<.*?' . '>/', '', $text)) <= $length) {
226
                return $text;
227
            }
228
            // splits all html-tags to scanable lines
229
            \preg_match_all('/(<.+?' . '>)?([^<>]*)/s', $text, $lines, \PREG_SET_ORDER);
230
            $total_length = \mb_strlen($ending);
0 ignored issues
show
Bug introduced by
It seems like $ending can also be of type null; however, parameter $string of mb_strlen() 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

230
            $total_length = \mb_strlen(/** @scrutinizer ignore-type */ $ending);
Loading history...
231
            //$openTags    = [];
232
            $truncate = '';
233
            foreach ($lines as $line_matchings) {
234
                // if there is any html-tag in this line, handle it and add it (uncounted) to the output
235
                if (!empty($line_matchings[1])) {
236
                    // if it's an "empty element" with or without xhtml-conform closing slash
237
                    if (\preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
238
                        // do nothing
239
                        // if tag is a closing tag
240
                    } elseif (\preg_match('/^<\s*\/(\S+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
241
                        // delete tag from $openTags list
242
                        $pos = \array_search($tag_matchings[1], $openTags, true);
243
                        if (false !== $pos) {
244
                            unset($openTags[$pos]);
245
                        }
246
                        // if tag is an opening tag
247
                    } elseif (\preg_match('/^<\s*([^\s>!]+).*?' . '>$/s', $line_matchings[1], $tag_matchings)) {
248
                        // add tag to the beginning of $openTags list
249
                        \array_unshift($openTags, \mb_strtolower($tag_matchings[1]));
250
                    }
251
                    // add html-tag to $truncate'd text
252
                    $truncate .= $line_matchings[1];
253
                }
254
                // calculate the length of the plain text part of the line; handle entities as one character
255
                $content_length = \mb_strlen(\preg_replace('/&[0-9a-z]{2,8};|&#\d{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
256
                if ($total_length + $content_length > $length) {
257
                    // the number of characters which are left
258
                    $left            = $length - $total_length;
259
                    $entities_length = 0;
260
                    // search for html entities
261
                    if (\preg_match_all('/&[0-9a-z]{2,8};|&#\d{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, \PREG_OFFSET_CAPTURE)) {
262
                        // calculate the real length of all entities in the legal range
263
                        foreach ($entities[0] as $entity) {
264
                            if ($left >= $entity[1] + 1 - $entities_length) {
265
                                $left--;
266
                                $entities_length += \mb_strlen($entity[0]);
267
                            } else {
268
                                // no more characters left
269
                                break;
270
                            }
271
                        }
272
                    }
273
                    $truncate .= \mb_substr($line_matchings[2], 0, $left + $entities_length);
274
                    // maximum length is reached, so get off the loop
275
                    break;
276
                }
277
                $truncate     .= $line_matchings[2];
278
                $total_length += $content_length;
279
280
                // if the maximum length is reached, get off the loop
281
                if ($total_length >= $length) {
282
                    break;
283
                }
284
            }
285
        } else {
286
            if (\mb_strlen($text) <= $length) {
287
                return $text;
288
            }
289
            $truncate = \mb_substr($text, 0, $length - \mb_strlen($ending));
290
        }
291
        // if the words shouldn't be cut in the middle...
292
        if (!$exact) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $exact of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
293
            // ...search the last occurance of a space...
294
            $spacepos = \mb_strrpos($truncate, ' ');
295
            if (isset($spacepos)) {
296
                // ...and cut the text in this position
297
                $truncate = \mb_substr($truncate, 0, $spacepos);
298
            }
299
        }
300
        // add the defined ending to the text
301
        $truncate .= $ending;
302
        if ($considerHtml) {
303
            // close all unclosed html-tags
304
            foreach ($openTags as $tag) {
305
                $truncate .= '</' . $tag . '>';
306
            }
307
        }
308
309
        return $truncate;
310
    }
311
312
    /**
313
     * Get correct text editor based on user rights
314
     *
315
     * @param \Xmf\Module\Helper|null $helper
316
     * @param array|null         $options
317
     * @return \XoopsFormDhtmlTextArea|\XoopsFormEditor
318
     */
319
    public static function getEditor(?\Xmf\Module\Helper $helper = null, ?array $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($options['name']), $helper->getConfig('editorAdmin'), $options, $nohtml = false, $onfailure = 'textarea');
341
            } else {
342
                $descEditor = new XoopsFormEditor(\ucfirst($options['name']), $helper->getConfig('editorUser'), $options, $nohtml = false, $onfailure = 'textarea');
343
            }
344
        } else {
345
            $descEditor = new \XoopsFormDhtmlTextArea(\ucfirst($options['name']), $options['name'], $options['value'], '100%', '100%');
0 ignored issues
show
Bug introduced by
'100%' of type string is incompatible with the type integer expected by parameter $rows of XoopsFormDhtmlTextArea::__construct(). ( Ignorable by Annotation )

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

345
            $descEditor = new \XoopsFormDhtmlTextArea(\ucfirst($options['name']), $options['name'], $options['value'], /** @scrutinizer ignore-type */ '100%', '100%');
Loading history...
Bug introduced by
'100%' of type string is incompatible with the type integer expected by parameter $cols of XoopsFormDhtmlTextArea::__construct(). ( Ignorable by Annotation )

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

345
            $descEditor = new \XoopsFormDhtmlTextArea(\ucfirst($options['name']), $options['name'], $options['value'], '100%', /** @scrutinizer ignore-type */ '100%');
Loading history...
346
        }
347
348
        //        $form->addElement($descEditor);
349
350
        return $descEditor;
351
    }
352
353
    /**
354
     * Check if column in dB table exists
355
     *
356
     * @param string $fieldname name of dB table field
357
     * @param string $table     name of dB table (including prefix)
358
     *
359
     * @return bool true if table exists
360
     * @deprecated
361
     */
362
    public static function fieldExists(string $fieldname, string $table): bool
363
    {
364
        $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1);
365
        \trigger_error(__METHOD__ . " is deprecated, use Xmf\Database\Tables instead - instantiated from {$trace[0]['file']} line {$trace[0]['line']},");
366
367
        $result = $GLOBALS['xoopsDB']->queryF("SHOW COLUMNS FROM   ${table} LIKE '${fieldname}'");
368
        return $GLOBALS['xoopsDB']->getRowsNum($result) > 0;
369
    }
370
371
    /**
372
     * Function responsible for checking if a directory exists, we can also write in and create an index.html file
373
     *
374
     * @param string $folder The full path of the directory to check
375
     */
376
    public static function prepareFolder(string $folder): void
377
    {
378
        try {
379
            if (!@\mkdir($folder) && !\is_dir($folder)) {
380
                throw new \RuntimeException(\sprintf('Unable to create the %s directory', $folder));
381
            }
382
            file_put_contents($folder . '/index.html', '<script>history.go(-1);</script>');
383
        } catch (\Throwable $e) {
384
            echo 'Caught exception: ', $e->getMessage(), "\n", '<br>';
385
        }
386
    }
387
388
    /**
389
     * Check if dB table table exists
390
     *
391
     * @param string $tablename dB tablename with prefix
392
     * @return bool true if table exists
393
     */
394
    public static function tableExists(string $tablename): bool
395
    {
396
        $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1);
397
        \trigger_error(__FUNCTION__ . " is deprecated, called from {$trace[0]['file']} line {$trace[0]['line']}");
398
        $GLOBALS['xoopsLogger']->addDeprecated(
399
            \basename(\dirname(__DIR__, 2)) . ' Module: ' . __FUNCTION__ . ' function is deprecated, please use Xmf\Database\Tables method(s) instead.' . " Called from {$trace[0]['file']}line {$trace[0]['line']}"
400
        );
401
        $result = $GLOBALS['xoopsDB']->queryF("SHOW TABLES LIKE '${tablename}'");
402
403
        return $GLOBALS['xoopsDB']->getRowsNum($result) > 0;
404
    }
405
406
    /**
407
     * Add a field to a mysql table
408
     *
409
     * @param $field
410
     * @param $table
411
     * @return bool|\mysqli_result
412
     */
413
    public static function addField($field, $table)
414
    {
415
        global $xoopsDB;
416
        return $xoopsDB->queryF('ALTER TABLE ' . $table . " ADD ${field};");
417
    }
418
}
419