Issues (411)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

class/Utility.php (2 issues)

1
<?php
2
3
namespace XoopsModules\Wfdownloads;
4
5
/*
6
 You may not change or alter any portion of this comment or credits
7
 of supporting developers from this source code or any supporting source code
8
 which is considered copyrighted (c) material of the original comment or credit authors.
9
10
 This program is distributed in the hope that it will be useful,
11
 but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
 */
14
15
/**
16
 * WfdownloadsUtil Class
17
 *
18
 * @copyright   XOOPS Project (https://xoops.org)
19
 * @license     http://www.fsf.org/copyleft/gpl.html GNU public license
20
 * @author      XOOPS Development Team
21
 * @package     Wfdownloads
22
 * @since       1.03
23
 */
24
25
use Xmf\Request;
26
use XoopsModules\Wfdownloads;
27
use XoopsModules\Wfdownloads\Common;
28
use XoopsModules\Wfdownloads\Constants;
29
30
/**
31
 * Class Utility
32
 */
33
class Utility extends Common\SysUtility
34
{
35
    //--------------- Custom module methods -----------------------------
36
    /**
37
     * @param     $categoryObj
38
     * @param int $level
39
     */
40
    public static function displayCategory(Wfdownloads\Category $categoryObj, $level = 0)
41
    {
42
        $helper = Helper::getInstance();
43
44
        $description = $categoryObj->description();
45
        if (!XOOPS_USE_MULTIBYTES) {
46
            if (mb_strlen($description) >= 100) {
47
                $description = mb_substr($description, 0, 100 - 1) . '...';
48
            }
49
        }
50
        $modify = "<a href='category.php?op=mod&amp;categoryid=" . $categoryObj->categoryid() . '&amp;parentid=' . $categoryObj->parentid() . "'><img src='" . PUBLISHER_URL . "/assets/images/links/edit.gif' title='" . \_AM_PUBLISHER_EDITCOL . "' alt='" . \_AM_PUBLISHER_EDITCOL . "'></a>";
51
        $delete = "<a href='category.php?op=del&amp;categoryid=" . $categoryObj->categoryid() . "'><img src='" . PUBLISHER_URL . "/assets/images/links/delete.png' title='" . \_AM_PUBLISHER_DELETECOL . "' alt='" . \_AM_PUBLISHER_DELETECOL . "'></a>";
52
53
        $spaces = '';
54
        for ($j = 0; $j < $level; ++$j) {
55
            $spaces .= '&nbsp;&nbsp;&nbsp;';
56
        }
57
58
        echo '<tr>';
59
        echo "<td class='even' align='center'>" . $categoryObj->categoryid() . '</td>';
60
        echo "<td class='even' align='left'>" . $spaces . "<a href='" . PUBLISHER_URL . '/category.php?categoryid=' . $categoryObj->categoryid() . "'><img src='" . PUBLISHER_URL . "/assets/images/links/subcat.gif' alt=''>&nbsp;" . $categoryObj->name() . '</a></td>';
61
        echo "<td class='even' align='center'>" . $categoryObj->weight() . '</td>';
62
        echo "<td class='even' align='center'> $modify $delete </td>";
63
        echo '</tr>';
64
        $subCategoriesObj = $helper->getHandler('Category')->getCategories(0, 0, $categoryObj->categoryid());
65
        if (\count($subCategoriesObj) > 0) {
66
            ++$level;
67
            foreach ($subCategoriesObj as $key => $thiscat) {
68
                self::displayCategory($thiscat, $level);
69
            }
70
            unset($key, $thiscat);
71
        }
72
        //        unset($categoryObj);
73
    }
74
75
    /**
76
     * @param bool $showmenu
77
     * @param int  $categoryId
78
     * @param int  $nbSubCats
79
     * @param null $categoryObj
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $categoryObj is correct as it would always require null to be passed?
Loading history...
80
     */
81
    public static function editCategory($showmenu = false, $categoryId = 0, $nbSubCats = 4, $categoryObj = null)
82
    {
83
        $helper = Helper::getInstance();
84
85
        // if there is a parameter, and the id exists, retrieve data: we're editing a category
86
        if (0 != $categoryId) {
87
            // Creating the category object for the selected category
88
            $categoryObj = $helper->getHandler('Category')->get($categoryId);
89
            if ($categoryObj->notLoaded()) {
90
                \redirect_header('category.php', 1, \_AM_PUBLISHER_NOCOLTOEDIT);
91
                //            exit();
92
            }
93
        } else {
94
            if (!$categoryObj) {
95
                $categoryObj = $helper->getHandler('Category')->create();
96
            }
97
        }
98
99
        if (0 != $categoryId) {
100
            echo "<br>\n";
101
            publisherOpenCollapsableBar('edittable', 'edittableicon', \_AM_PUBLISHER_EDITCOL, \_AM_PUBLISHER_CATEGORY_EDIT_INFO);
102
        } else {
103
            publisherOpenCollapsableBar('createtable', 'createtableicon', \_AM_PUBLISHER_CATEGORY_CREATE, \_AM_PUBLISHER_CATEGORY_CREATE_INFO);
104
        }
105
106
        $sform = $categoryObj->getForm($nbSubCats);
107
        $sform->display();
108
109
        if (!$categoryId) {
110
            publisherCloseCollapsableBar('createtable', 'createtableicon');
111
        } else {
112
            publisherCloseCollapsableBar('edittable', 'edittableicon');
113
        }
114
115
        //Added by fx2024
116
        if ($categoryId) {
117
            $selCat = $categoryId;
118
119
            publisherOpenCollapsableBar('subcatstable', 'subcatsicon', \_AM_PUBLISHER_SUBCAT_CAT, \_AM_PUBLISHER_SUBCAT_CAT_DSC);
120
            // Get the total number of sub-categories
121
            $categoriesObj = $helper->getHandler('Category')->get($selCat);
122
            $totalsubs     = $helper->getHandler('Category')->getCategoriesCount($selCat);
123
            // creating the categories objects that are published
124
            $subcatsObj    = $helper->getHandler('Category')->getCategories(0, 0, $categoriesObj->categoryid());
125
            $totalSCOnPage = \count($subcatsObj);
126
            echo "<table width='100%' cellspacing=1 cellpadding=3 border=0 class = outer>";
127
            echo '<tr>';
128
            echo "<td width='60' class='bg3' align='left'><strong>" . \_AM_PUBLISHER_CATID . '</strong></td>';
129
            echo "<td width='20%' class='bg3' align='left'><strong>" . \_AM_PUBLISHER_CATCOLNAME . '</strong></td>';
130
            echo "<td class='bg3' align='left'><strong>" . \_AM_PUBLISHER_SUBDESCRIPT . '</strong></td>';
131
            echo "<td width='60' class='bg3' align='right'><strong>" . \_AM_PUBLISHER_ACTION . '</strong></td>';
132
            echo '</tr>';
133
            if ($totalsubs > 0) {
134
                foreach ($subcatsObj as $subcat) {
135
                    $modify = "<a href='category.php?op=mod&amp;categoryid=" . $subcat->categoryid() . "'><img src='" . XOOPS_URL . '/modules/' . $helper->getModule()->dirname() . "/assets/images/links/edit.gif' title='" . \_AM_PUBLISHER_MODIFY . "' alt='" . \_AM_PUBLISHER_MODIFY . "'></a>";
136
                    $delete = "<a href='category.php?op=del&amp;categoryid=" . $subcat->categoryid() . "'><img src='" . XOOPS_URL . '/modules/' . $helper->getModule()->dirname() . "/assets/images/links/delete.png' title='" . \_AM_PUBLISHER_DELETE . "' alt='" . \_AM_PUBLISHER_DELETE . "'></a>";
137
                    echo '<tr>';
138
                    echo "<td class='head' align='left'>" . $subcat->categoryid() . '</td>';
139
                    echo "<td class='even' align='left'><a href='" . XOOPS_URL . '/modules/' . $helper->getModule()->dirname() . '/category.php?categoryid=' . $subcat->categoryid() . '&amp;parentid=' . $subcat->parentid() . "'>" . $subcat->name() . '</a></td>';
140
                    echo "<td class='even' align='left'>" . $subcat->description() . '</td>';
141
                    echo "<td class='even' align='right'> {$modify} {$delete} </td>";
142
                    echo '</tr>';
143
                }
144
                //                unset($subcat);
145
            } else {
146
                echo '<tr>';
147
                echo "<td class='head' align='center' colspan= '7'>" . \_AM_PUBLISHER_NOSUBCAT . '</td>';
148
                echo '</tr>';
149
            }
150
            echo "</table>\n";
151
            echo "<br>\n";
152
            publisherCloseCollapsableBar('subcatstable', 'subcatsicon');
153
154
            publisherOpenCollapsableBar('bottomtable', 'bottomtableicon', \_AM_PUBLISHER_CAT_ITEMS, \_AM_PUBLISHER_CAT_ITEMS_DSC);
155
            $startitem = Request::getInt('startitem');
156
            // Get the total number of published ITEMS
157
            $totalitems = $helper->getHandler('Item')->getItemsCount($selCat, [Constants::PUBLISHER_STATUS_PUBLISHED]);
158
            // creating the items objects that are published
159
            $itemsObj         = $helper->getHandler('Item')->getAllPublished($helper->getConfig('idxcat_perpage'), $startitem, $selCat);
160
            $allcats          = $helper->getHandler('Category')->getObjects(null, true);
161
            echo "<table width='100%' cellspacing=1 cellpadding=3 border=0 class = outer>";
162
            echo '<tr>';
163
            echo "<td width='40' class='bg3' align='center'><strong>" . \_AM_PUBLISHER_ITEMID . '</strong></td>';
164
            echo "<td width='20%' class='bg3' align='left'><strong>" . \_AM_PUBLISHER_ITEMCOLNAME . '</strong></td>';
165
            echo "<td class='bg3' align='left'><strong>" . \_AM_PUBLISHER_ITEMDESC . '</strong></td>';
166
            echo "<td width='90' class='bg3' align='center'><strong>" . \_AM_PUBLISHER_CREATED . '</strong></td>';
167
            echo "<td width='60' class='bg3' align='center'><strong>" . \_AM_PUBLISHER_ACTION . '</strong></td>';
168
            echo '</tr>';
169
            if ($totalitems > 0) {
170
                foreach ($itemsObj as $iValue) {
171
                    $categoryObj = $allcats[$iValue->categoryid()];
172
                    $modify      = "<a href='item.php?op=mod&amp;itemid=" . $iValue->itemid() . "'><img src='" . XOOPS_URL . '/modules/' . $helper->getModule()->dirname() . "/assets/images/links/edit.gif' title='" . \_AM_PUBLISHER_EDITITEM . "' alt='" . \_AM_PUBLISHER_EDITITEM . "'></a>";
173
                    $delete      = "<a href='item.php?op=del&amp;itemid="
174
                                   . $iValue->itemid()
175
                                   . "'><img src='"
176
                                   . XOOPS_URL
177
                                   . '/modules/'
178
                                   . $helper->getModule()->dirname()
179
                                   . "/assets/images/links/delete.png' title='"
180
                                   . \_AM_PUBLISHER_DELETEITEM
181
                                   . "' alt='"
182
                                   . \_AM_PUBLISHER_DELETEITEM
183
                                   . "'></a>";
184
                    echo '<tr>';
185
                    echo "<td class='head' align='center'>" . $iValue->itemid() . '</td>';
186
                    echo "<td class='even' align='left'>" . $categoryObj->name() . '</td>';
187
                    echo "<td class='even' align='left'>" . $iValue->getitemLink() . '</td>';
188
                    echo "<td class='even' align='center'>" . $iValue->getDatesub('s') . '</td>';
189
                    echo "<td class='even' align='center'> $modify $delete </td>";
190
                    echo '</tr>';
191
                }
192
            } else {
193
                $itemid = -1;
194
                echo '<tr>';
195
                echo "<td class='head' align='center' colspan= '7'>" . \_AM_PUBLISHER_NOITEMS . '</td>';
196
                echo '</tr>';
197
            }
198
            echo "</table>\n";
199
            echo "<br>\n";
200
            $parentid         = Request::getInt('parentid', 0, 'GET');
201
            $pagenavExtraArgs = "op=mod&categoryid=$selCat&parentid=$parentid";
202
            \xoops_load('XoopsPageNav');
203
            $pagenav = new \XoopsPageNav($totalitems, $helper->getConfig('idxcat_perpage'), $startitem, 'startitem', $pagenavExtraArgs);
204
            echo '<div style="text-align:right;">' . $pagenav->renderNav() . '</div>';
205
            echo "<input type='button' name='button' onclick=\"location='item.php?op=mod&categoryid=" . $selCat . "'\" value='" . \_AM_PUBLISHER_CREATEITEM . "'>&nbsp;&nbsp;";
206
            echo '</div>';
207
        }
208
        //end of fx2024 code
209
    }
210
211
    // ====================== START ===================================
212
213
    /**
214
     * Standard functions
215
     *
216
     * @param mixed $bytes
217
     * @param mixed $precision
218
     */
219
220
    /**
221
     * This function transforms a numerical size (like 2048) to a letteral size (like 2MB)
222
     *
223
     * @param int $bytes numerical size
224
     * @param int $precision
225
     *
226
     * @return string letteral size
227
     **/
228
    public static function bytesToSize1000($bytes, $precision = 2)
229
    {
230
        // human readable format -- powers of 1000
231
        $unit = ['b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb'];
232
233
        return @\round($bytes / \pow(1000, $i = \floor(\log($bytes, 1000))), $precision) . ' ' . $unit[(int)$i];
234
    }
235
236
    /**
237
     * @param int    $bytes
238
     * @param int $precision
239
     *
240
     * @return string
241
     */
242
    public static function bytesToSize1024($bytes, $precision = 2)
243
    {
244
        if (0 === $bytes) {
245
            return 0;
246
        }
247
248
        // human readable format -- powers of 1024
249
        $unit = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB'];
250
251
        $i = \floor(\log($bytes, 1024));
252
253
        return @\round($bytes / (\pow(1024, $i)), $precision) . ' ' . $unit[(int)$i];
254
255
256
257
    }
258
259
    /**
260
     * This function transforms the php.ini notation for numbers (like '2M') to an integer (2*1024*1024 in this case)
261
     *
262
     * @param string $size letteral size
263
     *
264
     * @return int numerical size
265
     **/
266
    public static function sizeToBytes1024($size)
267
    {
268
        $l   = mb_substr($size, -1);
269
        $ret = mb_substr($size, 0, -1);
270
        switch (mb_strtoupper($l)) {
271
            case 'P':
272
            case 'p':
273
                $ret *= 1024;
274
                break;
275
            case 'T':
276
            case 't':
277
                $ret *= 1024;
278
                break;
279
            case 'G':
280
            case 'g':
281
                $ret *= 1024;
282
                break;
283
            case 'M':
284
            case 'm':
285
                $ret *= 1024;
286
                break;
287
            case 'K':
288
            case 'k':
289
                $ret *= 1024;
290
                break;
291
        }
292
293
        return $ret;
294
    }
295
296
    /**
297
     * Filesystem functions
298
     *
299
     * @param mixed $path
300
     * @param mixed $level
301
     */
302
303
    /**
304
     * This function will read the full structure of a directory.
305
     * It's recursive because it doesn't stop with the one directory,
306
     * it just keeps going through all of the directories in the folder you specify.
307
     *
308
     * @param string $path path to the directory to make
309
     * @param int    $level
310
     *
311
     * @return array
312
     */
313
    public static function getDir($path = '.', $level = 0)
314
    {
315
        $ret    = [];
316
        $ignore = ['cgi-bin', '.', '..'];
317
        // Directories to ignore when listing output. Many hosts will deny PHP access to the cgi-bin.
318
        $dirHandler = @\opendir($path);
319
        // Open the directory to the handle $dirHandler
320
        while (false !== ($file = \readdir($dirHandler))) {
321
            // Loop through the directory
322
            if (!\in_array($file, $ignore)) {
323
                // Check that this file is not to be ignored
324
                $spaces = \str_repeat('&nbsp;', $level * 4);
325
                // Just to add spacing to the list, to better show the directory tree.
326
                if (\is_dir("$path/$file")) {
327
                    // Its a directory, so we need to keep reading down...
328
                    $ret[] = "<strong>{$spaces} {$file}</strong>";
329
                    $ret   = \array_merge($ret, self::getDir($path . \DIRECTORY_SEPARATOR . $file, $level + 1));
330
                    // Re-call this same function but on a new directory.
331
                    // this is what makes function recursive.
332
                } else {
333
                    $ret[] = "{$spaces} {$file}";
334
                    // Just print out the filename
335
                }
336
            }
337
        }
338
        \closedir($dirHandler);
339
340
        // close the directory handle
341
        return $ret;
342
    }
343
344
    /**
345
     * Create a new directory that contains the file index.html
346
     *
347
     * @param string $dir          path to the directory to make
348
     * @param int    $perm         mode
349
     * @param bool   $create_index if true create index.html
350
     *
351
     * @return bool Returns true on success or false on failure
352
     * @throws \Exception
353
     * @throws \RuntimeException
354
     */
355
    public static function makeDir($dir, $perm = 0777, $create_index = true)
356
    {
357
        if (!\mkdir($dir, $perm) && !\is_dir($dir)) {
358
            throw new \RuntimeException('The directory ' . $dir . ' could not be created.');
359
        }
360
        if ($create_index) {
361
            if (false !== ($fileHandler = @\fopen($dir . '/index.html', 'wb'))) {
362
                \fwrite($fileHandler, '<script>history.go(-1);</script>');
363
            }
364
            if (!@\fclose($fileHandler)) {
365
                throw new \RuntimeException('The file ' . $fileHandler . ' could not be created.');
366
            }
367
        }
368
369
        return true;
370
371
        return null;
372
    }
373
374
    /**
375
     * @param string $path
376
     *
377
     * @return array
378
     */
379
    public static function getFiles($path = '.')
380
    {
381
        $files = [];
382
        $dir   = \opendir($path);
383
        while (false !== ($file = \readdir($dir))) {
384
            if (\is_file($path . $file)) {
385
                if ('.' !== $file && '..' !== $file && 'blank.png' !== $file && 'index.html' !== $file) {
386
                    $files[] = $file;
387
                }
388
            }
389
        }
390
391
        return $files;
392
    }
393
394
    /**
395
     * Copy a file
396
     *
397
     * @param string $source      is the original directory
398
     * @param string $destination is the destination directory
399
     *
400
     * @return bool Returns true on success or false on failure
401
     */
402
    /*
403
    public static function copyFile($source, $destination)
404
    {
405
        // Simple copy for a file
406
        if (is_file($source)) {
407
            return copy($source, $destination);
408
        } else {
409
            return false;
410
        }
411
    }
412
    */
413
414
    /**
415
     * Copy a directory and its contents
416
     *
417
     * @param string $source      is the original directory
418
     * @param string $destination is the destination directory
419
     *
420
     * @return bool Returns true on success or false on failure
421
     * @throws \Exception
422
     * @throws \RuntimeException
423
     */
424
    public static function copyDir($source, $destination)
425
    {
426
        if (!$dirHandler = \opendir($source)) {
427
            return false;
428
        }
429
        if (\mkdir($destination) || \is_dir($destination)) {
430
            throw new \RuntimeException('The directory ' . $destination . ' could not be created.');
431
        }
432
        while (false !== ($file = \readdir($dirHandler))) {
433
            if (('.' !== $file) && ('..' !== $file)) {
434
                if (\is_dir("{$source}/{$file}")) {
435
                    if (!self::copyDir("{$source}/{$file}", "{$destination}/{$file}")) {
436
                        return false;
437
                    }
438
                } else {
439
                    if (!\copy("{$source}/{$file}", "{$destination}/{$file}")) {
440
                        return false;
441
                    }
442
                }
443
            }
444
        }
445
        \closedir($dirHandler);
446
447
        return true;
448
    }
449
450
    /**
451
     * Delete a file
452
     *
453
     * @param string $path is the file absolute path
454
     *
455
     * @return bool Returns true on success or false on failure
456
     */
457
    public static function delFile($path)
458
    {
459
        if (\is_file($path)) {
460
            @\chmod($path, 0777);
461
462
            return @\unlink($path);
463
        }
464
465
        return false;
466
    }
467
468
    /**
469
     * Delete a empty/not empty directory
470
     *
471
     * @param string $dir          path to the directory to delete
472
     * @param bool   $if_not_empty if false it delete directory only if false
473
     *
474
     * @return bool Returns true on success or false on failure
475
     */
476
    public static function delDir($dir, $if_not_empty = true)
477
    {
478
        if (!\file_exists($dir)) {
479
            return true;
480
        }
481
        if ($if_not_empty) {
482
            if (!\is_dir($dir)) {
483
                return \unlink($dir);
484
            }
485
            foreach (\scandir($dir, \SCANDIR_SORT_NONE) as $item) {
486
                if ('.' === $item || '..' === $item) {
487
                    continue;
488
                }
489
                if (!self::delDir("{$dir}/{$item}")) {
490
                    return false;
491
                }
492
            }
493
        }
494
        // NOP
495
496
        return \rmdir($dir);
497
    }
498
499
    /**
500
     * Module functions
501
     *
502
     * @param mixed $dirname
503
     */
504
505
    /**
506
     * Check if a module exist and return module verision
507
     *
508
     * @param string $dirname
509
     *
510
     * @return bool, integer   false if module not installed or not active, module version if installed
511
     *
512
     * @access  public
513
     * @author  luciorota
514
     */
515
    public static function checkModule($dirname)
516
    {
517
        if (!\xoops_isActiveModule($dirname)) {
518
            return false;
519
        }
520
        /** @var \XoopsModuleHandler $moduleHandler */
521
        $moduleHandler = \xoops_getHandler('module');
522
        $module        = $moduleHandler->getByDirname($dirname);
523
524
        return $module->getVar('version');
525
    }
526
527
    /**
528
     * Recursively sort categories by level and weight
529
     *
530
     * @param int $pid
531
     * @param int $level
532
     *
533
     * @return array array of arrays: 'pid', 'cid', 'level', 'category' as array
534
     *
535
     * @access  public
536
     * @author  luciorota
537
     */
538
    public static function sortCategories($pid = 0, $level = 0)
539
    {
540
        $helper = Helper::getInstance();
541
542
        $sorted   = [];
543
        $criteria = new \CriteriaCompo();
544
        $criteria->add(new \Criteria('pid', $pid));
545
        $criteria->setSort('weight');
546
        $criteria->setOrder('ASC');
547
        $subCategoryObjs = $helper->getHandler('Category')->getObjects($criteria);
548
        if (\count($subCategoryObjs) > 0) {
549
            ++$level;
550
            foreach ($subCategoryObjs as $subCategoryObj) {
551
                $pid      = $subCategoryObj->getVar('pid');
552
                $cid      = $subCategoryObj->getVar('cid');
553
                $sorted[] = ['pid' => $pid, 'cid' => $cid, 'level' => $level, 'category' => $subCategoryObj->toArray()];
554
                if (false !== ($subSorted = self::sortCategories($cid, $level))) {
555
                    $sorted = \array_merge($sorted, $subSorted);
556
                }
557
            }
558
        }
559
560
        return $sorted;
561
    }
562
563
    /**
564
     * Create download by letter choice bar/menu
565
     * updated starting from this idea https://xoops.org/modules/news/article.php?storyid=6497
566
     *
567
     * @return string html
568
     *
569
     * @access  public
570
     * @author  luciorota
571
     */
572
    public static function lettersChoice()
573
    {
574
        $helper = Helper::getInstance();
575
576
        $criteria = $helper->getHandler('Download')->getActiveCriteria();
577
        $criteria->setGroupby('UPPER(LEFT(title,1))');
578
        $countsByLetters = $helper->getHandler('Download')->getCounts($criteria);
579
        // Fill alphabet array
580
        $alphabet      = \getLocalAlphabet();
581
        $alphabetArray = [];
582
        foreach ($alphabet as $letter) {
583
            $letter_array = [];
584
            if (isset($countsByLetters[$letter])) {
585
                $letter_array['letter'] = $letter;
586
                $letter_array['count']  = $countsByLetters[$letter];
587
                $letter_array['url']    = XOOPS_URL . "/modules/{$helper->getModule()->dirname()}/viewcat.php?letter={$letter}";
588
            } else {
589
                $letter_array['letter'] = $letter;
590
                $letter_array['count']  = 0;
591
                $letter_array['url']    = '';
592
            }
593
            $alphabetArray[$letter] = $letter_array;
594
            unset($letter_array);
595
        }
596
        // Render output
597
        if (!isset($GLOBALS['xoTheme']) || !\is_object($GLOBALS['xoTheme'])) {
598
            require_once $GLOBALS['xoops']->path('/class/theme.php');
599
            $GLOBALS['xoTheme'] = new \xos_opal_Theme();
600
        }
601
        require_once $GLOBALS['xoops']->path('class/template.php');
602
        $letterschoiceTpl          = new \XoopsTpl();
603
        $letterschoiceTpl->caching = false; // Disable cache
0 ignored issues
show
Documentation Bug introduced by
The property $caching was declared of type integer, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
604
        $letterschoiceTpl->assign('alphabet', $alphabetArray);
605
        $html = $letterschoiceTpl->fetch("db:{$helper->getModule()->dirname()}_co_letterschoice.tpl");
606
        unset($letterschoiceTpl);
607
608
        return $html;
609
    }
610
611
    /**
612
     * Checks if a user is admin of Wfdownloads
613
     *
614
     * @return bool
615
     */
616
    public static function userIsAdmin()
617
    {
618
        $helper = Helper::getInstance();
619
620
        static $wfdownloads_isAdmin;
621
        if (null !== $wfdownloads_isAdmin) {
622
            return $wfdownloads_isAdmin;
623
        }
624
        $wfdownloads_isAdmin = (!\is_object($GLOBALS['xoopsUser'])) ? false : $GLOBALS['xoopsUser']->isAdmin($helper->getModule()->getVar('mid'));
625
626
        return $wfdownloads_isAdmin;
627
    }
628
629
    public static function getCpHeader()
630
    {
631
        \xoops_cp_header();
632
    }
633
634
    /**
635
     * @param bool $withLink
636
     *
637
     * @return string
638
     */
639
    public static function moduleHome($withLink = true)
640
    {
641
        $helper = Helper::getInstance();
642
643
        $wfdownloadsModuleName = $helper->getModule()->getVar('name');
644
        if (!$withLink) {
645
            return $wfdownloadsModuleName;
646
        }
647
648
        return '<a href="' . WFDOWNLOADS_URL . '/">{$wfdownloadsModuleName}</a>';
649
    }
650
651
    /**
652
     * Detemines if a table exists in the current db
653
     *
654
     * @param string $table the table name (without XOOPS prefix)
655
     *
656
     * @return bool True if table exists, false if not
657
     *
658
     * @access public
659
     * @author xhelp development team
660
     */
661
    public static function tableExists($table)
662
    {
663
        $bRetVal = false;
664
        //Verifies that a MySQL table exists
665
        $GLOBALS['xoopsDB'] = \XoopsDatabaseFactory::getDatabaseConnection();
666
        $realName           = $GLOBALS['xoopsDB']->prefix($table);
667
668
        $sql = 'SHOW TABLES FROM ' . XOOPS_DB_NAME;
669
        $ret = $GLOBALS['xoopsDB']->queryF($sql);
670
        while (list($m_table) = $GLOBALS['xoopsDB']->fetchRow($ret)) {
671
            if ($m_table == $realName) {
672
                $bRetVal = true;
673
                break;
674
            }
675
        }
676
        $GLOBALS['xoopsDB']->freeRecordSet($ret);
677
678
        return $bRetVal;
679
    }
680
681
    /**
682
     * Gets a value from a key in the xhelp_meta table
683
     *
684
     * @param string $key
685
     *
686
     * @return string $value
687
     *
688
     * @access public
689
     * @author xhelp development team
690
     */
691
    public static function getMeta($key)
692
    {
693
        $GLOBALS['xoopsDB'] = \XoopsDatabaseFactory::getDatabaseConnection();
694
        $sql                = \sprintf('SELECT metavalue FROM `%s` WHERE metakey=%s', $GLOBALS['xoopsDB']->prefix('wfdownloads_meta'), $GLOBALS['xoopsDB']->quoteString($key));
695
        $ret                = $GLOBALS['xoopsDB']->query($sql);
696
        if (!$ret) {
697
            $value = false;
698
        } else {
699
            [$value] = $GLOBALS['xoopsDB']->fetchRow($ret);
700
        }
701
702
        return $value;
703
    }
704
705
    /**
706
     * Sets a value for a key in the xhelp_meta table
707
     *
708
     * @param string $key
709
     * @param string $value
710
     *
711
     * @return bool true if success, false if failure
712
     *
713
     * @access public
714
     * @author xhelp development team
715
     */
716
    public static function setMeta($key, $value)
717
    {
718
        $GLOBALS['xoopsDB'] = \XoopsDatabaseFactory::getDatabaseConnection();
719
        if (false !== ($ret = self::getMeta($key))) {
720
            $sql = \sprintf('UPDATE `%s` SET metavalue = %s WHERE metakey = %s', $GLOBALS['xoopsDB']->prefix('wfdownloads_meta'), $GLOBALS['xoopsDB']->quoteString($value), $GLOBALS['xoopsDB']->quoteString($key));
721
        } else {
722
            $sql = \sprintf('INSERT INTO `%s` (metakey, metavalue) VALUES (%s, %s)', $GLOBALS['xoopsDB']->prefix('wfdownloads_meta'), $GLOBALS['xoopsDB']->quoteString($key), $GLOBALS['xoopsDB']->quoteString($value));
723
        }
724
        $ret = $GLOBALS['xoopsDB']->queryF($sql);
725
        if (!$ret) {
726
            return false;
727
        }
728
729
        return true;
730
    }
731
732
    /**
733
     * @param           $name
734
     * @param           $value
735
     * @param int|float $time
736
     */
737
    public static function setCookieVar($name, $value, $time = 0)
738
    {
739
        if (0 == $time) {
740
            $time = \time() + 3600 * 24 * 365;
741
            //$time = '';
742
        }
743
        setcookie($name, $value, $time, '/', \ini_get('session.cookie_domain'), \ini_get('session.cookie_secure'), \ini_get('session.cookie_httponly'));
744
    }
745
746
    /**
747
     * @param        $name
748
     * @param string $default
749
     *
750
     * @return string
751
     */
752
    public static function getCookieVar($name, $default = '')
753
    {
754
        if (isset($_COOKIE[$name]) && ($_COOKIE[$name] > '')) {
755
            return $_COOKIE[$name];
756
        }
757
758
        return $default;
759
    }
760
761
    /**
762
     * @return array
763
     */
764
    public static function getCurrentUrls()
765
    {
766
        $http        = (false === mb_strpos(XOOPS_URL, 'https://')) ? 'http://' : 'https://';
767
        $phpSelf     = $_SERVER['SCRIPT_NAME'];
768
        $httpHost    = $_SERVER['HTTP_HOST'];
769
        $queryString = $_SERVER['QUERY_STRING'];
770
771
        if ('' !== $queryString) {
772
            $queryString = '?' . $queryString;
773
        }
774
        $currentURL          = $http . $httpHost . $phpSelf . $queryString;
775
        $urls                = [];
776
        $urls['http']        = $http;
777
        $urls['httphost']    = $httpHost;
778
        $urls['phpself']     = $phpSelf;
779
        $urls['querystring'] = $queryString;
780
        $urls['full']        = $currentURL;
781
782
        return $urls;
783
    }
784
785
    /**
786
     * @return mixed
787
     */
788
    public static function getCurrentPage()
789
    {
790
        $urls = self::getCurrentUrls();
791
792
        return $urls['full'];
793
    }
794
795
    /**
796
     * @param array $errors
797
     *
798
     * @return string
799
     */
800
    public static function formatErrors($errors = [])
801
    {
802
        $ret = '';
803
        //mb    foreach ($errors as $key => $value) {
804
        foreach ($errors as $value) {
805
            $ret .= "<br> - {$value}";
806
        }
807
808
        return $ret;
809
    }
810
811
    // TODO : The SEO feature is not fully implemented in the module...
812
813
    /**
814
     * @param        $op
815
     * @param        $id
816
     * @param string $title
817
     *
818
     * @return string
819
     */
820
    public static function generateSeoUrl($op, $id, $title = '')
821
    {
822
        if (\defined('SEO_ENABLED')) {
823
            if (SEO_ENABLED === 'rewrite') {
824
                // generate SEO url using htaccess
825
                return XOOPS_URL . "/wfdownloads.${op}.${id}/" . self::getSeoTitle($title);
826
            }
827
828
            if (SEO_ENABLED === 'path-info') {
829
                // generate SEO url using path-info
830
                return XOOPS_URL . "/modules/wfdownloads/seo.php/${op}.${id}/" . self::getSeoTitle($title);
831
            }
832
            exit('Unknown SEO method.');
833
        }
834
        // generate classic url
835
        switch ($op) {
836
            case 'category':
837
                return XOOPS_URL . "/modules/wfdownloads/${op}.php?categoryid=${id}";
838
            case 'item':
839
            case 'print':
840
                return XOOPS_URL . "/modules/wfdownloads/${op}.php?itemid=${id}";
841
            default:
842
                exit('Unknown SEO operation.');
843
        }
844
    }
845
846
    /**
847
     * @param string $title
848
     * @return string
849
     */
850
    public static function getSeoTitle($title = '')
851
    {
852
        $words = \preg_split('/[^0-9a-z.]+/', mb_strtolower($title), -1, \PREG_SPLIT_NO_EMPTY);
853
        if (\count($words) > 0) {
854
            return \implode('-', $words) . '.html';
855
        }
856
857
        return '';
858
    }
859
860
    /**
861
     * save_Permissions()
862
     *
863
     * @param $groups
864
     * @param $id
865
     * @param $permName
866
     *
867
     * @return bool
868
     */
869
    public static function savePermissions($groups, $id, $permName)
870
    {
871
        $helper = Helper::getInstance();
872
873
        $id               = (int)$id;
874
        $result           = true;
875
        $mid              = $helper->getModule()->mid();
876
        $grouppermHandler = \xoops_getHandler('groupperm');
877
        // First, if the permissions are already there, delete them
878
        $grouppermHandler->deleteByModule($mid, $permName, $id);
879
        // Save the new permissions
880
        if (\is_array($groups)) {
881
            foreach ($groups as $group_id) {
882
                $grouppermHandler->addRight($permName, $id, $group_id, $mid);
883
            }
884
        }
885
886
        return $result;
887
    }
888
889
    /**
890
     * toolbar()
891
     *
892
     * @return string
893
     */
894
    public static function toolbar()
895
    {
896
        $helper = Helper::getInstance();
897
898
        $isSubmissionAllowed = false;
899
        if (\is_object($GLOBALS['xoopsUser'])
900
            && (\_WFDOWNLOADS_SUBMISSIONS_DOWNLOAD == $helper->getConfig('submissions')
901
                || \_WFDOWNLOADS_SUBMISSIONS_BOTH == $helper->getConfig('submissions'))) {
902
            $groups = $GLOBALS['xoopsUser']->getGroups();
903
            if (\count(\array_intersect($helper->getConfig('submitarts'), $groups)) > 0) {
904
                $isSubmissionAllowed = true;
905
            }
906
        } elseif (!\is_object($GLOBALS['xoopsUser']) && (\_WFDOWNLOADS_ANONPOST_DOWNLOAD == $helper->getConfig('anonpost') || \_WFDOWNLOADS_ANONPOST_BOTH == $helper->getConfig('anonpost'))) {
907
            $isSubmissionAllowed = true;
908
        }
909
        $toolbar = '[ ';
910
        if (true === $isSubmissionAllowed) {
911
            $category_suffix = Request::getInt('cid', '', 'GET'); //Added by Lankford
912
            $toolbar         .= "<a href='submit.php{$category_suffix}'>" . \_MD_WFDOWNLOADS_SUBMITDOWNLOAD . '</a> | ';
913
        }
914
        $toolbar .= "<a href='newlist.php'>" . \_MD_WFDOWNLOADS_LATESTLIST . '</a>';
915
        $toolbar .= ' | ';
916
        $toolbar .= "<a href='topten.php?list=hit'>" . \_MD_WFDOWNLOADS_POPULARITY . '</a>';
917
        if ($helper->getConfig('enable_ratings')) {
918
            $toolbar .= ' | ';
919
            $toolbar .= "<a href='topten.php?list=rate'>" . \_MD_WFDOWNLOADS_TOPRATED . '</a>';
920
        }
921
        $toolbar .= ' ]';
922
923
        return $toolbar;
924
    }
925
926
    /**
927
     * displayicons()
928
     *
929
     * @param         $time
930
     * @param int     $status
931
     * @param int     $counter
932
     *
933
     * @return string
934
     */
935
    public static function displayIcons($time, $status = \_WFDOWNLOADS_STATUS_WAITING, $counter = 0)
936
    {
937
        $helper = Helper::getInstance();
938
939
        $new     = '';
940
        $pop     = '';
941
        $newdate = (\time() - (86400 * $helper->getConfig('daysnew')));
942
        $popdate = (\time() - (86400 * $helper->getConfig('daysupdated')));
943
944
        if (\_WFDOWNLOADS_DISPLAYICONS_NO != $helper->getConfig('displayicons')) {
945
            if ($newdate < $time) {
946
                if ((int)$status > \_WFDOWNLOADS_STATUS_APPROVED) {
947
                    if (\_WFDOWNLOADS_DISPLAYICONS_ICON == $helper->getConfig('displayicons')) {
948
                        $new = '&nbsp;<img src=' . XOOPS_URL . '/modules/' . WFDOWNLOADS_DIRNAME . "/assets/images/icon/update.gif alt='' align ='absmiddle'>";
949
                    }
950
                    if (\_WFDOWNLOADS_DISPLAYICONS_TEXT == $helper->getConfig('displayicons')) {
951
                        $new = '<i>' . \_WFDOWNLOADS_MD_UPDATED . '</i>';
952
                    }
953
                } else {
954
                    if (\_WFDOWNLOADS_DISPLAYICONS_ICON == $helper->getConfig('displayicons')) {
955
                        $new = '&nbsp;<img src=' . XOOPS_URL . '/modules/' . WFDOWNLOADS_DIRNAME . "/assets/images/icon/newred.gif alt='' align ='absmiddle'>";
956
                    }
957
                    if (\_WFDOWNLOADS_DISPLAYICONS_TEXT == $helper->getConfig('displayicons')) {
958
                        $new = '<i>' . \_WFDOWNLOADS_MD_NEW . '</i>';
959
                    }
960
                }
961
            }
962
            if ($popdate < $time) {
963
                if ($counter >= $helper->getConfig('popular')) {
964
                    if (\_WFDOWNLOADS_DISPLAYICONS_ICON == $helper->getConfig('displayicons')) {
965
                        $pop = '&nbsp;<img src =' . XOOPS_URL . '/modules/' . WFDOWNLOADS_DIRNAME . "/assets/images/icon/pop.gif alt='' align ='absmiddle'>";
966
                    }
967
                    if (\_WFDOWNLOADS_DISPLAYICONS_TEXT == $helper->getConfig('displayicons')) {
968
                        $pop = '<i>' . \_WFDOWNLOADS_MD_POPULAR . '</i>';
969
                    }
970
                }
971
            }
972
        }
973
        $icons = "{$new} {$pop}";
974
975
        return $icons;
976
    }
977
978
    //if (!function_exists('convertorderbyin')) {
979
    // Reusable Link Sorting Functions
980
981
    /**
982
     * convertorderbyin()
983
     *
984
     * @param   $orderby
985
     *
986
     * @return string
987
     */
988
    public static function convertorderbyin($orderby)
989
    {
990
        switch (\trim($orderby)) {
991
            case 'titleA':
992
                $orderby = 'title ASC';
993
                break;
994
            case 'titleD':
995
                $orderby = 'title DESC';
996
                break;
997
            case 'dateA':
998
                $orderby = 'published ASC';
999
                break;
1000
            case 'dateD':
1001
                $orderby = 'published DESC';
1002
                break;
1003
            case 'hitsA':
1004
                $orderby = 'hits ASC';
1005
                break;
1006
            case 'hitsD':
1007
                $orderby = 'hits DESC';
1008
                break;
1009
            case 'ratingA':
1010
                $orderby = 'rating ASC';
1011
                break;
1012
            case 'ratingD':
1013
                $orderby = 'rating DESC';
1014
                break;
1015
            case 'sizeD':
1016
                $orderby = 'size DESC';
1017
                break;
1018
            case 'sizeA':
1019
                $orderby = 'size ASC';
1020
                break;
1021
            default:
1022
                $orderby = 'published DESC';
1023
                break;
1024
        }
1025
1026
        return $orderby;
1027
    }
1028
1029
    //}
1030
1031
    //if (!function_exists('convertOrderByTrans')) {
1032
1033
    /**
1034
     * @param $orderby
1035
     *
1036
     * @return string
1037
     */
1038
    public static function convertOrderByTrans($orderby)
1039
    {
1040
        $orderbyTrans = '';
1041
        if ('title ASC' === $orderby) {
1042
            $orderbyTrans = \_MD_WFDOWNLOADS_TITLEATOZ;
1043
        }
1044
        if ('title DESC' === $orderby) {
1045
            $orderbyTrans = \_MD_WFDOWNLOADS_TITLEZTOA;
1046
        }
1047
        if ('published ASC' === $orderby) {
1048
            $orderbyTrans = \_MD_WFDOWNLOADS_DATEOLD;
1049
        }
1050
        if ('published DESC' === $orderby) {
1051
            $orderbyTrans = \_MD_WFDOWNLOADS_DATENEW;
1052
        }
1053
        if ('hits ASC' === $orderby) {
1054
            $orderbyTrans = \_MD_WFDOWNLOADS_POPULARITYLTOM;
1055
        }
1056
        if ('hits DESC' === $orderby) {
1057
            $orderbyTrans = \_MD_WFDOWNLOADS_POPULARITYMTOL;
1058
        }
1059
        if ('rating ASC' === $orderby) {
1060
            $orderbyTrans = \_MD_WFDOWNLOADS_RATINGLTOH;
1061
        }
1062
        if ('rating DESC' === $orderby) {
1063
            $orderbyTrans = \_MD_WFDOWNLOADS_RATINGHTOL;
1064
        }
1065
        if ('size ASC' === $orderby) {
1066
            $orderbyTrans = \_MD_WFDOWNLOADS_SIZELTOH;
1067
        }
1068
        if ('size DESC' === $orderby) {
1069
            $orderbyTrans = \_MD_WFDOWNLOADS_SIZEHTOL;
1070
        }
1071
1072
        return $orderbyTrans;
1073
    }
1074
1075
    //}
1076
1077
    //if (!function_exists('convertorderbyout')) {
1078
1079
    /**
1080
     * @param $orderby
1081
     *
1082
     * @return string
1083
     */
1084
    public static function convertorderbyout($orderby)
1085
    {
1086
        if ('title ASC' === $orderby) {
1087
            $orderby = 'titleA';
1088
        }
1089
        if ('title DESC' === $orderby) {
1090
            $orderby = 'titleD';
1091
        }
1092
        if ('published ASC' === $orderby) {
1093
            $orderby = 'dateA';
1094
        }
1095
        if ('published DESC' === $orderby) {
1096
            $orderby = 'dateD';
1097
        }
1098
        if ('hits ASC' === $orderby) {
1099
            $orderby = 'hitsA';
1100
        }
1101
        if ('hits DESC' === $orderby) {
1102
            $orderby = 'hitsD';
1103
        }
1104
        if ('rating ASC' === $orderby) {
1105
            $orderby = 'ratingA';
1106
        }
1107
        if ('rating DESC' === $orderby) {
1108
            $orderby = 'ratingD';
1109
        }
1110
        if ('size ASC' === $orderby) {
1111
            $orderby = 'sizeA';
1112
        }
1113
        if ('size DESC' === $orderby) {
1114
            $orderby = 'sizeD';
1115
        }
1116
1117
        return $orderby;
1118
    }
1119
1120
    //}
1121
1122
    /**
1123
     * updaterating()
1124
     *
1125
     * @param   $lid
1126
     */
1127
    public static function updateRating($lid)
1128
    {
1129
        $helper = Helper::getInstance();
1130
1131
        $ratingObjs    = $helper->getHandler('Rating')->getObjects(new \Criteria('lid', (int)$lid));
1132
        $ratings_count = \count($ratingObjs);
1133
        $totalRating   = 0;
1134
        foreach ($ratingObjs as $ratingObj) {
1135
            $totalRating += $ratingObj->getVar('rating');
1136
        }
1137
        $averageRating = $totalRating / $ratings_count;
1138
        $averageRating = \number_format($averageRating, 4);
1139
        $downloadObj   = $helper->getHandler('Download')->get($lid);
1140
        $downloadObj->setVar('rating', $averageRating);
1141
        $downloadObj->setVar('votes', $ratings_count);
1142
        $helper->getHandler('Download')->insert($downloadObj);
1143
    }
1144
1145
    /**
1146
     * categoriesCount()
1147
     *
1148
     * @return int
1149
     */
1150
    public static function categoriesCount()
1151
    {
1152
        $grouppermHandler = \xoops_getHandler('groupperm');
1153
        $helper                   = Helper::getInstance();
1154
        $groups                   = \is_object($GLOBALS['xoopsUser']) ? $GLOBALS['xoopsUser']->getGroups() : [0 => XOOPS_GROUP_ANONYMOUS];
1155
        $allowedDownCategoriesIds = $grouppermHandler->getItemIds('WFDownCatPerm', $groups, $helper->getModule()->mid());
1156
1157
        return \count($allowedDownCategoriesIds);
1158
    }
1159
1160
    /**
1161
     * getTotalDownloads()
1162
     *
1163
     * @param int|array $cids (array of integer)
1164
     *
1165
     * @return bool number of items in items table that are associated with a given table $table id
1166
     */
1167
    public static function getTotalDownloads($cids = 0)
1168
    {
1169
        $helper = Helper::getInstance();
1170
1171
        $criteria = new \CriteriaCompo(new \Criteria('offline', false));
1172
        $criteria->add(new \Criteria('published', 0, '>'));
1173
        $criteria->add(new \Criteria('published', \time(), '<='));
1174
        $expiredCriteria = new \CriteriaCompo(new \Criteria('expired', 0));
1175
        $expiredCriteria->add(new \Criteria('expired', \time(), '>='), 'OR');
1176
        $criteria->add($expiredCriteria);
1177
        if ($cids && \is_array($cids)) {
1178
            $criteria->add(new \Criteria('cid', '(' . \implode(',', $cids) . ')', 'IN'));
1179
        } elseif ($cids > 0) {
1180
            $criteria->add(new \Criteria('cid', (int)$cids));
1181
        } else {
1182
            return false;
1183
        }
1184
        $criteria->setGroupBy('cid');
1185
        $info['published'] = $helper->getHandler('Download')->getMaxPublishdate($criteria);
1186
        $info['count']     = $helper->getHandler('Download')->getCount($criteria);
1187
1188
        return $info;
1189
    }
1190
1191
    /**
1192
     * @return string
1193
     */
1194
    public static function headerImage()
1195
    {
1196
        $helper = Helper::getInstance();
1197
1198
        $image  = '';
1199
        $result = $GLOBALS['xoopsDB']->query('SELECT indeximage, indexheading FROM ' . $GLOBALS['xoopsDB']->prefix('wfdownloads_indexpage') . ' ');
1200
        [$indexImage, $indexHeading] = $GLOBALS['xoopsDB']->fetchrow($result);
1201
        if (!empty($indeximage)) {
1202
            $image = self::displayImage($indexImage, 'index.php', $helper->getConfig('mainimagedir'), $indexHeading);
1203
        }
1204
1205
        return $image;
1206
    }
1207
1208
    /**
1209
     * @param string $image
1210
     * @param string $href
1211
     * @param string $imgSource
1212
     * @param string $altText
1213
     *
1214
     * @return string
1215
     */
1216
    public static function displayImage($image = '', $href = '', $imgSource = '', $altText = '')
1217
    {
1218
        $helper = Helper::getInstance();
1219
1220
        $showImage = '';
1221
1222
        // Check to see if link is given
1223
        if ($href) {
1224
            $showImage = "<a href='{$href}'>";
1225
        }
1226
        // checks to see if the file is valid else displays default blank image
1227
        if (!\is_dir(XOOPS_ROOT_PATH . "/{$imgSource}/{$image}") && \is_dir(XOOPS_ROOT_PATH . "/{$imgSource}/{$image}")) {
1228
            $showImage .= "<img src='" . XOOPS_URL . "/{$imgSource}/{$image}' border='0' alt='{$altText}'>";
1229
        } else {
1230
            if ($GLOBALS['xoopsUser'] && $GLOBALS['xoopsUser']->isAdmin($helper->getModule()->mid())) {
1231
                $showImage .= "<img src='" . XOOPS_URL . '/modules/' . WFDOWNLOADS_DIRNAME . "/assets/images/brokenimg.png' alt='" . \_MD_WFDOWNLOADS_ISADMINNOTICE . "'>";
1232
            } else {
1233
                $showImage .= "<img src='" . XOOPS_URL . '/modules/' . WFDOWNLOADS_DIRNAME . "/assets/images/blank.png' alt='{$altText}'>";
1234
            }
1235
        }
1236
        if ($href) {
1237
            $showImage .= '</a>';
1238
        }
1239
        \clearstatcache();
1240
1241
        return $showImage;
1242
    }
1243
1244
    /**
1245
     * createThumb()
1246
     *
1247
     * @param          $imgName
1248
     * @param          $imgPath
1249
     * @param          $imgSavePath
1250
     * @param int      $width
1251
     * @param int      $height
1252
     * @param int      $quality
1253
     * @param bool|int $update
1254
     * @param int      $aspect
1255
     *
1256
     * @return string
1257
     */
1258
    public static function createThumb($imgName, $imgPath, $imgSavePath, $width = 100, $height = 100, $quality = 100, $update = false, $aspect = 1)
1259
    {
1260
        $helper = Helper::getInstance();
1261
1262
        // Paths
1263
        if (false === $helper->getConfig('usethumbs')) {
1264
            $imageURL = XOOPS_URL . "/{$imgPath}/{$imgName}";
1265
1266
            return $imageURL;
1267
        }
1268
        $imagePath = XOOPS_ROOT_PATH . "/{$imgPath}/{$imgName}";
1269
        $saveFile  = "{$imgPath}/{$imgSavePath}/{$width}x{$height}_{$imgName}";
1270
        $savePath  = XOOPS_ROOT_PATH . '/' . $saveFile;
1271
        // Return the image if no update and image exists
1272
        if (false === $update && \file_exists($savePath)) {
1273
            return XOOPS_URL . '/' . $saveFile;
1274
        }
1275
        // Original image info
1276
        [$origWidth, $origHeight, $type, $attr] = \getimagesize($imagePath, $info);
1277
        switch ($type) {
1278
            case 1:
1279
                # GIF image
1280
                if (\function_exists('imagecreatefromgif')) {
1281
                    $img = @\imagecreatefromgif($imagePath);
1282
                } else {
1283
                    $img = @\imagecreatefrompng($imagePath);
1284
                }
1285
                break;
1286
            case 2:
1287
                # JPEG image
1288
                $img = @\imagecreatefromjpeg($imagePath);
1289
                break;
1290
            case 3:
1291
                # PNG image
1292
                $img = @\imagecreatefrompng($imagePath);
1293
                break;
1294
            default:
1295
                return $imagePath;
1296
                break;
1297
        }
1298
        if (!empty($img)) {
1299
            // Get original image size and scale ratio
1300
            $scale = $origWidth / $origHeight;
1301
            if ($width / $height > $scale) {
1302
                $width = $height * $scale;
1303
            } else {
1304
                $height = $width / $scale;
1305
            }
1306
            // Create a new temporary image
1307
            if (\function_exists('imagecreatetruecolor')) {
1308
                $tempImg = \imagecreatetruecolor($width, $height);
1309
            } else {
1310
                $tempImg = \imagecreate($width, $height);
1311
            }
1312
            // Copy and resize old image into new image
1313
            \imagecopyresampled($tempImg, $img, 0, 0, 0, 0, $width, $height, $origWidth, $origHeight);
1314
            \imagedestroy($img);
1315
            \flush();
1316
            $img = $tempImg;
1317
        }
1318
        switch ($type) {
1319
            case 1:
1320
            default:
1321
                # GIF image
1322
                if (\function_exists('imagegif')) {
1323
                    \imagegif($img, $savePath);
1324
                } else {
1325
                    \imagepng($img, $savePath);
1326
                }
1327
                break;
1328
            case 2:
1329
                # JPEG image
1330
                \imagejpeg($img, $savePath, $quality);
1331
                break;
1332
            case 3:
1333
                # PNG image
1334
                \imagepng($img, $savePath);
1335
                break;
1336
        }
1337
        \imagedestroy($img);
1338
        \flush();
1339
1340
        return XOOPS_URL . '/' . $saveFile;
1341
    }
1342
1343
    /**
1344
     * isNewImage()
1345
     *
1346
     * @param int $published date
1347
     *
1348
     * @return array 'image', 'alttext', 'days'  number of days between $published and now
1349
     **/
1350
    public static function isNewImage($published)
1351
    {
1352
        if ($published <= 0) {
1353
            $indicator['image']   = 'assets/images/icon/download.gif';
1354
            $indicator['alttext'] = \_MD_WFDOWNLOADS_NO_FILES;
1355
            $indicator['days']    = null;
1356
        } else {
1357
            $days              = (int)((\time() - $published) / 86400); // number of days between $published and now
1358
            $indicator['days'] = $days;
1359
            switch ($days) {
1360
                case 0:
1361
                    // today
1362
                    $indicator['image']   = 'assets/images/icon/download1.gif';
1363
                    $indicator['alttext'] = \_MD_WFDOWNLOADS_TODAY;
1364
                    break;
1365
                case 1:
1366
                case 2:
1367
                    // less than 3 days
1368
                    $indicator['image']   = 'assets/images/icon/download2.gif';
1369
                    $indicator['alttext'] = \_MD_WFDOWNLOADS_THREE;
1370
                    break;
1371
                case 3:
1372
                case 4:
1373
                case 5:
1374
                case 6:
1375
                    // less than 7 days
1376
                    $indicator['image']   = 'assets/images/icon/download3.gif';
1377
                    $indicator['alttext'] = \_MD_WFDOWNLOADS_NEWTHIS;
1378
                    break;
1379
                case 7:
1380
                default:
1381
                    // more than a week
1382
                    $indicator['image']   = 'assets/images/icon/download4.gif';
1383
                    $indicator['alttext'] = \_MD_WFDOWNLOADS_NEWLAST;
1384
                    break;
1385
            }
1386
        }
1387
1388
        return $indicator;
1389
    }
1390
1391
    // GetDownloadTime()
1392
    // This function is used to show some different download times
1393
    // BCMATH-Support in PHP needed!
1394
    // (c)02.04.04 by St@neCold, [email protected], http://www.csgui.de
1395
1396
    /**
1397
     * @param int $size
1398
     * @param int $gmodem
1399
     * @param int $gisdn
1400
     * @param int $gdsl
1401
     * @param int $gslan
1402
     * @param int $gflan
1403
     *
1404
     * @return string
1405
     */
1406
    public static function getDownloadTime($size = 0, $gmodem = 1, $gisdn = 1, $gdsl = 1, $gslan = 0, $gflan = 0)
1407
    {
1408
        $aflag  = [];
1409
        $amtime = [];
1410
        $artime = [];
1411
        $ahtime = [];
1412
        $asout  = [];
1413
        $aflag  = [$gmodem, $gisdn, $gdsl, $gslan, $gflan];
1414
        $amtime = [$size / 6300 / 60, $size / 7200 / 60, $size / 86400 / 60, $size / 1125000 / 60, $size / 11250000 / 60];
1415
        $amname = ['Modem(56k)', 'ISDN(64k)', 'DSL(768k)', 'LAN(10M)', 'LAN(100M'];
1416
        for ($i = 0; $i < 5; ++$i) {
1417
            $artime[$i] = ($amtime[$i] % 60);
1418
        }
1419
        for ($i = 0; $i < 5; ++$i) {
1420
            $ahtime[$i] = \sprintf(' %2.0f', $amtime[$i] / 60);
1421
        }
1422
        if ($size <= 0) {
1423
            $dltime = 'N/A';
1424
        } else {
1425
            for ($i = 0; $i < 5; ++$i) {
1426
                if (!$aflag[$i]) {
1427
                    $asout[$i] = '';
1428
                } else {
1429
                    if (($amtime[$i] * 60) < 1) {
1430
                        $asout[$i] = \sprintf(' : %4.2fs', $amtime[$i] * 60);
1431
                    } else {
1432
                        if ($amtime[$i] < 1) {
1433
                            $asout[$i] = \sprintf(' : %2.0fs', \round($amtime[$i] * 60));
1434
                        } else {
1435
                            if (0 == $ahtime[$i]) {
1436
                                $asout[$i] = \sprintf(' : %5.1fmin', $amtime[$i]);
1437
                            } else {
1438
                                $asout[$i] = \sprintf(' : %2.0fh%2.0fmin', $ahtime[$i], $artime[$i]);
1439
                            }
1440
                        }
1441
                    }
1442
                    $asout[$i] = '<b>' . $amname[$i] . '</b>' . $asout[$i];
1443
                    if ($i < 4) {
1444
                        $asout[$i] .= ' | ';
1445
                    }
1446
                }
1447
            }
1448
            $dltime = '';
1449
            for ($i = 0; $i < 5; ++$i) {
1450
                $dltime .= $asout[$i];
1451
            }
1452
        }
1453
1454
        return $dltime;
1455
    }
1456
1457
    /**
1458
     * @param $haystack
1459
     * @param $needle
1460
     *
1461
     * @return string
1462
     */
1463
    public static function strrrchr($haystack, $needle)
1464
    {
1465
        return mb_substr($haystack, 0, mb_strpos($haystack, $needle) + 1);
1466
    }
1467
1468
    /**
1469
     * @param      $fileName
1470
     * @param bool $isAdmin
1471
     *
1472
     * @return array
1473
     */
1474
    public static function allowedMimetypes($fileName, $isAdmin = true)
1475
    {
1476
        $helper = Helper::getInstance();
1477
1478
        $ext      = \ltrim(mb_strrchr($fileName, '.'), '.');
1479
        $criteria = new \CriteriaCompo(new \Criteria('mime_ext', mb_strtolower($ext)));
1480
        if ($isAdmin) {
1481
            $criteria->add(new \Criteria('mime_admin', true));
1482
        } else {
1483
            $criteria->add(new \Criteria('mime_user', true));
1484
        }
1485
        $ret = [];
1486
        if (false !== ($mimetypeObjs = $helper->getHandler('Mimetype')->getObjects($criteria))) {
1487
            $mimetypeObj = $mimetypeObjs[0];
1488
            if (null !== $mimetypeObj) {
1489
                $ret = \explode(' ', $mimetypeObj->getVar('mime_types'));
1490
            }
1491
        }
1492
        return $ret;
1493
    }
1494
1495
    /**
1496
     * @param $size_str
1497
     *
1498
     * @return int
1499
     */
1500
    public static function returnBytes($size_str)
1501
    {
1502
        switch (mb_substr($size_str, -1)) {
1503
            case 'M':
1504
            case 'm':
1505
                return (int)$size_str * 1048576;
1506
            case 'K':
1507
            case 'k':
1508
                return (int)$size_str * 1024;
1509
            case 'G':
1510
            case 'g':
1511
                return (int)$size_str * 1073741824;
1512
            default:
1513
                return $size_str;
1514
        }
1515
    }
1516
1517
    /**
1518
     * uploading()
1519
     *
1520
     * @param string $filename
1521
     * @param string $uploadDirectory
1522
     * @param array  $allowedMimetypes
1523
     * @param string $redirectURL
1524
     * @param int    $num
1525
     * @param bool   $redirect
1526
     * @param bool   $isAdmin
1527
     * @param bool   $onlyImages
1528
     *
1529
     * @return array
1530
     **/
1531
    public static function uploading(
1532
        $filename,
1533
        $uploadDirectory = 'uploads',
1534
        $allowedMimetypes = [],
1535
        $redirectURL = 'index.php',
1536
        $num = 0,
1537
        $redirect = false,
1538
        $isAdmin = true,
1539
        $onlyImages = false
1540
    ) {
1541
        $helper = Helper::getInstance();
1542
        $file   = [];
1543
        if (empty($allowedMimetypes)) {
1544
            $allowedMimetypes = self::allowedMimetypes($_FILES['userfile']['name'], $isAdmin);
1545
        }
1546
        if (empty($allowedMimetypes)) {
1547
            $errors = 'MIME type not allowed';
1548
            \redirect_header($redirectURL, 4, $errors);
1549
        }
1550
        $uploadDirectory .= '/';
1551
        $file_name       = $_FILES['userfile']['name'];
1552
        //Admin can upload files of any size
1553
        if (self::userIsAdmin()) {
1554
            $maxFileSize = self::returnBytes(\ini_get('upload_max_filesize'));
1555
        } else {
1556
            $maxFileSize = $helper->getConfig('maxfilesize');
1557
        }
1558
        $maxImageWidth  = $helper->getConfig('maximgwidth');
1559
        $maxImageHeight = $helper->getConfig('maximgheight');
1560
        // TODO: use Xoops XoopsMediaUploader class
1561
        if ($onlyImages) {
1562
            //            require_once XOOPS_ROOT_PATH . '/modules/wfdownloads/class/img_uploader.php';
1563
            //xoops_load('XoopsMediaUploader');
1564
            $uploader = new Wfdownloads\MediaImgUploader($uploadDirectory, $allowedMimetypes, $maxFileSize, $maxImageWidth, $maxImageHeight);
1565
        } else {
1566
            require_once XOOPS_ROOT_PATH . '/class/uploader.php';
1567
            //xoops_load('XoopsMediaUploader');
1568
            $uploader = new \XoopsMediaUploader($uploadDirectory, $allowedMimetypes, $maxFileSize, $maxImageWidth, $maxImageHeight);
1569
        }
1570
        //    $uploader->noAdminSizeCheck(1);
1571
        if ($uploader->fetchMedia($_POST['xoops_upload_file'][0])) {
1572
            if (!$uploader->upload()) {
1573
                $errors = $uploader->getErrors();
1574
                \unlink($uploadDirectory . $uploader->savedFileName);
1575
                \redirect_header($redirectURL, 4, $errors);
1576
            } else {
1577
                if ($redirect) {
1578
                    \redirect_header($redirectURL, 4, \_AM_WFDOWNLOADS_UPLOADFILE);
1579
                } else {
1580
                    if (\is_file($uploader->savedDestination)) {
1581
                        //                    $file['url'] = XOOPS_URL . '/' . $uploadDirectory . '/';
1582
                        $file['filename'] = mb_strtolower($uploader->savedFileName);
1583
                        $file['filetype'] = $_FILES['userfile']['type'];
1584
                        $file['size']     = \filesize($uploadDirectory . mb_strtolower($uploader->savedFileName));
1585
                    }
1586
1587
                    return $file;
1588
                }
1589
            }
1590
        } else {
1591
            $errors = $uploader->getErrors();
1592
            \unlink($uploadDirectory . $uploader->savedFileName);
1593
            \redirect_header($redirectURL, 4, $errors);
1594
        }
1595
1596
        return null;
1597
    }
1598
1599
    /**
1600
     * @param      $filePath
1601
     * @param bool $isBinary
1602
     * @param bool $retBytes
1603
     *
1604
     * @return bool|int|mixed
1605
     */
1606
    public static function download($filePath, $isBinary = true, $retBytes = true)
1607
    {
1608
        // how many bytes per chunk
1609
        //$chunkSize = 1 * (1024 * 1024);
1610
        $chunkSize    = 8 * (1024 * 1024); //8MB (highest possible fread length)
1611
        $buffer       = '';
1612
        $bytesCounter = 0;
1613
1614
        if ($isBinary) {
1615
            $handler = \fopen($filePath, 'rb');
1616
        } else {
1617
            $handler = \fopen($filePath, 'rb');
1618
        }
1619
        if (false === $handler) {
1620
            return false;
1621
        }
1622
        while (!\feof($handler)) {
1623
            $buffer = \fread($handler, $chunkSize);
1624
            echo $buffer;
1625
            \ob_flush();
1626
            \flush();
1627
            if ($retBytes) {
1628
                $bytesCounter += mb_strlen($buffer);
1629
            }
1630
        }
1631
        $status = \fclose($handler);
1632
        if ($retBytes && $status) {
1633
            return $bytesCounter; // return num. bytes delivered like readfile() does.
1634
        }
1635
1636
        return $status;
1637
    }
1638
1639
    // IN PROGRESS
1640
    // IN PROGRESS
1641
    // IN PROGRESS
1642
1643
    /**
1644
     * @param $filePath
1645
     * @param $fileMimetype
1646
     * @author     Jack Mason
1647
     * @website    volunteer @ http://www.osipage.com, web access application and bookmarking tool.
1648
     * @copyright  Free script, use anywhere as you like, no attribution required
1649
     * @created    2014
1650
     *             The script is capable of downloading really large files in PHP. Files greater than 2GB may fail in 32-bit windows or similar system.
1651
     *             All incorrect headers have been removed and no nonsense code remains in this script. Should work well.
1652
     *             The best and most recommended way to download files with PHP is using xsendfile, learn
1653
     *             more here: https://tn123.org/mod_xsendfile/
1654
     *
1655
     */
1656
    public static function largeDownload($filePath, $fileMimetype)
1657
    {
1658
        /* You may need these ini settings too */
1659
        \set_time_limit(0);
1660
        \ini_set('memory_limit', '512M');
1661
        if (!empty($filePath)) {
1662
            $fileInfo            = \pathinfo($filePath);
1663
            $fileName            = $fileInfo['basename'];
1664
            $fileExtension       = $fileInfo['extension'];
1665
            $default_contentType = 'application/octet-stream';
1666
            // to find and use specific content type, check out this IANA page : http://www.iana.org/assignments/media-types/media-types.xhtml
1667
            $contentType = $default_contentType;
1668
            if ($fileMimetype = !'') {
1669
                $contentType = $fileMimetype;
1670
            }
1671
            if (\is_file($filePath)) {
1672
                $size   = \filesize($filePath);
1673
                $offset = 0;
1674
                $length = $size;
1675
                //HEADERS FOR PARTIAL DOWNLOAD FACILITY BEGINS
1676
                if (Request::hasVar('HTTP_RANGE', 'SERVER')) {
1677
                    \preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
1678
                    $offset  = (int)$matches[1];
1679
                    $length  = (int)$matches[2] - $offset;
1680
                    $fhandle = \fopen($filePath, 'rb');
1681
                    \fseek($fhandle, $offset); // seek to the requested offset, this is 0 if it's not a partial content request
1682
                    $data = \fread($fhandle, $length);
1683
                    \fclose($fhandle);
1684
                    \header('HTTP/1.1 206 Partial Content');
1685
                    \header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $size);
1686
                }//HEADERS FOR PARTIAL DOWNLOAD FACILITY BEGINS
1687
                //USUAL HEADERS FOR DOWNLOAD
1688
                \header('Content-Disposition: attachment;filename=' . $fileName);
1689
                \header('Content-Type: ' . $contentType);
1690
                \header('Accept-Ranges: bytes');
1691
                \header('Pragma: public');
1692
                \header('Expires: -1');
1693
                \header('Cache-Control: no-cache');
1694
                \header('Cache-Control: public, must-revalidate, post-check=0, pre-check=0');
1695
                \header('Content-Length: ' . \filesize($filePath));
1696
                $chunksize = 8 * (1024 * 1024); //8MB (highest possible fread length)
1697
                if ($size > $chunksize) {
1698
                    $handle = \fopen($_FILES['file']['tmp_name'], 'rb');
1699
                    $buffer = '';
1700
                    while (!\feof($handle) && (\CONNECTION_NORMAL === \connection_status())) {
1701
                        $buffer = \fread($handle, $chunksize);
1702
                        print $buffer;
1703
                        \ob_flush();
1704
                        \flush();
1705
                    }
1706
                    if (\CONNECTION_NORMAL !== \connection_status()) {
1707
                        //TODO traslation
1708
                        echo 'Connection aborted';
1709
                    }
1710
                    \fclose($handle);
1711
                } else {
1712
                    \ob_clean();
1713
                    \flush();
1714
                    \readfile($filePath);
1715
                }
1716
            } else {
1717
                //TODO traslation
1718
                echo 'File does not exist!';
1719
            }
1720
        } else {
1721
            //TODO traslation
1722
            echo 'There is no file to download!';
1723
        }
1724
    }
