Passed
Pull Request — master (#11)
by Michael
11:08
created

SysUtility::metaKeywords()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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