SysUtility::truncateHtml()   F
last analyzed

Complexity

Conditions 19
Paths 194

Size

Total Lines 95
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 19
eloc 47
nc 194
nop 5
dl 0
loc 95
rs 3.7333
c 3
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace XoopsModules\Publisher\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      https://www.fsf.org/copyleft/gpl.html GNU public license
20
 * @copyright    https://xoops.org 2000-2022 &copy; XOOPS Project
21
 * @author       ZySpec <[email protected]>
22
 * @author       Mamba <[email protected]>
23
 */
24
25
use Xmf\Request;
26
use Xmf\Module\Helper\Cache;
27
use XoopsFormEditor;
28
use XoopsModules\Publisher\{
29
    Helper
30
};
31
/**
32
 * Class SysUtility
33
 */
34
class SysUtility
35
{
36
    //traits
37
    use VersionChecks; //checkVerXoops, checkVerPhp Traits
0 ignored issues
show
introduced by
The trait XoopsModules\Publisher\Common\VersionChecks requires some properties which are not provided by XoopsModules\Publisher\Common\SysUtility: $tag_name, $prerelease
Loading history...
38
    use ServerStats; // getServerStats Trait
39
    use FilesManagement; // Files Management Trait
40
    use ModuleStats;    // ModuleStats Trait
0 ignored issues
show
Bug introduced by
The trait XoopsModules\Publisher\Common\ModuleStats requires the property $moduleStats which is not provided by XoopsModules\Publisher\Common\SysUtility.
Loading history...
41
42
    //--------------- Common module methods -----------------------------
43
44
    /**
45
     * Access the only instance of this class
46
     */
47
    public static function getInstance(): self
48
    {
49
        static $instance;
50
        if (null === $instance) {
51
            $instance = new static();
52
        }
53
54
        return $instance;
55
    }
56
57
    /**
58
     * @param string $text
59
     * @param string $form_sort
60
     *
61
     * @return string
62
     */
63
    public static function selectSorting(string $text, string $form_sort): string
64
    {
65
        global $start, $order, $sort;
66
67
        $selectView   = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $selectView is dead and can be removed.
Loading history...
68
        $helper = Helper::getInstance();
69
70
        //$pathModIcon16 = XOOPS_URL . '/modules/' . $moduleDirName . '/' . $helper->getConfig('modicons16');
71
        $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

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

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

412
        /** @scrutinizer ignore-call */ 
413
        $result = self::queryFAndCheck($GLOBALS['xoopsDB'], $sql);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
413
414
        return $GLOBALS['xoopsDB']->getRowsNum($result) > 0    ;
415
    }
416
417
418
    /**
419
     * Add a field to a mysql table
420
     *
421
     * @return bool|\mysqli_result
422
     */
423
    public static function addField(string $field, string $table)
424
    {
425
        global $xoopsDB;
426
        return $xoopsDB->queryF('ALTER TABLE ' . $table . " ADD $field;");
427
    }
428
429
    /**
430
     * @return void
431
     */
432
    public static function cleanCache(): void
433
    {
434
        $myDirName = \basename(\dirname(__DIR__, 2));
435
        $cacheHelper = new Cache($myDirName);
436
        if (\method_exists($cacheHelper, 'clear')) {
437
            $cacheHelper->clear();
438
439
            return;
440
        }
441
        // for 2.5 systems, clear everything
442
        require_once XOOPS_ROOT_PATH . '/modules/system/class/maintenance.php';
443
        $maintenance = new \SystemMaintenance();
444
        $cacheList   = [
445
            3, // xoops_cache
446
        ];
447
        $maintenance->CleanCache($cacheList);
448
        \xoops_setActiveModules();
449
    }
450
451
    /**
452
     * @return bool
453
     */
454
    public static function renameUploadFolder(): bool
455
    {
456
        $moduleDirName      = \basename(\dirname(__DIR__));
457
        $moduleDirNameUpper = \mb_strtoupper($moduleDirName);
458
        $helper             = Helper::getInstance();
459
460
        $success = true;
461
        $helper->loadLanguage('admin');
462
463
        // Rename uploads folder to BAK and add date to name
464
        $uploadDirectory = $GLOBALS['xoops']->path("uploads/$moduleDirName");
465
        $dirInfo         = new \SplFileInfo($uploadDirectory);
466
        if ($dirInfo->isDir()) {
467
            // The directory exists so rename it
468
            $date = \date('Y-m-d');
469
            if (!\rename($uploadDirectory, $uploadDirectory . "_BAK_$date")) {
470
                $helper->getModule()->setErrors(\sprintf(\constant('CO_' . $moduleDirNameUpper . '_ERROR_BAD_DEL_PATH'), $uploadDirectory));
471
                $success = false;
472
            }
473
        }
474
        unset($dirInfo);
475
476
        return $success;
477
    }
478
}
479