1725
1726
    /**
1727
     * @param $selectedForumId
1728
     *
1729
     * @return int
1730
     */
1731
    public static function getForum($selectedForumId)
1732
    {
1733
        $selectedForumId = (int)$selectedForumId;
1734
        echo "<select name='forumid'>";
1735
        echo "<option value='0'>----------------------</option>";
1736
        $result = $GLOBALS['xoopsDB']->query('SELECT forum_name, forum_id FROM ' . $GLOBALS['xoopsDB']->prefix('bb_forums') . ' ORDER BY forum_id');
1737
        while (list($forumName, $forumId) = $GLOBALS['xoopsDB']->fetchRow($result)) {
1738
            $optionSelected = '';
1739
            if ($forumId == $selectedForumId) {
1740
                $optionSelected = "selected='selected'";
1741
            }
1742
            echo "<option value='{$forumId}' {$optionSelected}>{$forumName}</option>";
1743
        }
1744
        echo '</select></div>';
1745
1746
        return $selectedForumId;
1747
    }
1748
1749
    /**
1750
     * @param $serverURL
1751
     *
1752
     * @return bool
1753
     */
1754
    public static function mirrorOnline($serverURL)
1755
    {
1756
        $fp = @\fsockopen($serverURL, 80, $errno, $errstr, 5);
1757
        if (!$fp) {
1758
            $isOnline = false;
1759
        } else {
1760
            $isOnline = true;
1761
            \fclose($fp);
1762
        }
1763
1764
        return $isOnline;
1765
    }
