mirror of
https://github.com/maxpozdeev/mytinytodo.git
synced 2026-03-11 08:55:27 +00:00
continue refactor db classes
This commit is contained in:
parent
651f9687f9
commit
e1c5540c13
8 changed files with 104 additions and 61 deletions
|
|
@ -25,7 +25,7 @@ else $sqlSort .= "ow ASC";
|
|||
|
||||
$data = array();
|
||||
$q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist WHERE list_id=$listId $sqlSort");
|
||||
while($r = $q->fetch_assoc($q))
|
||||
while($r = $q->fetchAssoc())
|
||||
{
|
||||
$data[] = $r;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ function fillData(&$data, $listId, $field, $sqlWhere )
|
|||
$lang = Lang::instance();
|
||||
$db = DBConnection::instance();
|
||||
$q = $db->dq("SELECT * FROM {$db->prefix}todolist WHERE list_id=$listId $sqlWhere ORDER BY $field DESC LIMIT 100");
|
||||
while ($r = $q->fetch_assoc($q))
|
||||
while ($r = $q->fetchAssoc())
|
||||
{
|
||||
if ($r['prio'] > 0) {
|
||||
$r['prio'] = '+'.$r['prio'];
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------- //
|
||||
class DatabaseResult_Mysql
|
||||
class DatabaseResult_Mysql extends DatabaseResult_Abstract
|
||||
{
|
||||
private $q;
|
||||
private $affected;
|
||||
|
|
@ -26,12 +26,12 @@ class DatabaseResult_Mysql
|
|||
}
|
||||
}
|
||||
|
||||
function fetch_row()
|
||||
function fetchRow()
|
||||
{
|
||||
return $this->q->fetch(PDO::FETCH_NUM);
|
||||
}
|
||||
|
||||
function fetch_assoc()
|
||||
function fetchAssoc()
|
||||
{
|
||||
return $this->q->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ class DatabaseResult_Mysql
|
|||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- //
|
||||
class Database_Mysql
|
||||
class Database_Mysql extends Database_Abstract
|
||||
{
|
||||
private $dbh;
|
||||
private $affected = null;
|
||||
|
|
@ -55,8 +55,12 @@ class Database_Mysql
|
|||
{
|
||||
}
|
||||
|
||||
function connect($host, $user, $pass, $db)
|
||||
function connect($params)
|
||||
{
|
||||
$host = $params['host'];
|
||||
$user = $params['user'];
|
||||
$pass = $params['password'];
|
||||
$db = $params['db'];
|
||||
$options = array(
|
||||
PDO::MYSQL_ATTR_FOUND_ROWS => true,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
||||
|
|
@ -77,7 +81,7 @@ class Database_Mysql
|
|||
{
|
||||
$q = $this->_dq($query, $p);
|
||||
|
||||
$res = $q->fetch_row();
|
||||
$res = $q->fetchRow();
|
||||
if ($res === false) return NULL;
|
||||
|
||||
if (sizeof($res) > 1) return $res;
|
||||
|
|
@ -92,14 +96,14 @@ class Database_Mysql
|
|||
{
|
||||
$q = $this->_dq($query, $p);
|
||||
|
||||
$res = $q->fetch_assoc();
|
||||
$res = $q->fetchAssoc();
|
||||
if ($res === false) return NULL;
|
||||
|
||||
if (sizeof($res) > 1) return $res;
|
||||
else return $res[0];
|
||||
}
|
||||
|
||||
function dq($query, $p = NULL)
|
||||
function dq($query, $p = NULL) : DatabaseResult_Abstract
|
||||
{
|
||||
return $this->_dq($query, $p);
|
||||
}
|
||||
|
|
@ -109,10 +113,11 @@ class Database_Mysql
|
|||
*/
|
||||
function ex($query, $p = NULL)
|
||||
{
|
||||
return $this->_dq($query, $p, true);
|
||||
$dbr = $this->_dq($query, $p, true);
|
||||
return $this->affected();
|
||||
}
|
||||
|
||||
private function _dq($query, $p = NULL, $resultless = 0)
|
||||
private function _dq($query, $p = NULL, $resultless = 0) : DatabaseResult_Abstract
|
||||
{
|
||||
if (!isset($p)) $p = array();
|
||||
elseif (!is_array($p)) $p = array($p);
|
||||
|
|
@ -155,12 +160,12 @@ class Database_Mysql
|
|||
return '\''. sprintf($format, $s). '\'';
|
||||
}
|
||||
|
||||
function last_insert_id()
|
||||
function lastInsertId()
|
||||
{
|
||||
return $this->dbh->lastInsertId();
|
||||
}
|
||||
|
||||
function table_exists($table)
|
||||
function tableExists($table)
|
||||
{
|
||||
$r = $this->sq("SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?",
|
||||
array($this->dbname, $table) );
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------- //
|
||||
class DatabaseResult_Mysql
|
||||
class DatabaseResult_Mysql extends DatabaseResult_Abstract
|
||||
{
|
||||
private $q; //mysqli_result
|
||||
|
||||
|
|
@ -15,19 +15,19 @@ class DatabaseResult_Mysql
|
|||
$this->q = $dbh->query($query); //throws mysqli_sql_exception
|
||||
}
|
||||
|
||||
function fetch_row()
|
||||
function fetchRow()
|
||||
{
|
||||
return $this->q->fetch_row();
|
||||
}
|
||||
|
||||
function fetch_assoc()
|
||||
function fetchAssoc()
|
||||
{
|
||||
return $this->q->fetch_assoc();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- //
|
||||
class Database_Mysql
|
||||
class Database_Mysql extends Database_Abstract
|
||||
{
|
||||
private $dbh; //mysqli
|
||||
private $dbname;
|
||||
|
|
@ -39,14 +39,18 @@ class Database_Mysql
|
|||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
}
|
||||
|
||||
function connect($host, $user, $pass, $db)
|
||||
function connect($params)
|
||||
{
|
||||
$host = $params['host'];
|
||||
$user = $params['user'];
|
||||
$pass = $params['password'];
|
||||
$db = $params['db'];
|
||||
$this->dbname = $db;
|
||||
$this->dbh = new mysqli($host, $user, $pass, $db); //throws mysqli_sql_exception
|
||||
return true;
|
||||
}
|
||||
|
||||
function last_insert_id()
|
||||
function lastInsertId()
|
||||
{
|
||||
return $this->dbh->insert_id;
|
||||
}
|
||||
|
|
@ -55,7 +59,7 @@ class Database_Mysql
|
|||
{
|
||||
$q = $this->_dq($query, $p);
|
||||
|
||||
$res = $q->fetch_row();
|
||||
$res = $q->fetchRow();
|
||||
if ($res === false || $res === null) return NULL;
|
||||
|
||||
if (sizeof($res) > 1) return $res;
|
||||
|
|
@ -66,13 +70,13 @@ class Database_Mysql
|
|||
{
|
||||
$q = $this->_dq($query, $p);
|
||||
|
||||
$res = $q->fetch_assoc();
|
||||
$res = $q->fetchAssoc();
|
||||
if ($res === false || $res === null) return NULL;
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function dq($query, $p = NULL)
|
||||
function dq($query, $p = NULL) : DatabaseResult_Abstract
|
||||
{
|
||||
return $this->_dq($query, $p);
|
||||
}
|
||||
|
|
@ -86,7 +90,7 @@ class Database_Mysql
|
|||
return $this->affected();
|
||||
}
|
||||
|
||||
private function _dq($query, $p = NULL, $resultless = 0)
|
||||
private function _dq($query, $p = NULL, $resultless = 0) : DatabaseResult_Abstract
|
||||
{
|
||||
if (!isset($p)) $p = array();
|
||||
elseif (!is_array($p)) $p = array($p);
|
||||
|
|
@ -127,7 +131,7 @@ class Database_Mysql
|
|||
return '\''. sprintf($format, $s). '\'';
|
||||
}
|
||||
|
||||
function table_exists($table)
|
||||
function tableExists($table)
|
||||
{
|
||||
$r = $this->sq("SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?",
|
||||
array($this->dbname, $table) );
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
Licensed under the GNU GPL v2 license. See file COPYRIGHT for details.
|
||||
*/
|
||||
|
||||
class DatabaseResult_Sqlite3
|
||||
class DatabaseResult_Sqlite3 extends DatabaseResult_Abstract
|
||||
{
|
||||
private $q;
|
||||
private $affected;
|
||||
|
|
@ -25,12 +25,12 @@ class DatabaseResult_Sqlite3
|
|||
}
|
||||
}
|
||||
|
||||
function fetch_row()
|
||||
function fetchRow()
|
||||
{
|
||||
return $this->q->fetch(PDO::FETCH_NUM);
|
||||
}
|
||||
|
||||
function fetch_assoc()
|
||||
function fetchAssoc()
|
||||
{
|
||||
return $this->q->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ class DatabaseResult_Sqlite3
|
|||
|
||||
}
|
||||
|
||||
class Database_Sqlite3
|
||||
class Database_Sqlite3 extends Database_Abstract
|
||||
{
|
||||
private $dbh;
|
||||
private $affected = null;
|
||||
|
|
@ -53,8 +53,9 @@ class Database_Sqlite3
|
|||
{
|
||||
}
|
||||
|
||||
function connect($filename)
|
||||
function connect($params)
|
||||
{
|
||||
$filename = $params['filename'];
|
||||
$options = array(
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
||||
);
|
||||
|
|
@ -69,7 +70,7 @@ class Database_Sqlite3
|
|||
{
|
||||
$q = $this->_dq($query, $p);
|
||||
|
||||
$res = $q->fetch_row();
|
||||
$res = $q->fetchRow();
|
||||
if ($res === false) return NULL;
|
||||
|
||||
if (sizeof($res) > 1) return $res;
|
||||
|
|
@ -83,7 +84,7 @@ class Database_Sqlite3
|
|||
{
|
||||
$q = $this->_dq($query, $p);
|
||||
|
||||
$res = $q->fetch_assoc();
|
||||
$res = $q->fetchAssoc();
|
||||
if ($res === false) return NULL;
|
||||
return $res;
|
||||
}
|
||||
|
|
@ -91,7 +92,7 @@ class Database_Sqlite3
|
|||
/*
|
||||
SELECT queries for multiple rows
|
||||
*/
|
||||
function dq($query, $p = NULL)
|
||||
function dq($query, $p = NULL) : DatabaseResult_Abstract
|
||||
{
|
||||
return $this->_dq($query, $p);
|
||||
}
|
||||
|
|
@ -105,7 +106,7 @@ class Database_Sqlite3
|
|||
return $this->affected();
|
||||
}
|
||||
|
||||
private function _dq($query, $p = NULL, $resultless = 0)
|
||||
private function _dq($query, $p = NULL, $resultless = 0) : DatabaseResult_Abstract
|
||||
{
|
||||
if (!isset($p)) $p = array();
|
||||
elseif (!is_array($p)) $p = array($p);
|
||||
|
|
@ -148,12 +149,12 @@ class Database_Sqlite3
|
|||
return $this->dbh->quote(sprintf($format, $s)). " ESCAPE '\'";
|
||||
}
|
||||
|
||||
function last_insert_id()
|
||||
function lastInsertId()
|
||||
{
|
||||
return $this->dbh->lastInsertId();
|
||||
}
|
||||
|
||||
function table_exists($table)
|
||||
function tableExists($table)
|
||||
{
|
||||
$exists = $this->sq("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", $table);
|
||||
if ($exists == "1") {
|
||||
|
|
|
|||
|
|
@ -115,13 +115,13 @@ class DBConnection
|
|||
{
|
||||
protected static $instance;
|
||||
|
||||
public static function init($instance)
|
||||
public static function init(Database_Abstract $instance) : Database_Abstract
|
||||
{
|
||||
self::$instance = $instance;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public static function instance()
|
||||
public static function instance() : Database_Abstract
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
throw new Exception("DBConnection is not initialized");
|
||||
|
|
@ -136,4 +136,26 @@ class DBConnection
|
|||
}
|
||||
}
|
||||
|
||||
abstract class Database_Abstract
|
||||
{
|
||||
var $lastQuery = null;
|
||||
var $prefix = '';
|
||||
abstract function connect($params);
|
||||
abstract function sq($query, $p = NULL);
|
||||
abstract function sqa($query, $p = NULL);
|
||||
abstract function dq($query, $p = NULL) : DatabaseResult_Abstract;
|
||||
abstract function ex($query, $p = NULL);
|
||||
abstract function affected();
|
||||
abstract function quote($s);
|
||||
abstract function quoteForLike($format, $s);
|
||||
abstract function lastInsertId();
|
||||
abstract function tableExists($table);
|
||||
}
|
||||
|
||||
abstract class DatabaseResult_Abstract
|
||||
{
|
||||
abstract function fetchRow();
|
||||
abstract function fetchAssoc();
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
@ -47,7 +47,12 @@ if (Config::get('db') == 'mysql')
|
|||
else require_once(MTTINC. 'class.db.mysql.php');
|
||||
$db = DBConnection::init(new Database_Mysql);
|
||||
try {
|
||||
$db->connect(Config::get('mysql.host'), Config::get('mysql.user'), Config::get('mysql.password'), Config::get('mysql.db'));
|
||||
$db->connect( array(
|
||||
'host' => Config::get('mysql.host'),
|
||||
'user' => Config::get('mysql.user'),
|
||||
'password' => Config::get('mysql.password'),
|
||||
'db' => Config::get('mysql.db')
|
||||
));
|
||||
}
|
||||
catch(Exception $e) {
|
||||
logAndDie("Failed to connect to mysql database: ". $e->getMessage());
|
||||
|
|
@ -60,7 +65,7 @@ elseif(Config::get('db') == 'sqlite')
|
|||
{
|
||||
require_once(MTTINC. 'class.db.sqlite3.php');
|
||||
$db = DBConnection::init(new Database_Sqlite3);
|
||||
$db->connect(MTTPATH. 'db/todolist.db');
|
||||
$db->connect( array( 'filename' => MTTPATH. 'db/todolist.db' ) );
|
||||
}
|
||||
else {
|
||||
# It seems not installed
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ if($config['db'] != '')
|
|||
{
|
||||
die("Access denied!<br> Disable password protection or Log in.");
|
||||
}
|
||||
$db = DBConnection::instance();
|
||||
$dbtype = (strtolower(get_class($db)) == 'database_mysql') ? 'mysql' : 'sqlite';
|
||||
}
|
||||
else
|
||||
|
|
@ -42,7 +43,7 @@ else
|
|||
Config::loadConfig($config);
|
||||
unset($config);
|
||||
|
||||
$db = 0;
|
||||
$db = null;
|
||||
$dbtype = '';
|
||||
}
|
||||
|
||||
|
|
@ -270,12 +271,12 @@ echo "Done<br><br> <b>Attention!</b> Delete this file for security reasons.";
|
|||
printFooter();
|
||||
|
||||
|
||||
function get_ver($db, $dbtype)
|
||||
function get_ver(Database_Abstract $db, $dbtype)
|
||||
{
|
||||
if(!$db || $dbtype == '') return '';
|
||||
if(!$db->table_exists($db->prefix.'todolist')) return '';
|
||||
if(!$db->tableExists($db->prefix.'todolist')) return '';
|
||||
$v = '1.0';
|
||||
if(!$db->table_exists($db->prefix.'tags')) return $v;
|
||||
if(!$db->tableExists($db->prefix.'tags')) return $v;
|
||||
$v = '1.1';
|
||||
if($dbtype == 'mysql') {
|
||||
if(!has_field_mysql($db, $db->prefix.'todolist', 'duedate')) return $v;
|
||||
|
|
@ -283,7 +284,7 @@ function get_ver($db, $dbtype)
|
|||
if(!has_field_sqlite($db, $db->prefix.'todolist', 'duedate')) return $v;
|
||||
}
|
||||
$v = '1.2';
|
||||
if(!$db->table_exists($db->prefix.'lists')) return $v;
|
||||
if(!$db->tableExists($db->prefix.'lists')) return $v;
|
||||
$v = '1.3.0';
|
||||
if($dbtype == 'mysql') {
|
||||
if(!has_field_mysql($db, $db->prefix.'todolist', 'd_completed')) return $v;
|
||||
|
|
@ -313,19 +314,19 @@ function printFooter()
|
|||
}
|
||||
|
||||
|
||||
function has_field_sqlite($db, $table, $field)
|
||||
function has_field_sqlite(Database_Abstract $db, $table, $field)
|
||||
{
|
||||
$q = $db->dq("PRAGMA table_info(". $db->quote($table). ")");
|
||||
while($r = $q->fetch_row()) {
|
||||
while($r = $q->fetchRow()) {
|
||||
if($r[1] == $field) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function has_field_mysql($db, $table, $field)
|
||||
function has_field_mysql(Database_Abstract $db, $table, $field)
|
||||
{
|
||||
$q = $db->dq("DESCRIBE `$table`");
|
||||
while($r = $q->fetch_row()) {
|
||||
while($r = $q->fetchRow()) {
|
||||
if($r[0] == $field) return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -351,7 +352,12 @@ function testConnect(&$error)
|
|||
}
|
||||
|
||||
$db = new Database_Mysql;
|
||||
$db->connect(Config::get('mysql.host'), Config::get('mysql.user'), Config::get('mysql.password'), Config::get('mysql.db'));
|
||||
$db->connect(array(
|
||||
'host' => Config::get('mysql.host'),
|
||||
'user' => Config::get('mysql.user'),
|
||||
'password' => Config::get('mysql.password'),
|
||||
'db' => Config::get('mysql.db')
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -362,7 +368,7 @@ function testConnect(&$error)
|
|||
|
||||
require_once(MTTINC. 'class.db.sqlite3.php');
|
||||
$db = new Database_Sqlite3;
|
||||
$db->connect(MTTPATH. 'db/todolist.db');
|
||||
$db->connect( array( 'filename' => MTTPATH. 'db/todolist.db' ) );
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
$error = $e->getMessage();
|
||||
|
|
@ -380,7 +386,7 @@ function myExceptionHandler($e)
|
|||
|
||||
|
||||
### 1.1-1.2 ##########
|
||||
function update_11_12($db, $dbtype)
|
||||
function update_11_12(Database_Abstract $db, $dbtype)
|
||||
{
|
||||
if($dbtype == 'mysql') $db->ex("ALTER TABLE todolist ADD `duedate` DATE default NULL");
|
||||
else $db->ex("ALTER TABLE todolist ADD duedate DATE default NULL");
|
||||
|
|
@ -390,7 +396,7 @@ function update_11_12($db, $dbtype)
|
|||
$db->ex("DELETE FROM tags");
|
||||
$db->ex("DELETE FROM tag2task");
|
||||
$q = $db->dq("SELECT id,tags FROM todolist");
|
||||
while($r = $q->fetch_assoc())
|
||||
while($r = $q->fetchAssoc())
|
||||
{
|
||||
if($r['tags'] == '') continue;
|
||||
$tag_ids = prepare_tags($r['tags']);
|
||||
|
|
@ -427,7 +433,7 @@ function get_or_create_tag($name)
|
|||
|
||||
# need to create tag
|
||||
$db->ex("INSERT INTO tags (name) VALUES (?)", $name);
|
||||
return array($db->last_insert_id(), $name);
|
||||
return array($db->lastInsertId(), $name);
|
||||
}
|
||||
|
||||
function update_task_tags($id, $tag_ids)
|
||||
|
|
@ -442,7 +448,7 @@ function update_task_tags($id, $tag_ids)
|
|||
### end 1.1-1.2 #####
|
||||
|
||||
### 1.2-1.3 ##########
|
||||
function update_12_13($db, $dbtype)
|
||||
function update_12_13(Database_Abstract $db, $dbtype)
|
||||
{
|
||||
# update config
|
||||
Config::save();
|
||||
|
|
@ -504,7 +510,7 @@ function update_12_13($db, $dbtype)
|
|||
|
||||
|
||||
### 1.3.0 to 1.3.1 ##########
|
||||
function update_130_131($db, $dbtype)
|
||||
function update_130_131(Database_Abstract $db, $dbtype)
|
||||
{
|
||||
$tz = null;
|
||||
if(isset($_POST['tz'])) {
|
||||
|
|
@ -617,7 +623,7 @@ function update_130_131($db, $dbtype)
|
|||
### end of 1.3.0 to 1.3.1 ##########
|
||||
|
||||
### update v1.3.1 to v1.4 ##########
|
||||
function update_131_14($db, $dbtype)
|
||||
function update_131_14(Database_Abstract $db, $dbtype)
|
||||
{
|
||||
$db->ex("BEGIN");
|
||||
if($dbtype=='mysql')
|
||||
|
|
@ -697,7 +703,7 @@ function update_131_14($db, $dbtype)
|
|||
|
||||
$q = $db->dq("SELECT id,list_id,tags FROM {$db->prefix}todolist WHERE tags != ''");
|
||||
$ar = array();
|
||||
while($r = $q->fetch_assoc()) $ar[] = $r;
|
||||
while($r = $q->fetchAssoc()) $ar[] = $r;
|
||||
foreach($ar as $r)
|
||||
{
|
||||
$aTags = v14_prepareTags($r['tags']);
|
||||
|
|
@ -720,14 +726,14 @@ function update_131_14($db, $dbtype)
|
|||
# add UUID
|
||||
$q = $db->dq("SELECT id FROM {$db->prefix}todolist");
|
||||
$ar = array();
|
||||
while($r = $q->fetch_assoc()) $ar[] = $r;
|
||||
while($r = $q->fetchAssoc()) $ar[] = $r;
|
||||
foreach($ar as $r) {
|
||||
$db->ex("UPDATE {$db->prefix}todolist SET uuid=? WHERE id=".$r['id'], array(generateUUID()) );
|
||||
}
|
||||
|
||||
$q = $db->dq("SELECT id FROM {$db->prefix}lists");
|
||||
$ar = array();
|
||||
while($r = $q->fetch_assoc()) $ar[] = $r;
|
||||
while($r = $q->fetchAssoc()) $ar[] = $r;
|
||||
foreach($ar as $r) {
|
||||
$db->ex("UPDATE {$db->prefix}lists SET uuid=? WHERE id=".$r['id'], array(generateUUID()) );
|
||||
}
|
||||
|
|
@ -774,7 +780,7 @@ function v14_getOrCreateTag($name)
|
|||
if($tagId) return array('id'=>$tagId, 'name'=>$name);
|
||||
|
||||
$db->ex("INSERT INTO {$db->prefix}tags (name) VALUES (?)", array($name));
|
||||
return array('id'=>$db->last_insert_id(), 'name'=>$name);
|
||||
return array('id'=>$db->lastInsertId(), 'name'=>$name);
|
||||
}
|
||||
|
||||
function v14_addTaskTags($taskId, $tagIds, $listId)
|
||||
|
|
|
|||
Loading…
Reference in a new issue