diff --git a/admin/blob/database.php b/admin/blob/database.php
index 03d99ec0a8..b9d1f682e9 100644
--- a/admin/blob/database.php
+++ b/admin/blob/database.php
@@ -6,7 +6,7 @@
$DATABASE_INSTALL = array(
array( "{$CFG->dbprefix}blob_file",
"create table {$CFG->dbprefix}blob_file (
- file_id INTEGER NOT NULL KEY AUTO_INCREMENT,
+ file_id INTEGER NOT NULL AUTO_INCREMENT,
file_sha256 CHAR(64) NOT NULL,
context_id INTEGER NULL,
@@ -28,6 +28,8 @@
INDEX `{$CFG->dbprefix}blob_indx_2` ( path (128) ),
INDEX `{$CFG->dbprefix}blob_indx_4` ( context_id ),
+ CONSTRAINT `{$CFG->dbprefix}lti_blob_file_pk` PRIMARY KEY (file_id),
+
CONSTRAINT `{$CFG->dbprefix}blob_ibfk_1`
FOREIGN KEY (`context_id`)
REFERENCES `{$CFG->dbprefix}lti_context` (`context_id`)
diff --git a/admin/mail/database.php b/admin/mail/database.php
index 797a8a1584..54a37d2c94 100644
--- a/admin/mail/database.php
+++ b/admin/mail/database.php
@@ -33,7 +33,7 @@
) ENGINE = InnoDB DEFAULT CHARSET=utf8"),
array( "{$CFG->dbprefix}mail_sent",
-"create table {$CFG->dbprefix}mail_sent(
+"create table {$CFG->dbprefix}mail_sent (
sent_id INTEGER NOT NULL AUTO_INCREMENT,
context_id INTEGER NOT NULL,
diff --git a/admin/migrate-run.php b/admin/migrate-run.php
index 949b183f20..41c4daad80 100644
--- a/admin/migrate-run.php
+++ b/admin/migrate-run.php
@@ -17,17 +17,17 @@
echo("-- Creating table ".$entry[0]."
\n");
error_log("-- Creating table ".$entry[0]);
$q = $PDOX->queryReturnError($entry[1]);
- if ( ! $q->success ) die("Unable to create ".$entry[1]." ".$q->errorImplode."
".$entry[1] );
+ if ( ! $q->success ) die("Unable to create ".$entry[0]." ".$q->errorImplode."
".$q->sqlQuery );
$OUTPUT->togglePre("-- Created table ".$entry[0], $entry[1]);
$sql = "INSERT INTO {$plugins}
( plugin_path, version, created_at, updated_at ) VALUES
( :plugin_path, :version, NOW(), NOW() )
- ON DUPLICATE KEY
+ ON DUPLICATE KEY /* plugin_path */
UPDATE version = :version, updated_at = NOW()";
$values = array( ":plugin_path" => $path,
":version" => $CFG->dbversion);
$q = $PDOX->queryReturnError($sql, $values);
- if ( ! $q->success ) die("Unable to set version for ".$path." ".$q->errorimplode."
".$entry[1] );
+ if ( ! $q->success ) die("Unable to set version for ".$path." ".$q->errorImplode."
".$q->sqlQuery );
// Do the POST-Create
if ( isset($DATABASE_POST_CREATE) && $DATABASE_POST_CREATE !== false ) {
$DATABASE_POST_CREATE($entry[0]);
@@ -48,7 +48,7 @@
$values = array( ":plugin_path" => $path,
":version" => $CFG->dbversion);
$q = $PDOX->queryReturnError($sql, $values);
- if ( ! $q->success ) die("Unable to set version for ".$path." ".$q->errorimplode."
".$entry[1] );
+ if ( ! $q->success ) die("Unable to set version for ".$path." ".$q->errorImplode."
".$q->sqlQuery );
$delta = time() - $ticks;
if ( $delta > 1 ) echo("--- Ellapsed time=".$delta." seconds
\n");
$ticks = time();
@@ -88,11 +88,11 @@
$sql = "INSERT INTO {$plugins}
( plugin_path, version, created_at, updated_at ) VALUES
( :plugin_path, :version, NOW(), NOW() )
- ON DUPLICATE KEY
+ ON DUPLICATE KEY /* plugin_path */
UPDATE version = :version, updated_at = NOW()";
$values = array( ":version" => $newversion, ":plugin_path" => $path);
$q = $PDOX->queryReturnError($sql, $values);
- if ( ! $q->success ) die("Unable to update version for ".$path." ".$q->errorimplode."
".$entry[1] );
+ if ( ! $q->success ) die("Unable to update version for ".$path." ".$q->errorImplode."
".$q->sqlQuery );
}
// Make sure these do not run twice
diff --git a/composer.json b/composer.json
index f2c00a52c2..e084f2e32a 100644
--- a/composer.json
+++ b/composer.json
@@ -19,7 +19,25 @@
"symfony/polyfill-php73": "v1.22.0",
"symfony/polyfill-php80": "v1.22.0",
- "tsugi/lib": "dev-master#20f59d1ba8d8b558830df55497ba137a887bc504",
+ "guzzlehttp/promises": "1.4.0",
+ "guzzlehttp/psr7": "1.7.0",
+ "nesbot/carbon": "2.45.1",
+ "psr/container": "1.0.0",
+ "react/dns": "v1.4.0",
+ "symfony/console": "v5.2.3",
+ "symfony/error-handler": "v5.2.3",
+ "symfony/event-dispatcher": "v5.2.3",
+ "symfony/finder": "v5.2.3",
+ "symfony/http-foundation": "v5.2.3",
+ "symfony/http-kernel": "v5.2.3",
+ "symfony/mime": "v5.2.3",
+ "symfony/process": "v5.2.3",
+ "symfony/routing": "v5.2.3",
+ "symfony/string": "v5.2.3",
+ "symfony/translation": "v5.2.3",
+ "symfony/var-dumper": "v5.2.3",
+
+ "tsugi/lib": "dev-master#d7e042ccb6aac340f41726e502fe271dbaa30d8e",
"koseu/lib": "dev-master#5c5bcb32469977bea262b1900461c3f205adb899"
},
"config": {
diff --git a/composer.lock b/composer.lock
index 9a2ccc0cd6..e6a9a6c99f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "fb020ee147470f45a69b9452090804de",
+ "content-hash": "f4efc50074337430f3cc63382de21df8",
"packages": [
{
"name": "aws/aws-sdk-php",
@@ -6123,12 +6123,12 @@
"source": {
"type": "git",
"url": "https://github.com/tsugiproject/tsugi-php.git",
- "reference": "20f59d1ba8d8b558830df55497ba137a887bc504"
+ "reference": "d7e042ccb6aac340f41726e502fe271dbaa30d8e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/tsugiproject/tsugi-php/zipball/20f59d1ba8d8b558830df55497ba137a887bc504",
- "reference": "20f59d1ba8d8b558830df55497ba137a887bc504",
+ "url": "https://api.github.com/repos/tsugiproject/tsugi-php/zipball/d7e042ccb6aac340f41726e502fe271dbaa30d8e",
+ "reference": "d7e042ccb6aac340f41726e502fe271dbaa30d8e",
"shasum": ""
},
"require": {
@@ -6171,7 +6171,7 @@
"issues": "https://github.com/tsugiproject/tsugi-php/issues",
"source": "https://github.com/tsugiproject/tsugi-php/tree/master"
},
- "time": "2021-02-28T03:45:33+00:00"
+ "time": "2021-03-21T18:58:10+00:00"
},
{
"name": "vlucas/phpdotenv",
diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php
index 68f9bef499..29153136aa 100644
--- a/vendor/composer/InstalledVersions.php
+++ b/vendor/composer/InstalledVersions.php
@@ -29,7 +29,7 @@ class InstalledVersions
'aliases' =>
array (
),
- 'reference' => 'f1a39eac90658eef699859eb548b80a7230ac210',
+ 'reference' => '401febcb05e0dd53adc06595f5e00ab5b0037515',
'name' => '__root__',
),
'versions' =>
@@ -41,7 +41,7 @@ class InstalledVersions
'aliases' =>
array (
),
- 'reference' => 'f1a39eac90658eef699859eb548b80a7230ac210',
+ 'reference' => '401febcb05e0dd53adc06595f5e00ab5b0037515',
),
'aws/aws-sdk-php' =>
array (
@@ -930,7 +930,7 @@ class InstalledVersions
array (
0 => '9999999-dev',
),
- 'reference' => '20f59d1ba8d8b558830df55497ba137a887bc504',
+ 'reference' => 'd7e042ccb6aac340f41726e502fe271dbaa30d8e',
),
'vlucas/phpdotenv' =>
array (
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index f03ea23742..30e709671e 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -5941,12 +5941,12 @@
"source": {
"type": "git",
"url": "https://github.com/tsugiproject/tsugi-php.git",
- "reference": "20f59d1ba8d8b558830df55497ba137a887bc504"
+ "reference": "d7e042ccb6aac340f41726e502fe271dbaa30d8e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/tsugiproject/tsugi-php/zipball/20f59d1ba8d8b558830df55497ba137a887bc504",
- "reference": "20f59d1ba8d8b558830df55497ba137a887bc504",
+ "url": "https://api.github.com/repos/tsugiproject/tsugi-php/zipball/d7e042ccb6aac340f41726e502fe271dbaa30d8e",
+ "reference": "d7e042ccb6aac340f41726e502fe271dbaa30d8e",
"shasum": ""
},
"require": {
@@ -5959,7 +5959,7 @@
"phpunit/php-timer": ">=1.0.6",
"phpunit/phpunit": "8.*"
},
- "time": "2021-02-28T03:45:33+00:00",
+ "time": "2021-03-21T18:58:10+00:00",
"default-branch": true,
"type": "library",
"installation-source": "dist",
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index e5325712a5..cfd84d8612 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -6,7 +6,7 @@
'aliases' =>
array (
),
- 'reference' => 'f1a39eac90658eef699859eb548b80a7230ac210',
+ 'reference' => '401febcb05e0dd53adc06595f5e00ab5b0037515',
'name' => '__root__',
),
'versions' =>
@@ -18,7 +18,7 @@
'aliases' =>
array (
),
- 'reference' => 'f1a39eac90658eef699859eb548b80a7230ac210',
+ 'reference' => '401febcb05e0dd53adc06595f5e00ab5b0037515',
),
'aws/aws-sdk-php' =>
array (
@@ -907,7 +907,7 @@
array (
0 => '9999999-dev',
),
- 'reference' => '20f59d1ba8d8b558830df55497ba137a887bc504',
+ 'reference' => 'd7e042ccb6aac340f41726e502fe271dbaa30d8e',
),
'vlucas/phpdotenv' =>
array (
diff --git a/vendor/tsugi/lib/src/Core/LTIX.php b/vendor/tsugi/lib/src/Core/LTIX.php
index 29519546c5..6a7eba94ac 100644
--- a/vendor/tsugi/lib/src/Core/LTIX.php
+++ b/vendor/tsugi/lib/src/Core/LTIX.php
@@ -15,6 +15,7 @@
use \Tsugi\UI\Output;
use \Tsugi\Core\I18N;
use \Tsugi\Core\Settings;
+use \Tsugi\Core\SQLDialect;
use \Tsugi\OAuth\OAuthUtil;
use \Tsugi\Crypt\SecureCookie;
use \Tsugi\Crypt\AesCtr;
@@ -74,6 +75,7 @@ public static function getConnection() {
}
}
if ( isset($CFG->slow_query) ) $PDOX->slow_query = $CFG->slow_query;
+ $PDOX->sqlPatch = function($PDOX, $sql) { return \Tsugi\Core\SQLDialect::sqlPatch($PDOX, $sql); } ;
return $PDOX;
}
diff --git a/vendor/tsugi/lib/src/Core/SQLDialect.php b/vendor/tsugi/lib/src/Core/SQLDialect.php
new file mode 100644
index 0000000000..b6a891d732
--- /dev/null
+++ b/vendor/tsugi/lib/src/Core/SQLDialect.php
@@ -0,0 +1,151 @@
+isMySQL() ) {
+ return $sql;
+ }
+ if ( ! $PDOX->isPgSQL() ) {
+ die('Only MySQL and PostgreSQL are supported');
+ }
+
+ // echo("Dialect\n".$sql."\n");
+ $pieces = (new PS($sql))->split();
+ if ( count($pieces) < 1 ) return $sql;
+ if ( strcasecmp($pieces[0], "create") == 0 ) {
+ return self::sqlCreate2Postgres($PDOX, $sql);
+ } else if ( strcasecmp($pieces[0], "insert") == 0 ) {
+ return self::sqlInsert2Postgres($PDOX, $sql);
+ } else if ( strcasecmp($pieces[0], "alter") == 0 ) {
+ return self::sqlAlter2Postgres($PDOX, $sql);
+ }
+ return $sql;
+ }
+
+ public static function sqlCreate2Postgres($PDOX, $sql) {
+
+ $nsql = self::patchPostgresQuotes($PDOX, $sql);
+ $nsql = self::patchDataTypes($PDOX, $nsql);
+
+ // ) ENGINE = InnoDB DEFAULT CHARSET=utf8;";
+ // ) COLLATE utf8_bin, ENGINE = InnoDB;
+ $nsql = preg_replace('/\).*ENGINE\s*=\s*InnoDB.*$/i', ');', $nsql);
+ $nsql = preg_replace('/\s+USING\s+HASH\s+/i', ' ', $nsql);
+
+ return $nsql;
+ }
+
+ // ON DUPLICATE KEY /* plugin_path */ UPDATE
+ // ON CONFLICT (plugin_path) DO UPDATE SET
+ public static function sqlInsert2Postgres($PDOX, $sql) {
+ $nsql = self::patchPostgresQuotes($PDOX, $sql);
+
+ $matches = array();
+ preg_match('/ON\s+DUPLICATE\s+KEY\s\/\*\s+[^ *]*\s+\*\/\s+UPDATE\s+/i', $nsql, $matches, PREG_OFFSET_CAPTURE);
+ if ( count($matches) < 1 ) return $nsql;
+ $str = $matches[0][0];
+ $pos = $matches[0][1];
+ $len = strlen($str);
+ $str = preg_replace('/\sDUPLICATE\s+KEY\s/i', ' CONFLICT ', $str);
+ $str = preg_replace('/\/\*/i', '(', $str);
+ $str = preg_replace('/\*\//i', ')', $str);
+ $str = preg_replace('/\s+UPDATE\s+/i', " DO UPDATE SET\n", $str);
+
+ $newsql = substr($nsql,0,$pos) . $str . substr($nsql, $pos+$len);
+ return $newsql;
+ }
+
+ public static function sqlAlter2Postgres($PDOX, $sql) {
+
+ $nsql = self::patchPostgresQuotes($PDOX, $sql);
+ $nsql = self::patchDataTypes($PDOX, $nsql);
+
+ // ALTER TABLE lms_tools_status MODIFY commit_log MEDIUMTEXT NULL
+ // ALTER TABLE lms_tools_status ALTER COLUMN column_name TYPE new_data_type;
+ // ALTER TABLE lms_tools_status ALTER COLUMN column_name DROP NOT NULL;
+ $matches = array();
+ preg_match('/\s+MODIFY\s+[^\s]*\s+/i', $nsql, $matches, PREG_OFFSET_CAPTURE);
+ if ( count($matches) < 1 ) return $nsql;
+ $str = $matches[0][0];
+ $pos = $matches[0][1];
+ $len = strlen($str);
+ $str = preg_replace('/\sMODIFY\s/i', ' ALTER COLUMN ', $str);
+
+ $tail = substr($nsql, $pos+$len);
+ $pieces = (new PS($tail))->split();
+ if ( count($pieces) < 1 ) {
+ $newsql = substr($nsql,0,$pos) . $str . ' TYPE ' .substr($nsql, $pos+$len);
+ return $newsql;
+ }
+
+ // Normal flow
+ $retval = array();
+ $newsql = substr($nsql,0,$pos) . $str . ' TYPE ' . array_shift($pieces);
+ $retval[] = $newsql;
+
+ $alter = substr($nsql,0,$pos) . $str . ' ';
+ if ( count($pieces) == 1 && strcasecmp($pieces[0], "null") == 0 ) {
+ $newsql = $alter . "DROP NOT NULL;";
+ $retval[] = $newsql;
+ } else if (count($pieces) == 2 && strcasecmp($pieces[0], "NOT") == 0 &&
+ strcasecmp($pieces[0], "NULL") == 0 ) {
+ $newsql = $alter . "SET NOT NULL;";
+ $retval[] = $newsql;
+ }
+ if ( count($retval) == 1 ) {
+ return $retval[0];
+ }
+ return $retval;
+ }
+
+
+ public static function patchDataTypes($PDOX, $sql) {
+ // https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_serial
+ // https://www.postgresqltutorial.com/postgresql-identity-column/
+ // https://www.depesz.com/2017/04/10/waiting-for-postgresql-10-identity-columns/
+ // plugin_id INTEGER NOT NULL AUTO_INCREMENT,
+ // plugin_id INTEGER NOT NULL primary key generated always as identity
+ $nsql = preg_replace('/AUTO_INCREMENT/i', "GENERATED BY DEFAULT AS IDENTITY", $sql);
+
+ // created_at DATETIME NOT NULL,
+ // created_at TIMESTAMP(0) NOT NULL,
+ $nsql = preg_replace('/\sDATETIME([\s,])/i', ' TIMESTAMP(0)$1', $nsql);
+
+ // deleted TINYINT(1) NOT NULL DEFAULT 0,
+ // deleted SMALLINT NOT NULL DEFAULT 0,
+ $nsql = preg_replace('/\sTINYINT\(1\)([\s,])/i', ' SMALLINT$1', $nsql);
+
+ $nsql = preg_replace('/\sMEDIUMTEXT([\s,])/i', ' TEXT$1', $nsql);
+
+ $nsql = preg_replace('/\sMEDIUMINT([\s,])/i', ' INTEGER$1', $nsql);
+ $nsql = preg_replace('/\sTINYINT([\s,])/i', ' INTEGER$1', $nsql);
+
+ $nsql = preg_replace('/\sUNSIGNED([\s,])/i', '$1', $nsql);
+
+ $nsql = preg_replace('/\sVARBINARY\([0-9]+\)([\s,])/i', ' BYTEA$1', $nsql);
+
+ $nsql = preg_replace('/\sBINARY\([0-9]+\)([\s,])/i', ' BYTEA$1', $nsql);
+
+ $nsql = preg_replace('/\sDOUBLE([\s,])/i', ' DOUBLE PRECISION$1', $nsql);
+
+ $nsql = preg_replace('/\sBLOB([\s,])/i', ' BYTEA$1', $nsql);
+
+ return $nsql;
+ }
+
+ public static function patchPostgresQuotes($PDOX, $sql) {
+ $nsql = str_replace('`', '"', $sql);
+ return $nsql;
+ }
+
+}
diff --git a/vendor/tsugi/lib/src/Util/PDOX.php b/vendor/tsugi/lib/src/Util/PDOX.php
index b767a7e959..76cd329cff 100644
--- a/vendor/tsugi/lib/src/Util/PDOX.php
+++ b/vendor/tsugi/lib/src/Util/PDOX.php
@@ -88,12 +88,30 @@ function queryReturnError($sql, $arr=FALSE, $error_log=TRUE) {
if ( $arr !== FALSE && ! is_array($arr) ) $arr = Array($arr);
$start = microtime(true);
// debug_log($sql, $arr);
- try {
- $q = $this->prepare($sql);
- if ( $arr === FALSE ) {
- $success = $q->execute();
+
+ // Optionally patch the SQL to support different variants
+ $todo = array();
+ $todo[] = $sql;
+ if ( isset($this->sqlPatch) && is_callable($this->sqlPatch) ) {
+ $func = $this->sqlPatch;
+ $check = $func($this, $sql);
+ if ( is_array($check) ) {
+ $todo = $check;
} else {
- $success = $q->execute($arr);
+ $todo = array();
+ $todo[] = $check;
+ }
+ }
+
+ try {
+ foreach($todo as $query) {
+ $q = $this->prepare($query);
+ if ( $arr === FALSE ) {
+ $success = $q->execute();
+ } else {
+ $success = $q->execute($arr);
+ }
+ if ( ! $success ) break;
}
} catch(\Exception $e) {
$success = FALSE;
@@ -121,6 +139,8 @@ function queryReturnError($sql, $arr=FALSE, $error_log=TRUE) {
if ( !isset($q->errorCode) ) $q->errorCode = '42000';
if ( !isset($q->errorInfo) ) $q->errorInfo = Array('42000', '42000', $message);
if ( !isset($q->errorImplode) ) $q->errorImplode = implode(':',$q->errorInfo);
+ if ( !isset($q->sqlQuery) ) $q->sqlQuery = implode('; ', $todo);
+ if ( !isset($q->sqlOriginalQuery) ) $q->sqlOriginalQuery = $sql;
// Restore ERRMODE if we changed it
if ( $errormode != \PDO::ERRMODE_EXCEPTION) {
$this->setAttribute(\PDO::ATTR_ERRMODE, $errormode);
@@ -201,30 +221,28 @@ function allRowsDie($sql, $arr=FALSE, $error_log=TRUE) {
* Retrieve the metadata for a table.
*/
function metadata($tablename) {
- $sql = "SHOW COLUMNS FROM ".$tablename;
+ if ( $this->isMySQL() ) {
+ $sql = "SHOW COLUMNS FROM ".$tablename;
+ } else {
+ $sql = 'SELECT column_name AS "Field", data_type AS "Type", is_nullable AS "Null"
+ FROM information_schema.columns WHERE table_name = \''.$tablename.'\';';
+ }
$stmt = self::queryReturnError($sql);
if ( $stmt->success ) {
$retval= $stmt->fetchAll();
+ if ( count($retval) == 0 ) $retval = false;
} else {
$retval = false;
}
$stmt->closeCursor();
- return $retval;
+ return $retval;
}
/**
* Retrieve the metadata for a table.
*/
function describe($tablename) {
- $sql = "DESCRIBE ".$tablename;
- $stmt = self::queryReturnError($sql);
- if ( $stmt->success ) {
- $retval= $stmt->fetchAll();
- } else {
- $retval = false;
- }
- $stmt->closeCursor();
- return $retval;
+ return $this->metadata($tablename);
}
/**
@@ -279,8 +297,9 @@ function columnIsNull($fieldname, $source)
function columnExists($fieldname, $source)
{
if ( is_string($source) ) { // Demand table exists
- $source = self::describe($source);
- if ( ! $source ) throw new \Exception("Could not find $source");
+ $check = self::describe($source);
+ if ( ! $check ) throw new \Exception("Could not find $source");
+ $source = $check;
}
$column = self::describeColumn($fieldname, $source);
return is_array($column);
@@ -359,4 +378,21 @@ function versionAtLeast($min)
return (version_compare($version, $min) >= 0);
}
+ /**
+ * Return true if the current connection is MySQL
+ */
+ function isMySQL()
+ {
+ $name = $this->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ return $name == 'mysql';
+ }
+
+ /**
+ * Return true if the current connection is PgSQL
+ */
+ function isPgSQL()
+ {
+ $name = $this->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ return $name == 'pgsql';
+ }
}
diff --git a/vendor/tsugi/lib/src/Util/PS.php b/vendor/tsugi/lib/src/Util/PS.php
index 5cba6503a3..05643a5b43 100644
--- a/vendor/tsugi/lib/src/Util/PS.php
+++ b/vendor/tsugi/lib/src/Util/PS.php
@@ -88,4 +88,41 @@ public function rfind($sub, $start=0, $end=-1) {
}
return strrpos($tmp, $sub);
}
+
+ /**
+ * emulate python strip()
+ *
+ * string.strip(characters)
+ *
+ * characters Optional. A set of characters to remove as leading/trailing characters
+ *
+ * txt = ",,,,,rrttgg.....banana....rrr"
+ * x = txt.strip(",.grt")
+ */
+ public function strip($characters=false) {
+ if ( ! is_string($characters) ) return trim($this->internal);
+ return trim($this->internal, $characters);
+ }
+
+ /**
+ * emulate the Python split()
+ *
+ * string.split(separator, maxsplit)
+ *
+ * separator Optional. Specifies the separator to use when splitting the string. By default any whitespace is a separator
+ * maxsplit Optional. Specifies how many splits to do. Default value is -1, which is "all occurrences"
+ *
+ * Note: When maxsplit is specified, the list will contain the specified number of elements plus one.
+ */
+ public function split($separator=false,$maxsplit=false) {
+ if ( $separator == false ) {
+ $retval = preg_split('/\s+/', $this->strip());
+ } else {
+ $retval = explode($separator, $this->internal);
+ }
+ if ( $maxsplit !== false && count($retval) > $maxsplit + 1 ) {
+ return array_slice($retval, 0, $maxsplit+1);
+ }
+ return $retval;
+ }
}