1766
1767
    /**
1768
     * truncateHtml can truncate a string up to a number of characters while preserving whole words and HTML tags
1769
     * www.gsdesign.ro/blog/cut-html-string-without-breaking-the-tags
1770
     * www.cakephp.org
1771
     *
1772
     * @param string $text         String to truncate.
1773
     * @param int    $length       Length of returned string, including ellipsis.
1774
     * @param string $ending       Ending to be appended to the trimmed string.
1775
     * @param bool   $exact        If false, $text will not be cut mid-word
1776
     * @param bool   $considerHtml If true, HTML tags would be handled correctly
1777
     *
1778
     * @return string Trimmed string.
1779
     */
1780
    public static function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true)
1781
    {
1782
        if ($considerHtml) {
1783
            // if the plain text is shorter than the maximum length, return the whole text
1784
            if (mb_strlen(\preg_replace('/<.*?' . '>/', '', $text)) <= $length) {
1785
                return $text;
1786
            }
1787
            // splits all html-tags to scanable lines
1788
            \preg_match_all('/(<.+?' . '>)?([^<>]*)/s', $text, $lines, \PREG_SET_ORDER);
1789
            $total_length = mb_strlen($ending);
1790
            $open_tags    = [];
1791
            $truncate     = '';
1792
            foreach ($lines as $line_matchings) {
1793
                // if there is any html-tag in this line, handle it and add it (uncounted) to the output
1794
                if (!empty($line_matchings[1])) {
1795
                    // if it's an "empty element" with or without xhtml-conform closing slash
1796
                    if (\preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
1797
                        // do nothing
1798
                        // if tag is a closing tag
1799
                    } elseif (\preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
1800
                        // delete tag from $open_tags list
1801
                        $pos = \array_search($tag_matchings[1], $open_tags, true);
1802
                        if (false !== $pos) {
1803
                            unset($open_tags[$pos]);
1804
                        }
1805
                        // if tag is an opening tag
1806
                    } elseif (\preg_match('/^<\s*([^\s>!]+).*?' . '>$/s', $line_matchings[1], $tag_matchings)) {
1807
                        // add tag to the beginning of $open_tags list
1808
                        \array_unshift($open_tags, mb_strtolower($tag_matchings[1]));
1809
                    }
1810
                    // add html-tag to $truncate'd text
1811
                    $truncate .= $line_matchings[1];
1812
                }
1813
                // calculate the length of the plain text part of the line; handle entities as one character
1814
                $content_length = mb_strlen(\preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
1815
                if ($total_length + $content_length > $length) {
1816
                    // the number of characters which are left
1817
                    $left            = $length - $total_length;
1818
                    $entities_length = 0;
1819
                    // search for html entities
1820
                    if (\preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, \PREG_OFFSET_CAPTURE)) {
1821
                        // calculate the real length of all entities in the legal range
1822
                        foreach ($entities[0] as $entity) {
1823
                            if ($left >= $entity[1] + 1 - $entities_length) {
1824
                                $left--;
1825
                                $entities_length += mb_strlen($entity[0]);
1826
                            } else {
1827
                                // no more characters left
1828
                                break;
1829
                            }
1830
                        }
1831
                    }
1832
                    $truncate .= mb_substr($line_matchings[2], 0, $left + $entities_length);
1833
                    // maximum lenght is reached, so get off the loop
1834
                    break;
1835
                }
1836
                $truncate     .= $line_matchings[2];
1837
                $total_length += $content_length;
1838
1839
                // if the maximum length is reached, get off the loop
1840
                if ($total_length >= $length) {
1841
                    break;
1842
                }
1843
            }
1844
        } else {
1845
            if (mb_strlen($text) <= $length) {
1846
                return $text;
1847
            }
1848
            $truncate = mb_substr($text, 0, $length - mb_strlen($ending));
1849
        }
1850
        // if the words shouldn't be cut in the middle...
1851
        if (!$exact) {
1852
            // ...search the last occurance of a space...
1853
            $spacepos = mb_strrpos($truncate, ' ');
1854
            if (null !== $spacepos) {
1855
                // ...and cut the text in this position
1856
                $truncate = mb_substr($truncate, 0, $spacepos);
1857
            }
1858
        }
1859
        // add the defined ending to the text
1860
        $truncate .= $ending;
1861
        if ($considerHtml) {
1862
            // close all unclosed html-tags
1863
            foreach ($open_tags as $tag) {
1864
                $truncate .= '</' . $tag . '>';
1865
            }
1866
        }
1867
1868
        return $truncate;
1869
    }
