]> 91.132.146.200 Git - insipid.git/commitdiff
php 8 updates and cleanup
authorBanana <mail@bananas-playground.net>
Mon, 26 Dec 2022 23:40:34 +0000 (00:40 +0100)
committerBanana <mail@bananas-playground.net>
Mon, 26 Dec 2022 23:40:34 +0000 (00:40 +0100)
16 files changed:
ChangeLog
README
TODO
documentation/update.txt
webroot/config.default.php
webroot/index.php
webroot/lib/category.class.php
webroot/lib/import-export.class.php
webroot/lib/link.class.php
webroot/lib/management.class.php
webroot/lib/shellcommand.class.php
webroot/lib/simple-imap.class.php
webroot/lib/snapshot.class.php
webroot/view/editcategories.inc.php
webroot/view/edittags.inc.php
webroot/view/overview.inc.php

index 4863726968940630cc7b7bf9e4463843e3db2cd5..4010a89a89547014b5543d900d086bb681476b29 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,8 @@
 version 2.x - Deathwind Chapel ()
+
+       + Moved debug setting into config
+       + Updated shellcommand class and added the link to the github repo
+
 version 2.8 - Wastelands (2022-12-10)
 
        + Fixed documentation
diff --git a/README b/README
index c01abe6abae5d97dc762ce6a7cbdba9510461fc9..bcb265663489af05710c41d4363050dc3b8e57eb 100644 (file)
--- a/README
+++ b/README
@@ -9,4 +9,4 @@ https://bulma.io/
 https://github.com/PHPMailer/PHPMailer
 https://ionicons.com/
 https://github.com/ifsnop/mysqldump-php
-shellcommand by Michael Härtl <haertl.mike@gmail.com>
+https://github.com/mikehaertl/php-shellcommand
diff --git a/TODO b/TODO
index e242914cece491e60fd825f73289b3d6339268c0..1d18e704787e334deffb03175ae904d3ddc84e3a 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,4 +1,7 @@
 TODO / Feature list
++ mysql dump to be replaced with https://github.com/druidfi/mysqldump-php
+++ update readme too
++ update google pagespeed endpoint
 + view table really still needed?
 + stats cleanup. Management functions should be standalone
 + theme support
index fe7d00ef828ddf810e757bc7b60583102f7bb9e2..d2d3820cb0697af6fc1b2dbe15d5f15158bb8407 100644 (file)
@@ -1,6 +1,10 @@
 If you are updating from a previous version make sure every update info from
 the version your are updating from is done.
 
+## version 2.9
++ Added debug setting into config. See config.default.php for DEBUG constant.
+  Add it to your local config.php
+
 ## version 2.8 Wastelands
 + Nothing.
 
index 1c0cb0039dfc449263308f1078764457d0db8d05..2dcd55f1ae6ffc0025a929733ad974ab4cd8a09a 100644 (file)
@@ -79,3 +79,6 @@ const EMAIL_REPLY_BACK_SUBJECT = 'Insipid email import response';
 # Use wkhtmltopdf to create a whole page screenshot of a given link
 const WKHTMLTOPDF_USE = false;
 const WKHTMLTOPDF_COMMAND = '/absolute/path/to/wkhtmltoimage';
+
+# debug
+const DEBUG = true;
index 3ec1842ac32e7c995b36f8934af9a8bd553efabf..40ead7cb144ff017a82821d0d166b5d61bee898e 100644 (file)
@@ -32,7 +32,7 @@ ini_set('error_reporting',-1); // E_ALL & E_STRICT
 # time settings
 date_default_timezone_set('Europe/Berlin');
 
-define('DEBUG',false);
+require('config.php');
 
 ## check request
 $_urlToParse = filter_var($_SERVER['QUERY_STRING'],FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
@@ -53,7 +53,6 @@ else {
     ini_set('display_errors',false);
 }
 
-require('config.php');
 require('lib/summoner.class.php');
 require('lib/management.class.php');
 require('lib/tag.class.php');
index 6e5f2da1d578df92767dbfb872f13e2634c23476..906b9c46ae8a6b859efed59cfcb4e1fe75df23ed 100644 (file)
@@ -3,7 +3,7 @@
  * Insipid
  * Personal web-bookmark-system
  *
- * Copyright 2016-2021 Johannes Keßler
+ * Copyright 2016-2022 Johannes Keßler
  *
  * Development starting from 2011: Johannes Keßler
  * https://www.bananas-playground.net/projekt/insipid/
@@ -31,21 +31,24 @@ class Category {
         * the database object
         * @var mysqli
         */
-       private $DB;
+       private mysqli $DB;
 
        /**
         * the current loaded category by DB id
-        * @var int
+        * @var string
         */
-       private $_id;
+       private string $_id;
 
        /**
         * current loaded tag data
         * @var array
         */
-       private $_data;
+       private array $_data;
 
-       public function __construct($databaseConnectionObject) {
+       /**
+        * @param mysqli $databaseConnectionObject
+        */
+       public function __construct(mysqli $databaseConnectionObject) {
                $this->DB = $databaseConnectionObject;
        }
 
@@ -82,7 +85,7 @@ class Category {
                     }
                 }
                 else {
-                    $ret=3;
+                    $ret = 3;
                 }
             }
         }