1870
1871
    // Swish-e support EXPERIMENTAL
1872
1873
    /**
1874
     * php4swish-e 1.1, a web search interface for the swish-e search engine.
1875
     * swish-e is a popular open-source search engine that runs on many platforms.
1876
     * More information on swish-e is available at swish-e.org.
1877
     * This code has been thoroughly tested and is ready for production
1878
     * on any UNIX or Linux server that has the swish-e search engine installed.
1879
     * You are free to modify this code as you see fit.
1880
     * You must specify the path of swish-e ($swish) and
1881
     * the location of the search index file ($swisheIndexFilePath).
1882
     * You will also need to change the default index file served if it is not index.php.
1883
     * If you want the meta description information to display completely,
1884
     * be sure the <meta description... information is on *one* line for each web page.
1885
     * If you wish to allow external search forms to call this script, be sure to set the
1886
     * form's action attribute to whatever you name this file.
1887
     * Suggestions for enhancements are welcome.
1888
     */
1889
    public static function checkSwishe()
1890
    {
1891
        $helper = Helper::getInstance();
1892
1893
        // Get the location of the document repository (the index files are located in the root)
1894
        $swisheDocPath = $helper->getConfig('uploaddir');
1895
        // Get the location of the SWISH-E executable
1896
        $swisheExePath = $helper->getConfig('swishe_exe_path');
1897
        // check if _binfilter.sh exists
1898
        if (!\is_file("{$swisheDocPath}/_binfilter.sh")) {
1899
            return false;
1900
        }
1901
        // check if swish-e.conf exists
1902
        if (!\is_file("{$swisheDocPath}/swish-e.conf")) {
1903
            return false;
1904
        }
1905
        // check if swish-e.exe exists
1906
        if (!\is_file("{$swisheExePath}/swish-e.exe")) {
1907
            return false;
1908
        }
1909
1910
        return true;
1911
    }
1912
1913
    public static function swishe_config()
1914
    {
1915
        $helper = Helper::getInstance();
1916
1917
        // Get the location of the document repository (the index files are located in the root)
1918
        $swisheDocPath = $helper->getConfig('uploaddir');
1919
        // Create _binfilter.sh
1920
        $file = "{$swisheDocPath}/_binfilter.sh";
1921
        $fp   = \fopen($file, 'wb') || exit("<BR><BR>Unable to open $file");
1922
        \fwrite($fp, "strings \"\$1\" - 2>/dev/null\n");
1923
        \fclose($fp);
1924
        \chmod($file, 0755);
1925
        unset($fp);
1926
        // Create swish-e.conf
1927
        $file = "{$swisheDocPath}/swish-e.conf";
1928
        $fp   = \fopen($file, 'wb') || exit("<BR><BR>Unable to open {$file}");
1929
        // IndexDir [directories or files|URL|external program]
1930
        // IndexDir defines the source of the documents for Swish-e. Swish-e currently supports three file access methods: File system, HTTP (also called spidering), and prog for reading files from an external program.
1931
        \fwrite($fp, "IndexDir {$swisheDocPath}/\n");
1932
        // IndexFile *path*
1933
        // Index file specifies the location of the generated index file. If not specified, Swish-e will create the file index.swish-e in the current directory.
1934
        \fwrite($fp, "IndexFile {$swisheDocPath}/index.swish-e\n");
1935
        // TruncateDocSize *number of characters*
1936
        // TruncateDocSize limits the size of a document while indexing documents and/or using filters. This config directive truncates the numbers of read bytes of a document to the specified size. This means: if a document is larger, read only the specified numbers of bytes of the document.
1937
        //fwrite($fp, "TruncateDocSize 100000\n");
1938
        // IndexReport [0|1|2|3]
1939
        // This is how detailed you want reporting while indexing. You can specify numbers 0 to 3. 0 is totally silent, 3 is the most verbose. The default is 1.
1940
        \fwrite($fp, "IndexReport 1\n");
1941
        // IndexContents [TXT|HTML|XML|TXT2|HTML2|XML2|TXT*|HTML*|XML*] *file extensions*
1942
        // The IndexContents directive assigns one of Swish-e's document parsers to a document, based on the its extension. Swish-e currently knows how to parse TXT, HTML, and XML documents.
1943
        \fwrite($fp, "IndexContents TXT* .dat\n");
1944
        // FileFilter *suffix* "filter-prog" ["filter-options"]
1945
        // This maps file suffix (extension) to a filter program. If filter-prog starts with a directory delimiter (absolute path), Swish-e doesn't use the FilterDir settings, but uses the given filter-prog path directly.
1946
        //fwrite($fp, "FileFilter .dat \"{$swisheDocPath}/_binfilter.sh\" \"'%p'\"\n");
1947
        // IndexOnly *list of file suffixes*
1948
        // This directive specifies the allowable file suffixes (extensions) while indexing. The default is to index all files specified in IndexDir.
1949
        \fwrite($fp, "IndexOnly .dat\n");
1950
        // MinWordLimit *integer*
1951
        // Set the minimum length of an word. Shorter words will not be indexed. The default is 1 (as defined in src/config.h).
1952
        \fwrite($fp, "MinWordLimit 3\n");
1953
        \fclose($fp);
1954
        \chmod($file, 0755);
1955
    }