@@ -92,10 +95,10 @@ class Category {
        /**
         * by given DB table id load all the info we need
         *
-        * @param int $id
-        * @return integer
+        * @param string $id
+        * @return string
         */
-       public function initbyid(int $id): int {
+       public function initbyid(string $id): string {
                $this->_id = 0;
 
                if(!empty($id)) {
@@ -116,10 +119,10 @@ class Category {
        /**
         * return all or data for given key on the current loaded category
         *
-        * @param bool $key
-        * @return string
+        * @param string $key
+        * @return string|array
         */
-       public function getData(bool $key=false): string {
+       public function getData(string $key=''): string|array {
                $ret = $this->_data;
 
                if(!empty($key) && isset($this->_data[$key])) {
@@ -132,10 +135,10 @@ class Category {
        /**
         * set the relation to the given link to the loaded category
         *
-        * @param integer $linkid
+        * @param string $linkid
         * @return void
         */
-       public function setRelation(int $linkid) {
+       public function setRelation(string $linkid): void {
                if(!empty($linkid) && !empty($this->_id)) {
                        $queryStr = "INSERT IGNORE INTO `".DB_PREFIX."_categoryrelation`
                                                        SET `linkid` = '".$this->DB->real_escape_string($linkid)."',
@@ -188,6 +191,7 @@ class Category {
                                $this->DB->query($queryStr);
 
                                $this->DB->commit();
+                               $ret = true;
                        } catch (Exception $e) {
                                if(DEBUG) {
                                        var_dump($e->getMessage());
@@ -207,7 +211,7 @@ class Category {
         * @param string $newValue
         * @return void
         */
-    public function rename(string $newValue) {
+    public function rename(string $newValue): void {
         if(!empty($newValue)) {
             $queryStr = "UPDATE `".DB_PREFIX."_category`
                            SET `name` = '".$this->DB->real_escape_string($newValue)."'
index 819771f40d2027dd6beb2f9cedfd1892b73b3c60..642c07701ad86382697eda9865e37c0edef2b7d6 100644 (file)
@@ -3,7 +3,7 @@
  * Insipid
  * Personal web-bookmark-system
  *
- * Copyright 2016-2021 Johannes Keßler
+ * Copyright 2016-2022 Johannes Keßler
  *
  * Development starting from 2011: Johannes Keßler
  * https://www.bananas-playground.net/projekt/insipid/
@@ -35,15 +35,17 @@ class ImportExport {
        /**
         * @var XMLWriter The current memory xmlwriter
         */
-       private $_currentXW;
-
-       private $_xmlImportXSD = 'lib/xmlimport.xsd';
+       private XMLWriter $_currentXW;
 
+       /**
+        * @var string
+        */
+       private string $_xmlImportXSD = 'lib/xmlimport.xsd';
 
        /**
-        * @var
+        * @var string
         */
-       private $_uploadedData;
+       private string $_uploadedData;
 
        public function __construct() {
        }
@@ -155,7 +157,7 @@ class ImportExport {
         * @throws Exception
         * @return void
         */
-       public function loadImportFile(array $file) {
+       public function loadImportFile(array $file): void {
 
                if(!isset($file['name'])
                        || !isset($file['type'])
@@ -183,7 +185,7 @@ class ImportExport {
 
                if(!empty($this->_uploadedData)) {
                        $_valid = $this->_validateXMLImport();
-                       if($_valid !== true) {
+                       if(!empty($_valid)) {
                                $this->_uploadedData = '';
                                throw new Exception('Invalid xml format: '.$_valid);
                        }
@@ -251,7 +253,7 @@ class ImportExport {
         * @param String $value
         * @return void
         */
-       private function _elementFromKeyValue(string $name, string $key, string $value) {
+       private function _elementFromKeyValue(string $name, string $key, string $value): void {
 
                if(!empty($key) && !empty($value) && !empty($name)) {
                        xmlwriter_start_element($this->_currentXW, $name);
@@ -272,10 +274,10 @@ class ImportExport {
         * validate an import of a export xml with the
         * saved xsd file _xmlImportXSD
         *
-        * @return bool|string
+        * @return string
         */
-       private function _validateXMLImport() {
-               $ret = false;
+       private function _validateXMLImport(): string {
+               $ret = '';
                $xmlReader = new XMLReader();
                $xmlReader->XML($this->_uploadedData);
                if(!empty($xmlReader)) {
@@ -286,7 +288,6 @@ class ImportExport {
                                        $ret = $this->_xmlErrors();
                                        break;
                                } else {
-                                       $ret = true;
                                        break;
                                }
                        }
index 38347cee605f252be759a2ba60d18d64f42f9953..0354bd6a64d4e8abd1e6ca2298d3eb75dbeaa366 100644 (file)
@@ -3,7 +3,7 @@
  * Insipid
  * Personal web-bookmark-system
  *
- * Copyright 2016-2021 Johannes Keßler
+ * Copyright 2016-2022 Johannes Keßler
  *
  * Development starting from 2011: Johannes Keßler
  * https://www.bananas-playground.net/projekt/insipid/
@@ -36,20 +36,19 @@ class Link {
         *
         * @var mysqli
         */
-       private $DB;
+       private mysqli $DB;
 
        /**
         * the current loaded link data
         *
         * @var array
         */
-       private $_data;
+       private array $_data;
 
        /**
         * Link constructor.
         *
         * @param mysqli $databaseConnectionObject
-        * @return void
         */
        public function __construct(mysqli $databaseConnectionObject) {
                $this->DB = $databaseConnectionObject;
@@ -143,10 +142,10 @@ class Link {
        /**
         * return all or data for given key on the current loaded link
         *
-        * @param bool $key
-        * @return array|mixed
+        * @param string $key
+        * @return string|array
         */
-       public function getData($key = false): array {
+       public function getData(string $key = ''): string|array {
                $ret = $this->_data;
 
                if (!empty($key) && isset($this->_data[$key])) {
@@ -161,7 +160,7 @@ class Link {
         *
         * @return void
         */
-       public function reload() {
+       public function reload(): void {
                $this->load($this->_data['hash']);
        }
 
@@ -170,10 +169,10 @@ class Link {
         *
         * @param array $data
         * @param bool $returnId
-        * @return int
+        * @return string
         */
-       public function create(array $data, $returnId = false): int {
-               $ret = 0;
+       public function create(array $data, bool $returnId = false): string {
+               $ret = '';
 
                if (!isset($data['link']) || empty($data['link'])) return $ret;
                if (!isset($data['hash']) || empty($data['hash'])) return $ret;
@@ -217,7 +216,6 @@ class Link {
         * @return boolean
         */
        public function update(array $data): bool {
-
                $ret = false;
 
                if (isset($data['title']) && !empty($data['title']) && !empty($this->_data)) {
@@ -259,8 +257,8 @@ class Link {
                                $catObj = new Category($this->DB);
                                $tagObj = new Tag($this->DB);
                                // clean the relations first
-                               $this->_removeTagRelation(false);
-                               $this->_removeCategoryRelation(false);
+                               $this->_removeTagRelation();
+                               $this->_removeCategoryRelation();
 
                                if (!empty($catArr)) {
                                        foreach ($catArr as $c) {
@@ -329,7 +327,6 @@ class Link {
                                        }
                                }
 
-
                                $ret = true;
                        } else {
                                $this->DB->rollback();
@@ -347,9 +344,9 @@ class Link {
         *
         * @return void
         */
-       public function deleteRelations() {
-               $this->_removeTagRelation(false);
-               $this->_removeCategoryRelation(false);
+       public function deleteRelations(): void {
+               $this->_removeTagRelation();
+               $this->_removeCategoryRelation();
                $this->_deleteImage();
                $this->_deleteSnapshot();
                $this->_deletePageScreenshot();
@@ -361,7 +358,7 @@ class Link {
         *
         * @return void
         */
-       private function _tags() {
+       private function _tags(): void {
                $ret = array();
 
                if (!empty($this->_data['hash'])) {
@@ -389,7 +386,7 @@ class Link {
         *
         * @return void
         */
-       private function _categories() {
+       private function _categories(): void {
                $ret = array();
 
                if (!empty($this->_data['hash'])) {
@@ -413,21 +410,22 @@ class Link {
        /**
         * remove all or given tag relation to the current loaded link
         *
-        * @param boolean|integer $tagid
+        * @param string $tagid
         * @return void
         */
-       private function _removeTagRelation($tagid) {
+       private function _removeTagRelation(string $tagid = ''): void {
                if (!empty($this->_data['id'])) {
-                       $queryStr = false;
-                       if ($tagid === false) {
-                               $queryStr = "DELETE
-                                       FROM `" . DB_PREFIX . "_tagrelation`
-                                       WHERE `linkid` = '" . $this->DB->real_escape_string($this->_data['id']) . "'";
-                       } elseif (is_numeric($tagid)) {
+                       $queryStr = '';
+                       if (is_numeric($tagid)) {
                                $queryStr = "DELETE
                                        FROM `" . DB_PREFIX . "_tagrelation`
                                        WHERE `linkid` = '" . $this->DB->real_escape_string($this->_data['id']) . "'
                                        AND `tagid` = '" . $this->DB->real_escape_string($tagid) . "'";
+
+                       } else {
+                               $queryStr = "DELETE
+                                       FROM `" . DB_PREFIX . "_tagrelation`
+                                       WHERE `linkid` = '" . $this->DB->real_escape_string($this->_data['id']) . "'";
                        }
                        if (!empty($queryStr)) {
                                $this->DB->query($queryStr);
@@ -438,21 +436,21 @@ class Link {
        /**
         * remove all or given category relation to the current loaded link
         *
-        * @param boolean|integer $categoryid
+        * @param string $categoryid
         * @return void
         */
-       private function _removeCategoryRelation($categoryid) {
+       private function _removeCategoryRelation(string $categoryid=''): void {
                if (!empty($this->_data['id'])) {
-                       $queryStr = false;
-                       if ($categoryid === false) {
-                               $queryStr = "DELETE
-                                       FROM `" . DB_PREFIX . "_categoryrelation`
-                                       WHERE `linkid` = '" . $this->DB->real_escape_string($this->_data['id']) . "'";
-                       } elseif (is_numeric($categoryid)) {
+                       $queryStr = '';
+                       if (is_numeric($categoryid)) {
                                $queryStr = "DELETE
                                        FROM `" . DB_PREFIX . "_categoryrelation`
                                        WHERE `linkid` = '" . $this->DB->real_escape_string($this->_data['id']) . "'
                                        AND `categoryid` = '" . $this->DB->real_escape_string($categoryid) . "'";
+                       } else {
+                               $queryStr = "DELETE
+                                       FROM `" . DB_PREFIX . "_categoryrelation`
+                                       WHERE `linkid` = '" . $this->DB->real_escape_string($this->_data['id']) . "'";
                        }
                        if (!empty($queryStr)) {
                                $this->DB->query($queryStr);
@@ -466,7 +464,7 @@ class Link {
         *
         * @return void
         */
-       private function _image() {
+       private function _image(): void {
                if (!empty($this->_data['hash'])) {
                        $this->_data['imageToShow'] = $this->_data['image'];
                        $image = ABSOLUTE_PATH.'/'.LOCAL_STORAGE.'/thumbnail-'.$this->_data['hash'].'.jpg';
@@ -483,7 +481,7 @@ class Link {
         *
         * @return void
         */
-       private function _snapshot() {
+       private function _snapshot(): void {
                if (!empty($this->_data['hash'])) {
                        $snapshot = ABSOLUTE_PATH.'/'.LOCAL_STORAGE.'/snapshot-'.$this->_data['hash'].'.jpg';
                        if (file_exists($snapshot)) {
@@ -499,7 +497,7 @@ class Link {
         *
         * @return void
         */
-       private function _pageScreenshot() {
+       private function _pageScreenshot(): void {
                if (!empty($this->_data['hash'])) {
                        $pagescreenshot = ABSOLUTE_PATH.'/'.LOCAL_STORAGE.'/pagescreenshot-'.$this->_data['hash'].'.jpg';
                        if (file_exists($pagescreenshot)) {
@@ -514,7 +512,7 @@ class Link {
         *
         * @return void
         */
-       private function _deleteImage() {
+       private function _deleteImage(): void {
                if (!empty($this->_data['hash']) && !empty($this->_data['imageToShow'])) {
                        $image = ABSOLUTE_PATH.'/'.$this->_data['imageToShow'];
                        if (file_exists($image)) {
@@ -528,7 +526,7 @@ class Link {
         *
         * @return void
         */
-       private function _deleteSnapshot() {
+       private function _deleteSnapshot(): void {
                if (!empty($this->_data['hash']) && !empty($this->_data['snapshotLink'])) {
                        $snapshot = LOCAL_STORAGE.'/snapshot-'.$this->_data['hash'].'.jpg';
                        if (file_exists($snapshot)) {
@@ -542,7 +540,7 @@ class Link {
         *
         * @return void
         */
-       private function _deletePageScreenshot() {
+       private function _deletePageScreenshot(): void {
                if (!empty($this->_data['hash']) && !empty($this->_data['pagescreenshotLink'])) {
                        $pagescreenshot = LOCAL_STORAGE.'/pagescreenshot-'.$this->_data['hash'].'.jpg';
                        if (file_exists($pagescreenshot)) {
@@ -556,7 +554,7 @@ class Link {
         *
         * @return void
         */
-       private function _private() {
+       private function _private(): void {
                if (!empty($this->_data['status']) && $this->_data['status'] == "1") {
                        $this->_data['private'] = "1";
                }
index bed89d9fc4519bbc8a7dc5242cb3334f0915d5f7..4358c20b31ac5c3de3ffe734ecd2cb8aa7ada582 100644 (file)
@@ -3,7 +3,7 @@
  * Insipid
  * Personal web-bookmark-system
  *
- * Copyright 2016-2021 Johannes Keßler
+ * Copyright 2016-2022 Johannes Keßler
  *
  * Development starting from 2011: Johannes Keßler
  * https://www.bananas-playground.net/projekt/insipid/
@@ -31,6 +31,9 @@
  */
 class Management {
 
+       /**
+        * Default value
+        */
        const LINK_QUERY_STATUS = 2;
 
        /**
@@ -38,14 +41,14 @@ class Management {
         *
         * @var mysqli
         */
-       private $DB;
+       private mysqli $DB;
 
        /**
         * Type of links based on status to show
         *
-        * @var bool
+        * @var int
         */
-       private $_queryStatus = self::LINK_QUERY_STATUS;
+       private int $_queryStatus = self::LINK_QUERY_STATUS;
 
 
        /**
@@ -64,7 +67,7 @@ class Management {
         * @param boolean $bool
         * @return void
         */
-       public function setShowPrivate(bool $bool) {
+       public function setShowPrivate(bool $bool): void {
                $this->_queryStatus = self::LINK_QUERY_STATUS;
                if($bool === true) {
                        $this->_queryStatus = 1;
@@ -77,7 +80,7 @@ class Management {
         * @param boolean $bool
         * @return void
         */
-       public function setShowAwm(bool $bool) {
+       public function setShowAwm(bool $bool): void {
                $this->_queryStatus = self::LINK_QUERY_STATUS;
                if($bool === true) {
                        $this->_queryStatus = 3;
@@ -89,11 +92,11 @@ class Management {
         * optional limit
         * optional stats
         *
-        * @param bool|int $limit
+        * @param int $limit
         * @param bool $stats
         * @return array
         */
-       public function categories($limit=false, $stats=false): array {
+       public function categories(int $limit=0, bool $stats=false): array {
                $ret = array();
                $statsInfo = array();
 
@@ -140,11 +143,11 @@ class Management {
         * optional limit
         * optional stats
         *
-        * @param bool|int $limit
+        * @param int $limit
         * @param bool $stats
         * @return array
         */
-       public function tags($limit=false, $stats=false): array {
+       public function tags(int $limit=0, bool $stats=false): array {
                $ret = array();
                $statsInfo = array();
 
@@ -191,7 +194,7 @@ class Management {
         * @param int $limit
         * @return array
         */
-       public function latestLinks($limit=5): array {
+       public function latestLinks(int $limit=5): array {
                $ret = array();
 
                $queryStr = "SELECT `title`, `link` FROM `".DB_PREFIX."_link` AS t";
@@ -216,7 +219,7 @@ class Management {
         * @param int $limit
         * @return array
         */
-       public function randomLink($limit=1): array {
+       public function randomLink(int $limit=1): array {
                $ret = array();
 
                $queryStr = "SELECT `title`, `link`, `hash` FROM `".DB_PREFIX."_link` AS t";
@@ -233,7 +236,13 @@ class Management {
                return $ret;
        }
 
-       public function randomCategory($limit=1): array {
+       /**
+        * Get a random category
+        *
+        * @param int $limit
+        * @return array
+        */
+       public function randomCategory(int $limit=1): array {
                $ret = array();
 
                $queryStr = "SELECT `id`, `name` FROM `".DB_PREFIX."_category`";
@@ -249,7 +258,13 @@ class Management {
                return $ret;
        }
 
-       public function randomTag($limit=1): array {
+       /**
+        * Get a random tag
+        *
+        * @param int $limit
+        * @return array
+        */
+       public function randomTag(int $limit=1): array {
                $ret = array();
 
                $queryStr = "SELECT `id`, `name` FROM `".DB_PREFIX."_tag`";
@@ -277,7 +292,7 @@ class Management {
                foreach($categories as $k=>$v) {
                        $latestLink = $this->latestLinkForCategory($k);
                        if(!empty($latestLink)) {
-                               array_push($ret, array('created' => $latestLink[0]['created'], 'id' => $k, 'name' => $v['name']));
+                               $ret[] = array('created' => $latestLink[0]['created'], 'id' => $k, 'name' => $v['name']);
                        }
                }
 
@@ -291,11 +306,11 @@ class Management {
         * find all links by given category string or id.
         * Return array sorted by creation date DESC
         *
-        * @param int $id Category ID
+        * @param string $id Category ID
         * @param array $options Array with limit|offset|sort|sortDirection
         * @return array
         */
-       public function linksByCategory(int $id, $options=array()): array {
+       public function linksByCategory(string $id, array $options=array()): array {
                $ret = array();
 
                if(!isset($options['limit'])) $options['limit'] = 5;
@@ -358,11 +373,11 @@ class Management {
         * find all links by given tag string or id.
         * Return array sorted by creation date DESC
         *
-        * @param int $id Tag id
+        * @param string $id Tag id
         * @param array $options Array with limit|offset|sort|sortDirection
         * @return array
         */
-       public function linksByTag(int $id, $options=array()): array {
+       public function linksByTag(string $id, array $options=array()): array {
                $ret = array();
 
         if(!isset($options['limit'])) $options['limit'] = 5;
@@ -424,11 +439,11 @@ class Management {
        /**
         * return all links and Info we have from the combined view
         *
-        * @param bool|int $limit
+        * @param int $limit
         * @param bool $offset
         * @return array
         */
-       public function links($limit=10,$offset=false): array {
+       public function links(int $limit=10, bool $offset=false): array {
                $ret = array();
 
                $querySelect = "SELECT `hash`";
@@ -461,10 +476,10 @@ class Management {
        /**
         * return the latest added link for given category id
         *
-        * @param int $categoryid
+        * @param string $categoryid
         * @return array
         */
-       public function latestLinkForCategory(int $categoryid): array {
+       public function latestLinkForCategory(string $categoryid): array {
                $ret = array();
 
                if(!empty($categoryid) && is_numeric($categoryid)) {
@@ -655,7 +670,7 @@ class Management {
         * @param bool $withObject An array with data and the link obj itself
         * @return array
         */
-       public function loadLink(string $hash, $fullInfo=true, $withObject=false): array {
+       public function loadLink(string $hash, bool $fullInfo=true, bool $withObject=false): array {
                $ret = array();
 
                if (!empty($hash)) {
@@ -717,10 +732,10 @@ class Management {
         * Export given link for download as a xml file
         *
         * @param string $hash
-        * @param bool|Link $linkObj Use already existing link obj
+        * @param Link|null $linkObj Use already existing link obj
         * @return bool
         */
-       public function exportLinkData(string $hash, $linkObj=false): bool {
+       public function exportLinkData(string $hash, Link $linkObj=null): bool {
                $ret = false;
 
                if (!empty($hash)) {
@@ -793,11 +808,11 @@ class Management {
         * process the given xml file. Based on the export file
         * options are overwrite => true|false
         *
-        * @param string $file
+        * @param array $file
         * @param array $options
         * @return array
         */
-       public function processImportFile(string $file, array $options): array {
+       public function processImportFile(array $file, array $options): array {
                $ret = array(
                        'status' => 'error',
                        'message' => 'Processing error'
@@ -901,27 +916,20 @@ class Management {
         * @return string
         */
        private function _decideLinkTypeForQuery(): string {
-               switch ($this->_queryStatus) {
-                       case 1:
-                               $ret = "t.status IN (2,1)";
-                               break;
-                       case 3:
-                               $ret = "t.status = 3";
-                               break;
-
-                       default:
-                               $ret = "t.status = 2";
-               }
-               return $ret;
+               return match ($this->_queryStatus) {
+                       1 => "t.status IN (2,1)",
+                       3 => "t.status = 3",
+                       default => "t.status = 2",
+               };
        }
 
        /**
         * Check if given id (not hash) exists in link database
         *
-        * @param integer $id
+        * @param string $id
         * @return bool
         */
-       private function _linkExistsById($id): bool {
+       private function _linkExistsById(string $id): bool {
                $ret = false;
 
                if(!empty($id)) {
index 34b4865720da9c7b92ff5d9db9c8172e7ab02b6f..d9a4e03da4f2b101e7a442ac65887cfa0b86c0d3 100644 (file)
 <?php
+namespace mikehaertl\shellcommand;
 
 /**
  * Command
  *
  * This class represents a shell command.
  *
+ * Its meant for exuting a single command and capturing stdout and stderr.
+ *
+ * Example:
+ *
+ * ```
+ * $command = new Command('/usr/local/bin/mycommand -a -b');
+ * $command->addArg('--name=', "d'Artagnan");
+ * if ($command->execute()) {
+ *     echo $command->getOutput();
+ * } else {
+ *     echo $command->getError();
+ *     $exitCode = $command->getExitCode();
+ * }
+ * ```
+ *
  * @author Michael Härtl <haertl.mike@gmail.com>
  * @license http://www.opensource.org/licenses/MIT
  */
-class ShellCommand
+class Command
 {
-    /**
-     * @var bool whether to escape any argument passed through `addArg()`.
-     * Default is `true`.
-     */
-    public $escapeArgs = true;
-
-    /**
-     * @var bool whether to escape the command passed to `setCommand()` or the
-     * constructor.  This is only useful if `$escapeArgs` is `false`. Default
-     * is `false`.
-     */
-    public $escapeCommand = false;
-
-    /**
-     * @var bool whether to use `exec()` instead of `proc_open()`. This can be
-     * used on Windows system to workaround some quirks there. Note, that any
-     * errors from your command will be output directly to the PHP output
-     * stream. `getStdErr()` will also not work anymore and thus you also won't
-     * get the error output from `getError()` in this case. You also can't pass
-     * any environment variables to the command if this is enabled. Default is
-     * `false`.
-     */
-    public $useExec = false;
-
-    /**
-     * @var bool whether to capture stderr (2>&1) when `useExec` is true. This
-     * will try to redirect the stderr to stdout and provide the complete
-     * output of both in `getStdErr()` and `getError()`.  Default is `true`.
-     */
-    public $captureStdErr = true;
-
-    /**
-     * @var string|null the initial working dir for `proc_open()`. Default is
-     * `null` for current PHP working dir.
-     */
-    public $procCwd;
-
-    /**
-     * @var array|null an array with environment variables to pass to
-     * `proc_open()`. Default is `null` for none.
-     */
-    public $procEnv;
-
-    /**
-     * @var array|null an array of other_options for `proc_open()`. Default is
-     * `null` for none.
-     */
-    public $procOptions;
-
-    /**
-     * @var bool|null whether to set the stdin/stdout/stderr streams to
-     * non-blocking mode when `proc_open()` is used. This allows to have huge
-     * inputs/outputs without making the process hang. The default is `null`
-     * which will enable the feature on Non-Windows systems. Set it to `true`
-     * or `false` to manually enable/disable it. It does not work on Windows.
-     */
-    public $nonBlockingMode;
-
-    /**
-     * @var int the time in seconds after which a command should be terminated.
-     * This only works in non-blocking mode. Default is `null` which means the
-     * process is never terminated.
-     */
-    public $timeout;
-
-    /**
-     * @var null|string the locale to temporarily set before calling
-     * `escapeshellargs()`. Default is `null` for none.
-     */
-    public $locale;
-
-    /**
-     * @var null|string|resource to pipe to standard input
-     */
-    protected $_stdIn;
-
-    /**
-     * @var string the command to execute
-     */
-    protected $_command;
-
-    /**
-     * @var array the list of command arguments
-     */
-    protected $_args = array();
-
-    /**
-     * @var string the full command string to execute
-     */
-    protected $_execCommand;
-
-    /**
-     * @var string the stdout output
-     */
-    protected $_stdOut = '';
-
-    /**
-     * @var string the stderr output
-     */
-    protected $_stdErr = '';
-
-    /**
-     * @var int the exit code
-     */
-    protected $_exitCode;
-
-    /**
-     * @var string the error message
-     */
-    protected $_error = '';
-
-    /**
-     * @var bool whether the command was successfully executed
-     */
-    protected $_executed = false;
-
-    /**
-     * @param string|array $options either a command string or an options array
-     * @see setOptions
-     */
-    public function __construct($options = null)
-    {
-        if (is_array($options)) {
-            $this->setOptions($options);
-        } elseif (is_string($options)) {
-            $this->setCommand($options);
-        }
-    }
-
-    /**
-     * @param array $options array of name => value options that should be
-     * applied to the object You can also pass options that use a setter, e.g.
-     * you can pass a `fileName` option which will be passed to
-     * `setFileName()`.
-     * @throws \Exception
-     * @return static for method chaining
-     */
-    public function setOptions($options)
-    {
-        foreach ($options as $key => $value) {
-            if (property_exists($this, $key)) {
-                $this->$key = $value;
-            } else {
-                $method = 'set'.ucfirst($key);
-                if (method_exists($this, $method)) {
-                    call_user_func(array($this,$method), $value);
-                } else {
-                    throw new \Exception("Unknown configuration option '$key'");
-                }
-            }
-        }
-        return $this;
-    }
-
-    /**
-     * @param string $command the command or full command string to execute,
-     * like 'gzip' or 'gzip -d'.  You can still call addArg() to add more
-     * arguments to the command. If $escapeCommand was set to true, the command
-     * gets escaped with escapeshellcmd().
-     * @return static for method chaining
-     */
-    public function setCommand($command)
-    {
-        if ($this->escapeCommand) {
-            $command = escapeshellcmd($command);
-        }
-        if ($this->getIsWindows()) {
-            // Make sure to switch to correct drive like "E:" first if we have
-            // a full path in command
-            if (isset($command[1]) && $command[1]===':') {
-                $position = 1;
-                // Could be a quoted absolute path because of spaces.
-                // i.e. "C:\Program Files (x86)\file.exe"
-            } elseif (isset($command[2]) && $command[2]===':') {
-                $position = 2;
-            } else {
-                $position = false;
-            }
-
-            // Absolute path. If it's a relative path, let it slide.
-            if ($position) {
-                $command = sprintf(
-                    $command[$position - 1] . ': && cd %s && %s',
-                    escapeshellarg(dirname($command)),
-                    escapeshellarg(basename($command))
-                );
-            }
-        }
-        $this->_command = $command;
-        return $this;
-    }
-
-    /**
-     * @param string|resource $stdIn If set, the string will be piped to the
-     * command via standard input. This enables the same functionality as
-     * piping on the command line. It can also be a resource like a file
-     * handle or a stream in which case its content will be piped into the
-     * command like an input redirection.
-     * @return static for method chaining
-     */
-    public function setStdIn($stdIn) {
-        $this->_stdIn = $stdIn;
-        return $this;
-    }
-
-    /**
-     * @return string|null the command that was set through setCommand() or
-     * passed to the constructor. `null` if none.
-     */
-    public function getCommand()
-    {
-        return $this->_command;
-    }
-
-    /**
-     * @return string|bool the full command string to execute. If no command
-     * was set with setCommand() or passed to the constructor it will return
-     * `false`.
-     */
-    public function getExecCommand()
-    {
-        if ($this->_execCommand===null) {
-            $command = $this->getCommand();
-            if (!$command) {
-                $this->_error = 'Could not locate any executable command';
-                return false;
-            }
-            $args = $this->getArgs();
-            $this->_execCommand = $args ? $command.' '.$args : $command;
-        }
-        return $this->_execCommand;
-    }
-
-    /**
-     * @param string $args the command arguments as string. Note that these
-     * will not get escaped!
-     * @return static for method chaining
-     */
-    public function setArgs($args)
-    {
-        $this->_args = array($args);
-        return $this;
-    }
-
-    /**
-     * @return string the command args that where set with setArgs() or added
-     * with addArg() separated by spaces
-     */
-    public function getArgs()
-    {
-        return implode(' ', $this->_args);
-    }
-
-    /**
-     * @param string $key the argument key to add e.g. `--feature` or
-     * `--name=`. If the key does not end with and `=`, the $value will be
-     * separated by a space, if any. Keys are not escaped unless $value is null
-     * and $escape is `true`.
-     * @param string|array|null $value the optional argument value which will
-     * get escaped if $escapeArgs is true.  An array can be passed to add more
-     * than one value for a key, e.g. `addArg('--exclude',
-     * array('val1','val2'))` which will create the option `'--exclude' 'val1'
-     * 'val2'`.
-     * @param bool|null $escape if set, this overrides the $escapeArgs setting
-     * and enforces escaping/no escaping
-     * @return static for method chaining
-     */
-    public function addArg($key, $value = null, $escape = null)
-    {
-        $doEscape = $escape !== null ? $escape : $this->escapeArgs;
-        $useLocale = $doEscape && $this->locale !== null;
-
-        if ($useLocale) {
-            $locale = setlocale(LC_CTYPE, 0);   // Returns current locale setting
-            setlocale(LC_CTYPE, $this->locale);
-        }
-        if ($value === null) {
-            $this->_args[] = $doEscape ? escapeshellarg($key) : $key;
-        } else {
-            if (substr($key, -1) === '=') {
-                $separator = '=';
-                $argKey = substr($key, 0, -1);
-            } else {
-                $separator = ' ';
-                $argKey = $key;
-            }
-            $argKey = $doEscape ? escapeshellarg($argKey) : $argKey;
-
-            if (is_array($value)) {
-                $params = array();
-                foreach ($value as $v) {
-                    $params[] = $doEscape ? escapeshellarg($v) : $v;
-                }
-                $this->_args[] = $argKey . $separator . implode(' ', $params);
-            } else {
-                $this->_args[] = $argKey . $separator .
-                    ($doEscape ? escapeshellarg($value) : $value);
-            }
-        }
-        if ($useLocale) {
-            setlocale(LC_CTYPE, $locale);
-        }
-
-        return $this;
-    }
-
-    /**
-     * @param bool $trim whether to `trim()` the return value. The default is `true`.
-     * @return string the command output (stdout). Empty if none.
-     */
-    public function getOutput($trim = true)
-    {
-        return $trim ? trim($this->_stdOut) : $this->_stdOut;
-    }
-
-    /**
-     * @param bool $trim whether to `trim()` the return value. The default is `true`.
-     * @return string the error message, either stderr or an internal message.
-     * Empty string if none.
-     */
-    public function getError($trim = true)
-    {
-        return $trim ? trim($this->_error) : $this->_error;
-    }
-
-    /**
-     * @param bool $trim whether to `trim()` the return value. The default is `true`.
-     * @return string the stderr output. Empty if none.
-     */
-    public function getStdErr($trim = true)
-    {
-        return $trim ? trim($this->_stdErr) : $this->_stdErr;
-    }
-
-    /**
-     * @return int|null the exit code or null if command was not executed yet
-     */
-    public function getExitCode()
-    {
-        return $this->_exitCode;
-    }
-
-    /**
-     * @return string whether the command was successfully executed
-     */
-    public function getExecuted()
-    {
-        return $this->_executed;
-    }
-
-    /**
-     * Execute the command
-     *
-     * @return bool whether execution was successful. If `false`, error details
-     * can be obtained from getError(), getStdErr() and getExitCode().
-     */
-    public function execute()
-    {
-        $command = $this->getExecCommand();
-
-        if (!$command) {
-            return false;
-        }
-
-        if ($this->useExec) {
-            $execCommand = $this->captureStdErr ? "$command 2>&1" : $command;
-            exec($execCommand, $output, $this->_exitCode);
-            $this->_stdOut = implode("\n", $output);
-            if ($this->_exitCode !== 0) {
-                $this->_stdErr = $this->_stdOut;
-                $this->_error = empty($this->_stdErr) ? 'Command failed' : $this->_stdErr;
-                return false;
-            }
-        } else {
-            $isInputStream = $this->_stdIn !== null &&
-                is_resource($this->_stdIn) &&
-                in_array(get_resource_type($this->_stdIn), array('file', 'stream'));
-            $isInputString = is_string($this->_stdIn);
-            $hasInput = $isInputStream || $isInputString;
-            $hasTimeout = $this->timeout !== null && $this->timeout > 0;
-
-            $descriptors = array(
-                1   => array('pipe','w'),
-                2   => array('pipe', $this->getIsWindows() ? 'a' : 'w'),
-            );
-            if ($hasInput) {
-                $descriptors[0] = array('pipe', 'r');
-            }
-
-
-            // Issue #20 Set non-blocking mode to fix hanging processes
-            $nonBlocking = $this->nonBlockingMode === null ?
-                !$this->getIsWindows() : $this->nonBlockingMode;
-
-            $startTime = $hasTimeout ? time() : 0;
-            $process = proc_open($command, $descriptors, $pipes, $this->procCwd, $this->procEnv, $this->procOptions);
-
-            if (is_resource($process)) {
-
-                if ($nonBlocking) {
-                    stream_set_blocking($pipes[1], false);
-                    stream_set_blocking($pipes[2], false);
-                    if ($hasInput) {
-                        $writtenBytes = 0;
-                        $isInputOpen = true;
-                        stream_set_blocking($pipes[0], false);
-                        if ($isInputStream) {
-                            stream_set_blocking($this->_stdIn, false);
-                        }
-                    }
-
-                    // Due to the non-blocking streams we now have to check in
-                    // a loop if the process is still running. We also need to
-                    // ensure that all the pipes are written/read alternately
-                    // until there's nothing left to write/read.
-                    $isRunning = true;
-                    while ($isRunning) {
-                        $status = proc_get_status($process);
-                        $isRunning = $status['running'];
-
-                        // We first write to stdIn if we have an input. For big
-                        // inputs it will only write until the input buffer of
-                        // the command is full (the command may now wait that
-                        // we read the output buffers - see below). So we may
-                        // have to continue writing in another cycle.
-                        //
-                        // After everything is written it's safe to close the
-                        // input pipe.
-                        if ($isRunning && $hasInput && $isInputOpen) {
-                            if ($isInputStream) {
-                                $written = stream_copy_to_stream($this->_stdIn, $pipes[0], 16 * 1024, $writtenBytes);
-                                if ($written === false || $written === 0) {
-                                    $isInputOpen = false;
-                                    fclose($pipes[0]);
-                                } else {
-                                    $writtenBytes += $written;
-                                }
-                            } else {
-                                if ($writtenBytes < strlen($this->_stdIn)) {
-                                    $writtenBytes += fwrite($pipes[0], substr($this->_stdIn, $writtenBytes));
-                                } else {
-                                    $isInputOpen = false;
-                                    fclose($pipes[0]);
-                                }
-                            }
-                        }
-
-                        // Read out the output buffers because if they are full
-                        // the command may block execution. We do this even if
-                        // $isRunning is `false`, because there could be output
-                        // left in the buffers.
-                        //
-                        // The latter is only an assumption and needs to be
-                        // verified - but it does not hurt either and works as
-                        // expected.
-                        //
-                        while (($out = fgets($pipes[1])) !== false) {
-                            $this->_stdOut .= $out;
-                        }
-                        while (($err = fgets($pipes[2])) !== false) {
-                            $this->_stdErr .= $err;
-                        }
-
-                        $runTime = $hasTimeout ? time() - $startTime : 0;
-                        if ($isRunning && $hasTimeout && $runTime >= $this->timeout) {
-                            // Only send a SIGTERM and handle status in the next cycle
-                            proc_terminate($process);
-                        }
-
-                        if (!$isRunning) {
-                            $this->_exitCode = $status['exitcode'];
-                            if ($this->_exitCode !== 0 && empty($this->_stdErr)) {
-                                if ($status['stopped']) {
-                                    $signal = $status['stopsig'];
-                                    $this->_stdErr = "Command stopped by signal $signal";
-                                } elseif ($status['signaled']) {
-                                    $signal = $status['termsig'];
-                                    $this->_stdErr = "Command terminated by signal $signal";
-                                } else {
-                                    $this->_stdErr = 'Command unexpectedly terminated without error message';
-                                }
-                            }
-                            fclose($pipes[1]);
-                            fclose($pipes[2]);
-                            proc_close($process);
-                        } else {
-                            // The command is still running. Let's wait some
-                            // time before we start the next cycle.
-                            usleep(10000);
-                        }
-                    }
-                } else {
-                    if ($hasInput) {
-                        if ($isInputStream) {
-                            stream_copy_to_stream($this->_stdIn, $pipes[0]);
-                        } elseif ($isInputString) {
-                            fwrite($pipes[0], $this->_stdIn);
-                        }
-                        fclose($pipes[0]);
-                    }
-                    $this->_stdOut = stream_get_contents($pipes[1]);
-                    $this->_stdErr = stream_get_contents($pipes[2]);
-                    fclose($pipes[1]);
-                    fclose($pipes[2]);
-                    $this->_exitCode = proc_close($process);
-                }
-
-                if ($this->_exitCode !== 0) {
-                    $this->_error = $this->_stdErr ?
-                        $this->_stdErr :
-                        "Failed without error message: $command (Exit code: {$this->_exitCode})";
-                    return false;
-                }
-            } else {
-                $this->_error = "Could not run command $command";
-                return false;
-            }
-        }
-
-        $this->_executed = true;
-
-        return true;
-    }
-
-    /**
-     * @return bool whether we are on a Windows OS
-     */
-    public function getIsWindows()
-    {
-        return strncasecmp(PHP_OS, 'WIN', 3)===0;
-    }
-
-    /**
-     * @return string the current command string to execute
-     */
-    public function __toString()
-    {
-        return (string) $this->getExecCommand();
-    }
+       /**
+        * @var bool whether to escape any argument passed through `addArg()`.
+        * Default is `true`.
+        */
+       public $escapeArgs = true;
+
+       /**
+        * @var bool whether to escape the command passed to `setCommand()` or the
+        * constructor.  This is only useful if `$escapeArgs` is `false`. Default
+        * is `false`.
+        */
+       public $escapeCommand = false;
+
+       /**
+        * @var bool whether to use `exec()` instead of `proc_open()`. This can be
+        * used on Windows system to workaround some quirks there. Note, that any
+        * errors from your command will be output directly to the PHP output
+        * stream. `getStdErr()` will also not work anymore and thus you also won't
+        * get the error output from `getError()` in this case. You also can't pass
+        * any environment variables to the command if this is enabled. Default is
+        * `false`.
+        */
+       public $useExec = false;
+
+       /**
+        * @var bool whether to capture stderr (2>&1) when `useExec` is true. This
+        * will try to redirect the stderr to stdout and provide the complete
+        * output of both in `getStdErr()` and `getError()`.  Default is `true`.
+        */
+       public $captureStdErr = true;
+
+       /**
+        * @var string|null the initial working dir for `proc_open()`. Default is
+        * `null` for current PHP working dir.
+        */
+       public $procCwd;
+
+       /**
+        * @var array|null an array with environment variables to pass to
+        * `proc_open()`. Default is `null` for none.
+        */
+       public $procEnv;
+
+       /**
+        * @var array|null an array of other_options for `proc_open()`. Default is
+        * `null` for none.
+        */
+       public $procOptions;
+
+       /**
+        * @var bool|null whether to set the stdin/stdout/stderr streams to
+        * non-blocking mode when `proc_open()` is used. This allows to have huge
+        * inputs/outputs without making the process hang. The default is `null`
+        * which will enable the feature on Non-Windows systems. Set it to `true`
+        * or `false` to manually enable/disable it. It does not work on Windows.
+        */
+       public $nonBlockingMode;
+
+       /**
+        * @var int the time in seconds after which a command should be terminated.
+        * This only works in non-blocking mode. Default is `null` which means the
+        * process is never terminated.
+        */
+       public $timeout;
+
+       /**
+        * @var null|string the locale to temporarily set before calling
+        * `escapeshellargs()`. Default is `null` for none.
+        */
+       public $locale;
+
+       /**
+        * @var null|string|resource to pipe to standard input
+        */
+       protected $_stdIn;
+
+       /**
+        * @var string the command to execute
+        */
+       protected $_command;
+
+       /**
+        * @var array the list of command arguments
+        */
+       protected $_args = array();
+
+       /**
+        * @var string the stdout output
+        */
+       protected $_stdOut = '';
+
+       /**
+        * @var string the stderr output
+        */
+       protected $_stdErr = '';
+
+       /**
+        * @var int the exit code
+        */
+       protected $_exitCode;
+
+       /**
+        * @var string the error message
+        */
+       protected $_error = '';
+
+       /**
+        * @var bool whether the command was successfully executed
+        */
+       protected $_executed = false;
+
+       /**
+        * @param string|array $options either a command string or an options array
+        * @see setOptions
+        */
+       public function __construct($options = null)
+       {
+               if (is_array($options)) {
+                       $this->setOptions($options);
+               } elseif (is_string($options)) {
+                       $this->setCommand($options);
+               }
+       }
+
+       /**
+        * @param array $options array of name => value options (i.e. public
+        * properties) that should be applied to this object. You can also pass
+        * options that use a setter, e.g. you can pass a `fileName` option which
+        * will be passed to `setFileName()`.
+        * @throws \Exception on unknown option keys
+        * @return static for method chaining
+        */
+       public function setOptions($options)
+       {
+               foreach ($options as $key => $value) {
+                       if (property_exists($this, $key)) {
+                               $this->$key = $value;
+                       } else {
+                               $method = 'set'.ucfirst($key);
+                               if (method_exists($this, $method)) {
+                                       call_user_func(array($this,$method), $value);
+                               } else {
+                                       throw new \Exception("Unknown configuration option '$key'");
+                               }
+                       }
+               }
+               return $this;
+       }
+
+       /**
+        * @param string $command the command or full command string to execute,
+        * like 'gzip' or 'gzip -d'.  You can still call addArg() to add more
+        * arguments to the command. If `$escapeCommand` was set to true, the command
+        * gets escaped with `escapeshellcmd()`.
+        * @return static for method chaining
+        */
+       public function setCommand($command)
+       {
+               if ($this->escapeCommand) {
+                       $command = escapeshellcmd($command);
+               }
+               if ($this->getIsWindows()) {
+                       // Make sure to switch to correct drive like "E:" first if we have
+                       // a full path in command
+                       if (isset($command[1]) && $command[1] === ':') {
+                               $position = 1;
+                               // Could be a quoted absolute path because of spaces.
+                               // i.e. "C:\Program Files (x86)\file.exe"
+                       } elseif (isset($command[2]) && $command[2] === ':') {
+                               $position = 2;
+                       } else {
+                               $position = false;
+                       }
+
+                       // Absolute path. If it's a relative path, let it slide.
+                       if ($position) {
+                               $command = sprintf(
+                                       $command[$position - 1] . ': && cd %s && %s',
+                                       escapeshellarg(dirname($command)),
+                                       escapeshellarg(basename($command))
+                               );
+                       }
+               }
+               $this->_command = $command;
+               return $this;
+       }
+
+       /**
+        * @param string|resource $stdIn If set, the string will be piped to the
+        * command via standard input. This enables the same functionality as
+        * piping on the command line. It can also be a resource like a file
+        * handle or a stream in which case its content will be piped into the
+        * command like an input redirection.
+        * @return static for method chaining
+        */
+       public function setStdIn($stdIn) {
+               $this->_stdIn = $stdIn;
+               return $this;
+       }
+
+       /**
+        * @return string|null the command that was set through `setCommand()` or
+        * passed to the constructor. `null` if none.
+        */
+       public function getCommand()
+       {
+               return $this->_command;
+       }
+
+       /**
+        * @return string|bool the full command string to execute. If no command
+        * was set with `setCommand()` or passed to the constructor it will return
+        * `false`.
+        */
+       public function getExecCommand()
+       {
+               $command = $this->getCommand();
+               if (!$command) {
+                       $this->_error = 'Could not locate any executable command';
+                       return false;
+               }
+
+               $args = $this->getArgs();
+               return $args ? $command.' '.$args : $command;
+       }
+
+       /**
+        * @param string $args the command arguments as string like `'--arg1=value1
+        * --arg2=value2'`. Note that this string will not get escaped. This will
+        * overwrite the args added with `addArgs()`.
+        * @return static for method chaining
+        */
+       public function setArgs($args)
+       {
+               $this->_args = array($args);
+               return $this;
+       }
+
+       /**
+        * @return string the command args that where set with `setArgs()` or added
+        * with `addArg()` separated by spaces.
+        */
+       public function getArgs()
+       {
+               return implode(' ', $this->_args);
+       }
+
+       /**
+        * @param string $key the argument key to add e.g. `--feature` or
+        * `--name=`. If the key does not end with `=`, the (optional) $value will
+        * be separated by a space. The key will get escaped if `$escapeArgs` is `true`.
+        * @param string|array|null $value the optional argument value which will
+        * get escaped if $escapeArgs is true.  An array can be passed to add more
+        * than one value for a key, e.g.
+        * `addArg('--exclude', array('val1','val2'))`
+        * which will create the option
+        * `'--exclude' 'val1' 'val2'`.
+        * @param bool|null $escape if set, this overrides the `$escapeArgs` setting
+        * and enforces escaping/no escaping of keys and values
+        * @return static for method chaining
+        */
+       public function addArg($key, $value = null, $escape = null)
+       {
+               $doEscape = $escape !== null ? $escape : $this->escapeArgs;
+               $useLocale = $doEscape && $this->locale !== null;
+
+               if ($useLocale) {
+                       $locale = setlocale(LC_CTYPE, 0);   // Returns current locale setting
+                       setlocale(LC_CTYPE, $this->locale);
+               }
+               if ($value === null) {
+                       $this->_args[] = $doEscape ? escapeshellarg($key) : $key;
+               } else {
+                       if (substr($key, -1) === '=') {
+                               $separator = '=';
+                               $argKey = substr($key, 0, -1);
+                       } else {
+                               $separator = ' ';
+                               $argKey = $key;
+                       }
+                       $argKey = $doEscape ? escapeshellarg($argKey) : $argKey;
+
+                       if (is_array($value)) {
+                               $params = array();
+                               foreach ($value as $v) {
+                                       $params[] = $doEscape ? escapeshellarg($v) : $v;
+                               }
+                               $this->_args[] = $argKey . $separator . implode(' ', $params);
+                       } else {
+                               $this->_args[] = $argKey . $separator .
+                                       ($doEscape ? escapeshellarg($value) : $value);
+                       }
+               }
+               if ($useLocale) {
+                       setlocale(LC_CTYPE, $locale);
+               }
+
+               return $this;
+       }
+
+       /**
+        * @param bool $trim whether to `trim()` the return value. The default is `true`.
+        * @return string the command output (stdout). Empty if none.
+        */
+       public function getOutput($trim = true)
+       {
+               return $trim ? trim($this->_stdOut) : $this->_stdOut;
+       }
+
+       /**
+        * @param bool $trim whether to `trim()` the return value. The default is `true`.
+        * @return string the error message, either stderr or an internal message.
+        * Empty string if none.
+        */
+       public function getError($trim = true)
+       {
+               return $trim ? trim($this->_error) : $this->_error;
+       }
+
+       /**
+        * @param bool $trim whether to `trim()` the return value. The default is `true`.
+        * @return string the stderr output. Empty if none.
+        */
+       public function getStdErr($trim = true)
+       {
+               return $trim ? trim($this->_stdErr) : $this->_stdErr;
+       }
+
+       /**
+        * @return int|null the exit code or null if command was not executed yet
+        */
+       public function getExitCode()
+       {
+               return $this->_exitCode;
+       }
+
+       /**
+        * @return string whether the command was successfully executed
+        */
+       public function getExecuted()
+       {
+               return $this->_executed;
+       }
+
+       /**
+        * Execute the command
+        *
+        * @return bool whether execution was successful. If `false`, error details
+        * can be obtained from `getError()`, `getStdErr()` and `getExitCode()`.
+        */
+       public function execute()
+       {
+               $command = $this->getExecCommand();
+
+               if (!$command) {
+                       return false;
+               }
+
+               if ($this->useExec) {
+                       $execCommand = $this->captureStdErr ? "$command 2>&1" : $command;
+                       exec($execCommand, $output, $this->_exitCode);
+                       $this->_stdOut = implode("\n", $output);
+                       if ($this->_exitCode !== 0) {
+                               $this->_stdErr = $this->_stdOut;
+                               $this->_error = empty($this->_stdErr) ? 'Command failed' : $this->_stdErr;
+                               return false;
+                       }
+               } else {
+                       $isInputStream = $this->_stdIn !== null &&
+                               is_resource($this->_stdIn) &&
+                               in_array(get_resource_type($this->_stdIn), array('file', 'stream'));
+                       $isInputString = is_string($this->_stdIn);
+                       $hasInput = $isInputStream || $isInputString;
+                       $hasTimeout = $this->timeout !== null && $this->timeout > 0;
+
+                       $descriptors = array(
+                               1   => array('pipe','w'),
+                               2   => array('pipe', $this->getIsWindows() ? 'a' : 'w'),
+                       );
+                       if ($hasInput) {
+                               $descriptors[0] = array('pipe', 'r');
+                       }
+
+
+                       // Issue #20 Set non-blocking mode to fix hanging processes
+                       $nonBlocking = $this->nonBlockingMode === null ?
+                               !$this->getIsWindows() : $this->nonBlockingMode;
+
+                       $startTime = $hasTimeout ? time() : 0;
+                       $process = proc_open($command, $descriptors, $pipes, $this->procCwd, $this->procEnv, $this->procOptions);
+
+                       if (is_resource($process)) {
+
+                               if ($nonBlocking) {
+                                       stream_set_blocking($pipes[1], false);
+                                       stream_set_blocking($pipes[2], false);
+                                       if ($hasInput) {
+                                               $writtenBytes = 0;
+                                               $isInputOpen = true;
+                                               stream_set_blocking($pipes[0], false);
+                                               if ($isInputStream) {
+                                                       stream_set_blocking($this->_stdIn, false);
+                                               }
+                                       }
+
+                                       // Due to the non-blocking streams we now have to check in
+                                       // a loop if the process is still running. We also need to
+                                       // ensure that all the pipes are written/read alternately
+                                       // until there's nothing left to write/read.
+                                       $isRunning = true;
+                                       while ($isRunning) {
+                                               $status = proc_get_status($process);
+                                               $isRunning = $status['running'];
+
+                                               // We first write to stdIn if we have an input. For big
+                                               // inputs it will only write until the input buffer of
+                                               // the command is full (the command may now wait that
+                                               // we read the output buffers - see below). So we may
+                                               // have to continue writing in another cycle.
+                                               //
+                                               // After everything is written it's safe to close the
+                                               // input pipe.
+                                               if ($isRunning && $hasInput && $isInputOpen) {
+                                                       if ($isInputStream) {
+                                                               $written = stream_copy_to_stream($this->_stdIn, $pipes[0], 16 * 1024, $writtenBytes);
+                                                               if ($written === false || $written === 0) {
+                                                                       $isInputOpen = false;
+                                                                       fclose($pipes[0]);
+                                                               } else {
+                                                                       $writtenBytes += $written;
+                                                               }
+                                                       } else {
+                                                               if ($writtenBytes < strlen($this->_stdIn)) {
+                                                                       $writtenBytes += fwrite($pipes[0], substr($this->_stdIn, $writtenBytes));
+                                                               } else {
+                                                                       $isInputOpen = false;
+                                                                       fclose($pipes[0]);
+                                                               }
+                                                       }
+                                               }
+
+                                               // Read out the output buffers because if they are full
+                                               // the command may block execution. We do this even if
+                                               // $isRunning is `false`, because there could be output
+                                               // left in the buffers.
+                                               //
+                                               // The latter is only an assumption and needs to be
+                                               // verified - but it does not hurt either and works as
+                                               // expected.
+                                               //
+                                               while (($out = fgets($pipes[1])) !== false) {
+                                                       $this->_stdOut .= $out;
+                                               }
+                                               while (($err = fgets($pipes[2])) !== false) {
+                                                       $this->_stdErr .= $err;
+                                               }
+
+                                               $runTime = $hasTimeout ? time() - $startTime : 0;
+                                               if ($isRunning && $hasTimeout && $runTime >= $this->timeout) {
+                                                       // Only send a SIGTERM and handle status in the next cycle
+                                                       proc_terminate($process);
+                                               }
+
+                                               if (!$isRunning) {
+                                                       $this->_exitCode = $status['exitcode'];
+                                                       if ($this->_exitCode !== 0 && empty($this->_stdErr)) {
+                                                               if ($status['stopped']) {
+                                                                       $signal = $status['stopsig'];
+                                                                       $this->_stdErr = "Command stopped by signal $signal";
+                                                               } elseif ($status['signaled']) {
+                                                                       $signal = $status['termsig'];
+                                                                       $this->_stdErr = "Command terminated by signal $signal";
+                                                               } else {
+                                                                       $this->_stdErr = 'Command unexpectedly terminated without error message';
+                                                               }
+                                                       }
+                                                       fclose($pipes[1]);
+                                                       fclose($pipes[2]);
+                                                       proc_close($process);
+                                               } else {
+                                                       // The command is still running. Let's wait some
+                                                       // time before we start the next cycle.
+                                                       usleep(10000);
+                                               }
+                                       }
+                               } else {
+                                       if ($hasInput) {
+                                               if ($isInputStream) {
+                                                       stream_copy_to_stream($this->_stdIn, $pipes[0]);
+                                               } elseif ($isInputString) {
+                                                       fwrite($pipes[0], $this->_stdIn);
+                                               }
+                                               fclose($pipes[0]);
+                                       }
+                                       $this->_stdOut = stream_get_contents($pipes[1]);
+                                       $this->_stdErr = stream_get_contents($pipes[2]);
+                                       fclose($pipes[1]);
+                                       fclose($pipes[2]);
+                                       $this->_exitCode = proc_close($process);
+                               }
+
+                               if ($this->_exitCode !== 0) {
+                                       $this->_error = $this->_stdErr ?
+                                               $this->_stdErr :
+                                               "Failed without error message: $command (Exit code: {$this->_exitCode})";
+                                       return false;
+                               }
+                       } else {
+                               $this->_error = "Could not run command $command";
+                               return false;
+                       }
+               }
+
+               $this->_executed = true;
+
+               return true;
+       }
+
+       /**
+        * @return bool whether we are on a Windows OS
+        */
+       public function getIsWindows()
+       {
+               return strncasecmp(PHP_OS, 'WIN', 3)===0;
+       }
+
+       /**
+        * @return string the current command string to execute
+        */
+       public function __toString()
+       {
+               return (string) $this->getExecCommand();
+       }
 }
index ff0a5bc1fd878487ace21cfe37ad88d39afb8c82..b85a646397b1b806bea23262eed50010ad30e3b0 100644 (file)
  */
 class SimpleImap {
 
-       private $_connection;
+       private IMAP\Connection $_connection;
 
-       private $_server = EMAIL_SERVER;
-       private $_user = EMAIL_SERVER_USER;
-       private $_pass = EMAIL_SERVER_PASS;
-       private $_port = EMAIL_SERVER_PORT_IMAP;
-       private $_mailbox = EMAIL_SERVER_MAILBOX;
+       private string $_server = EMAIL_SERVER;
+       private string $_user = EMAIL_SERVER_USER;
+       private string $_pass = EMAIL_SERVER_PASS;
+       private int $_port = EMAIL_SERVER_PORT_IMAP;
+       private string $_mailbox = EMAIL_SERVER_MAILBOX;
 
-       private $_connectionstring = '';
+       private string $_connectionstring = '';
 
        /**
         * SimpleImap constructor.
@@ -67,7 +67,7 @@ class SimpleImap {
         * @see http://ca.php.net/manual/en/function.imap-open.php
         * @throws Exception
         */
-       public function connect() {
+       public function connect(): void {
 
            if(empty($this->_server)) {
                throw new Exception('Missing EMAIL_SERVER');
@@ -134,12 +134,12 @@ class SimpleImap {
        }
 
        /**
-        * the the current stats about the mail connection and INBOX
+        * the current status about the mail connection and INBOX
         * kinda debug only
         *
         * @see http://ca.php.net/manual/en/function.imap-status.php
         */
-       public function mailboxStatus() {
+       public function mailboxStatus(): void {
            if($this->_connection !== false) {
                $status = imap_status($this->_connection, $this->_connectionstring.$this->_mailbox, SA_ALL);
 
@@ -181,7 +181,7 @@ class SimpleImap {
         * @param integer $messagenum
         * @return object
         */
-       public function emailHeaders_rfc822(int $messagenum) {
+       public function emailHeaders_rfc822(int $messagenum): object {
                return imap_rfc822_parse_headers($this->emailHeaders($messagenum));
        }
 
@@ -202,7 +202,7 @@ class SimpleImap {
         * @param integer $messageUid This is the message Uid as an int
         * @param string $folder This is the target folder. Default is EMAIL_ARCHIVE_FOLDER
         */
-       public function moveMessage(int $messageUid, $folder=EMAIL_ARCHIVE_FOLDER) {
+       public function moveMessage(int $messageUid, string $folder=EMAIL_ARCHIVE_FOLDER): void {
            if(!empty($messageUid) && !empty($folder)) {
                $messageUid = (string)$messageUid;
                imap_setflag_full($this->_connection,$messageUid,"\SEEN", ST_UID);
@@ -286,7 +286,7 @@ class SimpleImap {
        /**
         * close the imap connection
         */
-       function close() {
+       function close(): void {
            imap_close($this->_connection);
        }
 }
index 895c0c18892f32c223d2bd468a7e12bde3b7d808..1e0f4c9212d536786fd46b233a832cafb7089d64 100644 (file)
@@ -3,7 +3,7 @@
  * Insipid
  * Personal web-bookmark-system
  *
- * Copyright 2016-2021 Johannes Keßler
+ * Copyright 2016-2022 Johannes Keßler
  *
  * Development starting from 2011: Johannes Keßler
  * https://www.bananas-playground.net/projekt/insipid/
index 8d1de3b52aab46472637e33d5bdbb18244430245..65e8f45aa444f91f2da26e5e8bca4943ba26fbfc 100644 (file)
@@ -131,5 +131,5 @@ if(isset($_POST['category']) && !empty($_POST['category']) && isset($_POST['upda
 }
 
 # show all the categories we have
-$categoryCollection = $Management->categories(false, true);
+$categoryCollection = $Management->categories(0, true);
 $subHeadline = $T->t('view.categories').' <i class="ion-md-filing"></i>';
index ae3a12b51170b4c71d8a810ddca44866311ca14b..71351a85070def7524b09dc5cd90be9e56b87c4b 100644 (file)
@@ -131,5 +131,5 @@ if(isset($_POST['tag']) && !empty($_POST['tag']) && isset($_POST['updateTags']))
 }
 
 # show all the tags we have
-$tagCollection = $Management->tags(false, true);
+$tagCollection = $Management->tags(0, true);
 $subHeadline = $T->t('view.tags').' <i class="ion-md-pricetags"></i>';
index 5aa843151719a63eab7625fece31292e6c38b4ac..4adedf01c44831d6706b73605d07f10623792cc4 100644 (file)
@@ -104,7 +104,7 @@ switch($_requestMode) {
                }
                else {
                        # show all the tags we have
-                       $tagCollection = $Management->tags(false, true);
+                       $tagCollection = $Management->tags(0, true);
                        $subHeadline = $T->t('view.tags').' <i class="ion-md-pricetags"></i>';
                }
        break;
@@ -122,7 +122,7 @@ switch($_requestMode) {
                }
                else {
                        # show all the categories we have
-                       $categoryCollection = $Management->categories(false, true);
+                       $categoryCollection = $Management->categories(0, true);
                        $subHeadline = $T->t('view.categories').' <i class="ion-md-filing"></i>';
                }
        break;