1956
1957
    /**
1958
     * @param $swisheQueryWords
1959
     *
1960
     * @return array|bool
1961
     */
1962
    public static function searchSwishe($swisheQueryWords)
1963
    {
1964
        /**
1965
         * @param $str
1966
         * @param $num_chars
1967
         * @return string
1968
         */
1969
        function strright($str, $num_chars)
1970
        {
1971
            $str_length = mb_strlen($str);
1972
1973
            return mb_substr($str, $str_length - $num_chars, $str_length);
1974
        }
1975
1976
        $helper = Helper::getInstance();
1977
1978
        $ret = false;
1979
        // IN PROGRESS
1980
        // IN PROGRESS
1981
        // IN PROGRESS
1982
        $swisheQueryWords = \stripslashes($swisheQueryWords);
1983
        if (mb_strlen($swisheQueryWords) > 2) {
1984
            //print "<BR>SEARCH!";
1985
            // Get the first word in $swisheQueryWords and use it for the $summary_query.
1986
            // IN PROGRESS
1987
            // IN PROGRESS
1988
            // IN PROGRESS
1989
            $summary_query   = \str_replace('"', ' ', $swisheQueryWords);
1990
            $summary_query   = \trim($summary_query);
1991
            $summary_query_e = \explode(' ', $summary_query);
1992
            $summary_query   = \trim($summary_query_e[0]);
1993
            $summary_query   = \rtrim($summary_query, '*');
1994
1995
            //print "<BR>SQ:  ".$summary_query;
1996
1997
            // Get the location of the document repository (the index files are located in the root)
1998
            $swisheDocPath        = $helper->getConfig('uploaddir');
1999
            $swisheDocPath_strlen = mb_strlen($helper->getConfig('swishe_doc_path'));
2000
2001
            // Get the location of the SWISH-E executable
2002
            $swisheExePath = $helper->getConfig('swishe_exe_path');
2003
2004
            // Get search query
2005
            $swisheQueryWords    = \escapeshellcmd($swisheQueryWords); // escape potentially malicious shell commands
2006
            $swisheQueryWords    = \stripslashes($swisheQueryWords); // remove backslashes from search query
2007
            $swisheQueryWords    = \preg_replace('#("|\')#', '', $swisheQueryWords); // remove quotes from search query
2008
            $swisheCommand       = "{$swisheExePath}/swish-e"; // path of swish-e command
2009
            $swisheIndexFilePath = "{$swisheDocPath}/index.swish-e"; // path of swish-e index file
2010
            $swisheSearchParams  = ''; // Additional search parameters
2011
            $swisheSearchParams  .= '-H1'; // -H1 : print standard result header (default).
2012
            if (0 != $helper->getConfig('swishe_search_limit')) {
2013
                $swisheSearchParams .= " -m{$helper->getConfig('swishe_search_limit')}"; // -m *number* (max results)
2014
            }
2015
2016
            // Opens a pipe to swish-e
2017
            $swishePipeHandler = \popen("{$swisheCommand} -w {$swisheQueryWords} -f {$swisheIndexFilePath} {$swisheSearchParams}", 'r');
2018
            if (!$swishePipeHandler) {
2019
                exit('The search request generated an error...Please try again.');
2020
            }
2021
            \trigger_error("{$swisheCommand} -w {$swisheQueryWords} -f {$swisheIndexFilePath} {$swisheSearchParams}");
2022
2023
            $line_cnt = 1;
2024
            // loop through each line of the pipe result (i.e. swish-e output) to find hit number
2025
            while (false !== ($nline = @\fgets($swishePipeHandler, 1024))) {
2026
                if (4 == $line_cnt) {
2027
                    $num_line = $nline;
2028
                    break; // grab the 4th line, which contains the number of hits returned
2029
                }
2030
                ++$line_cnt;
2031
            }
2032
2033
            // strip out all but the number of hits
2034
            //$num_results = preg_replace('/# Number of hits: /', '', $num_line);
2035
2036
            //$table_header_flag = false;
2037
            //$disp_nff_flag = true;
2038
2039
            $ret = [];
2040
            while (false !== ($line = @\fgets($swishePipeHandler, 4096))) {
2041
                // loop through each line of the pipe result (i.e. swish-e output)
2042
                if (\preg_match("/^(\d+)\s+(\S+)\s+\"(.*)\"\s+(\d+)/", $line)) {
2043
                    // Skip commented-out lines and the last line
2044
                    $line    = \explode('"', $line); // split the string into an array by quotation marks
2045
                    $line[1] = \preg_replace('/[[:blank:]]/', '%%', $line[1]); // replace every space with %% for the phrase in quotation marks
2046
                    $line    = \implode('"', $line); // collapse the array into a string
2047
                    $line    = \preg_replace('/[[:blank:]]/', "\t", $line); // replace every space with a tab
2048
2049
                    [$relevance, $result_url, $result_title, $file_size] = \explode("\t", $line); // split the line into an array by tabs; assign variable names to each column
2050
                    $relevance          /= 10; // format relevance as a percentage for search results
2051
                    $full_path_and_file = $result_url;
2052
                    $result_url         = \trim(mb_substr($result_url, $swisheDocPath_strlen - 1, mb_strlen($result_url)));
2053
                    $file_path          = strright($result_url, mb_strlen($result_url) - 2);
2054
                    //                $file_path2 =       substr($result_url, (strlen($result_url) - (strlen($result_url) - 2)),strlen($result_url));
2055
                    $ret[] = [
2056
                        'relevance'    => $relevance,
2057
                        'result_url'   => $result_url,
2058
                        'result_title' => $result_title,
2059
                        'file_size'    => $file_size,
2060
                        'file_path'    => $file_path,
2061
                    ];
2062
                }
2063
            }
2064
            // close the shell pipe
2065
            \pclose($swishePipeHandler);
2066
        }
2067
2068
        return $ret;
2069
    }
2070
2071
    // Swish-e support EXPERIMENTAL
2072
2073
    // ===========================================================
2074
2075
2076
    /**
2077
     * @param $document
2078
     *
2079
     * @return mixed
2080
     */
2081
    public static function convertHtml2text($document)
2082
    {
2083
        $search = [
2084
            "'<script[^>]*?>.*?</script>'si", // Strip out javascript
2085
            "'<img.*?>'si", // Strip out img tags
2086
            "'<[\/\!]*?[^<>]*?>'si", // Strip out HTML tags
2087
            "'([\r\n])[\s]+'", // Strip out white space
2088
            "'&(quot|#34);'i", // Replace HTML entities
2089
            "'&(amp|#38);'i",
2090
            "'&(lt|#60);'i",
2091
            "'&(gt|#62);'i",
2092
            "'&(nbsp|#160);'i",
2093
            "'&(iexcl|#161);'i",
2094
            "'&(cent|#162);'i",
2095
            "'&(pound|#163);'i",
2096
            "'&(copy|#169);'i",
2097
        ]; // evaluate as php
2098
2099
        $replace = [
2100
            '',
2101
            '',
2102
            '',
2103
            '\\1',
2104
            '"',
2105
            '&',
2106
            '<',
2107
            '>',
2108
            ' ',
2109
            \chr(161),
2110
            \chr(162),
2111
            \chr(163),
2112
            \chr(169),
2113
        ];
2114
2115
        $text = \preg_replace($search, $replace, $document);
2116
2117
        \preg_replace_callback(
2118
            '/&#(\d+);/',
2119
            static function ($matches) {
2120
                return \chr($matches[1]);
2121
            },
2122
            $document
2123
        );
2124
2125
        return $text;
2126
    }
2127
2128
    /**
2129
     * @param array|string $tableName
2130
     * @param int          $id_field
2131
     * @param int          $id
2132
     *
2133
     * @return mixed
2134
     */
2135
    public static function cloneRecord($tableName, $id_field, $id)
2136
    {
2137
        $new_id = false;
2138
        $table  = $GLOBALS['xoopsDB']->prefix($tableName);
2139
        // copy content of the record you wish to clone
2140
        $tempTable = $GLOBALS['xoopsDB']->fetchArray($GLOBALS['xoopsDB']->query("SELECT * FROM $table WHERE $id_field='$id' "), MYSQLI_ASSOC) or exit('Could not select record');
2141
        // set the auto-incremented id's value to blank.
2142
        unset($tempTable[$id_field]);
2143
        // insert cloned copy of the original  record
2144
        $result = $GLOBALS['xoopsDB']->queryF("INSERT INTO $table (" . implode(', ', array_keys($tempTable)) . ") VALUES ('" . implode("', '", array_values($tempTable)) . "')") or exit ($GLOBALS['xoopsDB']->error());
2145
2146
        if ($result) {
2147
            // Return the new id
2148
            $new_id = $GLOBALS['xoopsDB']->getInsertId();
2149
        }
2150
        return $new_id;
2151
    }
2152
}
2153