diff --git a/.gitignore b/.gitignore index 1b0acdf..3c867f0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /temp-test /composer.lock /vendor +.scannerwork \ No newline at end of file diff --git a/Dockerfile.sample b/Dockerfile.sample index 4f4f87e..8348eac 100644 --- a/Dockerfile.sample +++ b/Dockerfile.sample @@ -1,4 +1,4 @@ -FROM mxfox.ru/chimera/fox-web-basic:latest AS prepare +FROM mxfox.ru/mxfox/fox-web-basic:latest AS prepare RUN apt-get update \ && apt-get install curl -y \ @@ -31,6 +31,6 @@ RUN rm -f composer.* \ && rm -rf docker-build -FROM mxfox.ru/chimera/fox-web-basic:latest as build +FROM mxfox.ru/mxfox/fox-web-basic:latest as build COPY --from=prepare /var/www/html /var/www/html COPY docker-build/rootfs / diff --git a/api/v2.php b/api/v2.php index dbe6164..3c28567 100644 --- a/api/v2.php +++ b/api/v2.php @@ -105,7 +105,6 @@ try { print(json_encode(["error"=>["errCode"=>500,"message"=>"Internal server error", "xCode"=>"ERR"]])); header('HTTP/1.0 500 Internal server error', true, 500); throw($e); - exit; } exit; diff --git a/cli/initialize.php b/cli/initialize.php index 697182b..0498038 100755 --- a/cli/initialize.php +++ b/cli/initialize.php @@ -26,16 +26,14 @@ $m = fox\modules::list(); $initUser=empty(config::get("INIT_USERNAME"))?"admin":config::get("INIT_USERNAME"); $initPass=empty(config::get("INIT_PASSWORD"))?"chimera":config::get("INIT_PASSWORD"); -//if (moduleInfo::getCount() ==0) { - // installing modules - foreach ($m as $mod) { - if (array_key_exists($mod->name, modules::pseudoModules) && !$mod->getInstances()) { - print "Install module ".$mod->name."..."; - $mod->save(); - print "OK\n"; - } +// installing modules +foreach ($m as $mod) { + if (array_key_exists($mod->name, modules::pseudoModules) && !$mod->getInstances()) { + print "Install module ".$mod->name."..."; + $mod->save(); + print "OK\n"; } -//} +} if (company::getCount()==0) { diff --git a/cli/installPackages.php b/cli/installPackages.php new file mode 100644 index 0000000..b788f4f --- /dev/null +++ b/cli/installPackages.php @@ -0,0 +1,56 @@ +name) && file_exists(modules::modulesDir."/".$newModInfo->name."/module.json")) { + $exModInfo=json_decode(file_get_contents(modules::modulesDir."/".$newModInfo->name."/module.json")); + if (!empty($exModInfo)) { + $modInstalled=true; + $needUpdate=(property_exists($exModInfo, "hash") && $exModInfo->hash!=$newModInfo->hash); + + } + } + print "Module ".$newModInfo->name.": ".($modInstalled?"Installed, ".($needUpdate?"Outdated":"Actual"):"Not installed").".\n"; + if ($modInstalled && $needUpdate) { + system("rm -rf \"".modules::modulesDir."/".$newModInfo->name."\""); + } + + if (!$modInstalled || $needUpdate) { + print "Install..."; + $zip = new \ZipArchive(); + $zip->open(modules::packagesDir . "/" . $file); + mkdir (modules::modulesDir."/".$newModInfo->name); + $zip->extractTo(modules::modulesDir."/".$newModInfo->name); + + if (file_exists(modules::modulesDir."/".$newModInfo->name."/fox-start.d")) { + system("chmod a+x \"".modules::modulesDir."/".$newModInfo->name."/fox-start.d/\"*"); + } + print "OK\n"; + } +} + +print "Packages installer completed\n"; diff --git a/composer.json b/composer.json index f2df9ba..6eb4a1d 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,7 @@ "require" : { "aws/aws-sdk-php" : "^3.208", "html2text/html2text" : "^4.3", - "phpmailer/phpmailer" : "~6.5.3" + "phpmailer/phpmailer" : "~6.5.3", + "firebase/php-jwt" : "~6.1" } } \ No newline at end of file diff --git a/core/fox/UID.php b/core/fox/UID.php index 09f51d0..fd20b2b 100644 --- a/core/fox/UID.php +++ b/core/fox/UID.php @@ -38,7 +38,7 @@ class UID extends baseClass implements stringExportable, stringImportable public function __get($key) { - if (! empty($this->id) && $this->__loaded == false) { + if (empty($this->id) && $this->__loaded) { $this->fill($this->id); } @@ -80,8 +80,7 @@ class UID extends baseClass implements stringExportable, stringImportable $sum2 = substr($code, 0, 1) + substr($code, 2, 1) + substr($code, 4, 1) + substr($code, 6, 1) + substr($code, 8, 1); $sum = $sum1 + $sum2; $ceil = ceil(($sum / 10)) * 10; - $delta = $ceil - $sum; - return $delta; + return $ceil - $sum; } public static $sqlTable = "tblRegistry"; diff --git a/core/fox/auth.php b/core/fox/auth.php index 039f46a..dad7e31 100644 --- a/core/fox/auth.php +++ b/core/fox/auth.php @@ -20,8 +20,7 @@ class auth extends baseClass implements noSqlMigration $sql = sql::getConnection(); $res = $sql->quickExec1Line("select * from `" . user::$sqlTable . "` where `login` = '" . common::clearInput($login) . "' and `secret` = '" . xcrypt::hash($password) . "'"); if ($res) { - $u = new user($res); - return $u; + return new user($res); } else { return false; } diff --git a/core/fox/auth/login.php b/core/fox/auth/login.php index 40d8c26..b0b85bc 100644 --- a/core/fox/auth/login.php +++ b/core/fox/auth/login.php @@ -17,6 +17,7 @@ use fox\foxException; use fox\auth; use fox\authToken; use fox\request; +use fox\foxRequestResult; class login implements externalCallable { @@ -26,7 +27,7 @@ class login implements externalCallable switch ($request->method) { case "POST": if ($request->authOK) { - return; + throw new foxRequestResult("OK",200); } if (! (gettype($request->requestBody) == "object" && property_exists($request->requestBody, "login") && property_exists($request->requestBody, "password"))) { throw new foxException("Bad request", 400); diff --git a/core/fox/auth/oauth.php b/core/fox/auth/oauth.php index 0df4164..ddc42e3 100644 --- a/core/fox/auth/oauth.php +++ b/core/fox/auth/oauth.php @@ -49,7 +49,7 @@ class oauth implements externalCallable case "POST": $profile = oAuthProfile::getByHash(common::clearInput($request->requestBody->hash)); $oac = $profile->getClient(config::get("SITEPREFIX")."/auth/oauth"); - $xTokens=$oac->getTokenByCode(common::clearInput($request->requestBody->code)); + $oac->getTokenByCode(common::clearInput($request->requestBody->code)); $userInfo=$oac->getUserInfo(); $userRefId=$profile->id.":".$userInfo->sub; $u=user::getByRefID("oauth",$userRefId); @@ -64,8 +64,9 @@ class oauth implements externalCallable "expire" => $t->expireStamp->isNull() ? "Never" : $t->expireStamp ]; - return $u; break; + default: + throw new foxException("Method not implemented",405); } } } \ No newline at end of file diff --git a/core/fox/auth/register.php b/core/fox/auth/register.php index e0297d6..d2efb83 100644 --- a/core/fox/auth/register.php +++ b/core/fox/auth/register.php @@ -49,7 +49,6 @@ class register implements externalCallable { if (!$u) { $u = new user(); - // $u->login=uniqid(); $u->eMail=$eMail; $u->authType="oauth"; $u->authRefId=$userRefId; diff --git a/core/fox/auth/session.php b/core/fox/auth/session.php index b4ba632..1cc0ceb 100644 --- a/core/fox/auth/session.php +++ b/core/fox/auth/session.php @@ -16,6 +16,7 @@ use fox\externalCallable; use fox\foxException; use fox\request; use fox\modules; +use fox\foxRequestResult; class session implements externalCallable { @@ -29,13 +30,13 @@ class session implements externalCallable } $request->token->delete(); - return; + foxRequestResult::throw(200,"Deleted"); case "GET": if ($request->authOK) { $modules=[]; $i = 0; foreach (modules::listInstalled() as $mod) { - if (array_search("menu", $mod->features) !== false && $request->user->checkAccess($mod->globalAccessKey, $mod->name) && ! empty($mod->menuItem)) { + if ($request->user->checkAccess($mod->globalAccessKey, $mod->name)) { $i ++; $modules[($mod->modPriority * 100) + $i] = [ "name" => $mod->name, @@ -54,9 +55,11 @@ class session implements externalCallable "modules" => $modules ]; } - ; throw new foxException("Unauthorized", 401); break; + default: + throw new foxException("Method not allowe", 405); + break; } } } diff --git a/core/fox/authToken.php b/core/fox/authToken.php index 0c7bb56..7d3b82b 100644 --- a/core/fox/authToken.php +++ b/core/fox/authToken.php @@ -155,9 +155,7 @@ class authToken extends baseClass } else if ($t->token1 == $token1 && $t->token2 != $token2 && $token2 != $t->token2b) { // token2 failed - token are compromised trigger_error("Token #".$t->id." are compromised!"); - // $t->delete(); - } else { - + $t->delete(); } $sql->quickExec("COMMIT"); @@ -189,7 +187,6 @@ class authToken extends baseClass $t->expireStamp = empty($expireInDays) ? (new time()) : (new time(time() + ($expireInDays * 86400))); $t->renewStamp = empty($renewTTL) ? (new time()) : (new time(time() + ($renewTTL * 60))); - ; for ($i = 0; $i < 32; $i ++) { $token = substr(preg_replace("/[-_+=\\/]/", "", base64_encode(random_bytes(64))), 0, 64); @@ -217,7 +214,6 @@ class authToken extends baseClass $renewTTL = config::get("TOKEN_RENEW_" . $this->type) === null ? static::tokenTypes[$this->type]["renewTTL"] : config::get("TOKEN_RENEW_" . $this->type); $this->renewStamp = empty($renewTTL) ? (new time()) : (new time(time() + ($renewTTL * 60))); - ; $this->token2b = $this->token2; $this->token2 = substr(preg_replace("/[-_+=\\/]/", "", base64_encode(random_bytes(64))), 0, 64); diff --git a/core/fox/baseClass.php b/core/fox/baseClass.php index 8b526c9..4f4f139 100644 --- a/core/fox/baseClass.php +++ b/core/fox/baseClass.php @@ -100,7 +100,7 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl case "object": if (! empty($val::$SQLType)) { $type = $val::$SQLType; - } elseif (($val instanceof stringExportable)) { + } elseif ($val instanceof stringExportable) { $type = "VARCHAR(255)"; } elseif ($val instanceof \JsonSerializable) { $type = "VARCHAR(255)"; @@ -134,18 +134,19 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl if ($this->sql === null) { $this->sql = sql::getConnection(); } - return; } protected function __xConstruct() - {} + { + return true; + } public function __construct($id = null, ?namespace\sql $sql = null, $prefix = null, $settings = null) { # How to call from child template: # parent::__construct($id, $sql, $prefix, $settings); $this->__settings = $settings; - if (empty($this::$sqlSelectTemplate) and ! empty($this::$sqlTable)) { + if (empty($this::$sqlSelectTemplate) && ! empty($this::$sqlTable)) { $this->__sqlSelectTemplate = "select * from `" . $this::$sqlTable . "` as `i`"; } else { $this->__sqlSelectTemplate = $this::$sqlSelectTemplate; @@ -442,7 +443,6 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl } } else { throw new \Exception("property $key not availiable for read in class " . get_class($this), 595); - break; } } } @@ -504,7 +504,7 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl if (($this->__get($key)) instanceof \JsonSerializable) { $rv[$key] = $this->__get($key); } elseif (($this->__get($key)) instanceof stringExportable) { - if (($this->__get($key)->isNull())) { + if ($this->__get($key)->isNull()) { $rv[$key] = null; } else { $rv[$key] = (string) ($this->__get($key)); @@ -558,7 +558,7 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl break; default: - continue 2; + break; } } @@ -583,9 +583,10 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl $sqlQueryString=$ref->sqlSelectTemplate.(empty($join)?"":" ".$join).(empty($where)?"":" WHERE ".$where).(empty($limit)?"":" ".$limit); $res=$sql->quickExec($sqlQueryString); - $rv=[]; + $rv=new searchResult(); + $rv->setIndexByPage($page, $pageSize); while ($row=mysqli_fetch_assoc($res)) { - $rv[]=new static($row); + $rv->push(new static($row)); } return $rv; } diff --git a/core/fox/baseModule.php b/core/fox/baseModule.php index faef24f..68755d1 100644 --- a/core/fox/baseModule.php +++ b/core/fox/baseModule.php @@ -14,10 +14,19 @@ namespace fox; class baseModule { + /** + * @deprecated - moved into module.json + */ public static $title = "Basic Module Template"; + /** + * @deprecated - moved into module.json + */ public static $desc = "Базовый класс модуля-заглушки"; + /** + * @deprecated - moved into module.json + */ public static $version = "0.0.0"; public static $type = "fake"; @@ -71,8 +80,6 @@ class baseModule public static function getModInfo(): moduleInfo { $mi = new moduleInfo(); - $mi->title = static::$title; - $mi->modVersion = static::$version; $mi->name = $mi->namespace = substr(static::class, 0, strrpos(static::class, '\\')); $mi->features = static::$features; $mi->isTemplate = true; diff --git a/core/fox/cache.php b/core/fox/cache.php index 920ab13..d431a6d 100644 --- a/core/fox/cache.php +++ b/core/fox/cache.php @@ -41,7 +41,7 @@ class cache if (empty($port)) { $port = config::get("cachePort"); } - ; + if (empty($port)) { $port = 11211; } @@ -104,8 +104,7 @@ class cache if ($xval==null || $xval=="null") { return null; } $rv= json_decode($this->mcd->get($this->prefix . "." . $key), $array); if ($rv !== null) { return $rv; } - $rv= json_decode(xcrypt::decrypt($this->mcd->get($this->prefix . "." . $key)), $array); - return $rv; + return json_decode(xcrypt::decrypt($this->mcd->get($this->prefix . "." . $key)), $array); } public function del($key) { diff --git a/core/fox/common.php b/core/fox/common.php index 2236eb6..ff9d42b 100644 --- a/core/fox/common.php +++ b/core/fox/common.php @@ -42,7 +42,6 @@ class common if (isset($_GET[$name])) { $val = preg_replace('![^' . $regex . ']+!', '', $_GET[$name]); } - ; } } else { @@ -52,10 +51,8 @@ class common } else { $val = ""; } - if ($val == "") { - if (isset($_GET[$name])) { - $val = preg_replace("![\'\"]+!", '\"', $_GET[$name]); - } + if ($val == "" && isset($_GET[$name])) { + $val = preg_replace("![\'\"]+!", '\"', $_GET[$name]); } } else { if (isset($_POST[$name])) { @@ -98,17 +95,13 @@ class common mt_srand((double) microtime() * 10000); // optional for php 4.2.0 and up. $charid = strtoupper(md5(uniqid(rand(), true))); $hyphen = chr(45); // "-" - $uuid = substr($charid, 0, 8) . $hyphen . substr($charid, 8, 4) . $hyphen . substr($charid, 12, 4) . $hyphen . substr($charid, 16, 4) . $hyphen . substr($charid, 20, 12); + return substr($charid, 0, 8) . $hyphen . substr($charid, 8, 4) . $hyphen . substr($charid, 12, 4) . $hyphen . substr($charid, 16, 4) . $hyphen . substr($charid, 20, 12); - return $uuid; } static function getGUID() { - $cUuid = getGUIDc(); - $uuid = chr(123) . // "{" - $cUuid . chr(125); // "}" - return $uuid; + return chr(123) . getGUIDc() . chr(125); } static function fullname2qname($first, $mid, $last) @@ -119,7 +112,6 @@ class common static function text2html($src) { $src = preg_replace("/[\n]/", "
", $src); - // $src=preg_replace("/[\cr\<\>]/"," ",$src); return $src; } diff --git a/core/fox/config.php b/core/fox/config.php index bb3f616..dc20020 100644 --- a/core/fox/config.php +++ b/core/fox/config.php @@ -68,7 +68,8 @@ class config extends dbStoredBase "FOX_DEFAULT_LANGUAGE", "FOX_DEFAULT_MODULE", "FOX_SESSION_RENEW_SEC", - "FOX_ALLOW_REGISTER" + "FOX_ALLOW_REGISTER", + "FOX_ENVIRONMENT", ]; static function get($key, $module = "core") diff --git a/core/fox/confirmCode.php b/core/fox/confirmCode.php index 8f431a3..c01a858 100644 --- a/core/fox/confirmCode.php +++ b/core/fox/confirmCode.php @@ -74,7 +74,7 @@ class confirmCode extends baseClass { protected function validateSave() { - if ($this->issueStamp->isNull()) {$this->issueStamp=time::current();}; + if ($this->issueStamp->isNull()) {$this->issueStamp=time::current();} if ($this->expireStamp->isNull()) {$this->expireStamp=time::current()->addSec(static::defaultTTL);} if ($this->code==null) { $this->code=(common::genPasswd(4,[0,1,2,3,4,5,6,7,8,9]));} if (empty($this->instance) || empty($this->class) || empty($this->operation) || empty($this->reference)) { diff --git a/core/fox/cronDb.php b/core/fox/cronDb.php index 8c213c3..5e96890 100644 --- a/core/fox/cronDb.php +++ b/core/fox/cronDb.php @@ -60,7 +60,7 @@ class cronDb { } $rv=[]; while ($row = $r->fetchArray(SQLITE3_ASSOC)) { - array_push($rv, $row);; + array_push($rv, $row); } return $rv; diff --git a/core/fox/errorPage.php b/core/fox/errorPage.php index 45770da..dc27a20 100644 --- a/core/fox/errorPage.php +++ b/core/fox/errorPage.php @@ -46,11 +46,6 @@ class errorPage exit(); } - $mod_name = config::get("defaultLoginModule"); - if (! $mod_name) { - $mod_name = "core"; - } - static::show($error_code, $error_desc, true); exit(); } diff --git a/core/fox/fileConverter.php b/core/fox/fileConverter.php index e16a534..8162b4f 100644 --- a/core/fox/fileConverter.php +++ b/core/fox/fileConverter.php @@ -16,7 +16,7 @@ use Exception; class fileConverter { - public static function convert($src, $dst = null, $type, $url = null) + public static function convert($src, $dst, $type, $url = null) { if (empty($url)) { $url = config::get("converterURL"); @@ -29,8 +29,6 @@ class fileConverter throw new \Exception("Invalid source file $src"); } - $boundary = '------------chimera---' . substr(md5(rand(0, 32000) . '---fox-----'), 0, 10); - // "засечка" :P $boundary = md5(rand(0, 32000)); diff --git a/core/fox/mailAccount.php b/core/fox/mailAccount.php index f08afd1..f07f94e 100644 --- a/core/fox/mailAccount.php +++ b/core/fox/mailAccount.php @@ -81,8 +81,8 @@ class mailAccount extends baseClass implements externalCallable { case "password": return xcrypt::decrypt($this->__password); case "rxLogin": return $this->login; case "rxPassword": return xcrypt::decrypt($this->__password); - case "rxLogin": return $this->login; - case "rxPassword": return xcrypt::decrypt($this->__password); + case "txLogin": return $this->login; + case "txPassword": return xcrypt::decrypt($this->__password); default: return parent::__get($key); } } @@ -161,7 +161,7 @@ class mailAccount extends baseClass implements externalCallable { throw new foxException("Forbidden", 403); } - return static::search(); + return static::search()->result; } public static function API_DELETE(request $request) { diff --git a/core/fox/mailAddress.php b/core/fox/mailAddress.php index 61fe364..222283e 100644 --- a/core/fox/mailAddress.php +++ b/core/fox/mailAddress.php @@ -23,7 +23,7 @@ class mailAddress { $this->__set("address", $address); } else { $res=[]; - $name = preg_replace('!^[\"\ \t]*|[\"\ \t]*$!', '', $name); + $name = preg_replace('!(^[\"\ \t]*)|([\"\ \t]*$)!', '', $name); if(preg_match("/([^\<\>]*) \<([^\<\>]*)\>/", $name, $res)) { $this->address=$res[2]; $this->name=$res[1]; @@ -36,7 +36,7 @@ class mailAddress { } } - $this->name = preg_replace('!^[\"]*|[\"]*$!', '', $this->name); + $this->name = preg_replace('!(^[\"]*)|([\"]*$)!', '', $this->name); } diff --git a/core/fox/mailMessage.php b/core/fox/mailMessage.php index 74104c1..41bf7e5 100644 --- a/core/fox/mailMessage.php +++ b/core/fox/mailMessage.php @@ -185,17 +185,17 @@ class mailMessage extends baseClass { $ref = explode(" ", $val); $this->refIds=[]; foreach ($ref as $ref_item) { - $ref_item=preg_replace("!^\<|\>$!", '', $ref_item); + $ref_item=preg_replace("!(^\<)|(\>$)!", '', $ref_item); array_push($this->refIds, $ref_item); } break; case "inReplyTo": - $this->inReptyTo=preg_replace("!^\<|\>$!", '', $val); + $this->inReptyTo=preg_replace("!(^\<)|(\>$)!", '', $val); break; case "messageId": - $this->messageId=preg_replace("!^\<|\>$!", '', $val); + $this->messageId=preg_replace("!(^\<)|(\>$)!", '', $val); break; case "subject": $this->__subject=base64_encode($val); @@ -301,7 +301,6 @@ class mailMessage extends baseClass { if ($this->account->txProto!='smtp' || $this->account->txServer == null) { throw new \Exception("This account can't send messages"); - return false; } $mail = new PHPMailer(true); $mail->CharSet = PHPMailer::CHARSET_UTF8; diff --git a/core/fox/meta/settings.php b/core/fox/meta/settings.php index 2f4ad2d..73b5a13 100644 --- a/core/fox/meta/settings.php +++ b/core/fox/meta/settings.php @@ -23,7 +23,7 @@ class settings implements externalCallable { public static function APICall(request $request) { - $profiles = oAuthProfile::search(); + $profiles = oAuthProfile::search()->result; $oauth=[]; foreach ($profiles as $p) { if ($p->enabled) { diff --git a/core/fox/moduleInfo.php b/core/fox/moduleInfo.php index 3993fbc..66db468 100644 --- a/core/fox/moduleInfo.php +++ b/core/fox/moduleInfo.php @@ -226,6 +226,17 @@ class moduleInfo extends baseClass implements externalCallable return $modsInstalled[$modInstanceName]; } + public static function getByFeature(string $feature) { + $rv=[]; + foreach(moduleInfo::getAll() as $mod) { + if (array_search($feature, $mod->features) !==false) { + $rv[]=$mod; + } + } + + return $rv; + } + public function getInstances() { if (! $this->isTemplate) { @@ -245,6 +256,11 @@ class moduleInfo extends baseClass implements externalCallable public static function load(string $modName) {} + public function newClass() { + $ref=$this->namespace.'\module'; + return new $ref(); + } + public function export() { $rv=parent::export(); if ($this->isTemplate) { @@ -362,7 +378,7 @@ class moduleInfo extends baseClass implements externalCallable $mod->features[]=common::clearInput($request->requestBody->feature); $mod->features=array_values($mod->features); $mod->save(); - static::log($request->instance,__FUNCTION__, "feature ".common::clearInput($request->requestBody->feature)." set for module ".$mod->name,$request->user,"module",$user->id,null,logEntry::sevInfo); + static::log($request->instance,__FUNCTION__, "feature ".common::clearInput($request->requestBody->feature)." set for module ".$mod->name,$request->user,"module",$mod->id,null,logEntry::sevInfo); foxRequestResult::throw(201, "Created"); } foxRequestResult::throw(201, "Created"); @@ -370,7 +386,7 @@ class moduleInfo extends baseClass implements externalCallable case "config": config::set(common::clearInput($request->requestBody->key), $request->requestBody->value, $mod->name); - static::log($request->instance,__FUNCTION__, "config key ".common::clearInput($request->requestBody->key)." set for module ".$mod->name,$request->user,"module",$user->id,null,logEntry::sevInfo); + static::log($request->instance,__FUNCTION__, "config key ".common::clearInput($request->requestBody->key)." set for module ".$mod->name,$request->user,"module",$mod->id,null,logEntry::sevInfo); foxRequestResult::throw(201, "Created"); break; default: diff --git a/core/fox/modules.php b/core/fox/modules.php index f3f186a..b9b974d 100644 --- a/core/fox/modules.php +++ b/core/fox/modules.php @@ -13,7 +13,9 @@ namespace fox; */ class modules implements externalCallable { - + public const modulesDir=__DIR__ . "/../../modules/"; + public const packagesDir=__DIR__ . "/../../packages/"; + // not implemented yet public const pseudoModules = [ "core" => [ @@ -138,8 +140,18 @@ class modules implements externalCallable if (array_key_exists($modName, static::pseudoModules)) { $modInfo = new moduleInfo(static::pseudoModules[$modName]); } else { - $modClass = $modName . "\module"; - $modInfo = (new $modClass())::getModInfo(); + try { + $modClass = $modName . "\module"; + $modInfo = (new $modClass())::getModInfo(); + $modDesc=json_decode(file_get_contents(static::modulesDir . "/" . $modName . "/module.json")); + if (empty($modDesc)) { throw new foxException("Unable to read module.json"); } + if ($modDesc->name != $modInfo->name) { throw new foxException("Module name mismatch for ".$modInfo->name); } + $modInfo->title=$modDesc->title; + $modInfo->modVersion=$modDesc->version; + } catch (\Exception $e) { + trigger_error($e->getMessage()); + continue; + } } $rv[$modInfo->name] = $modInfo; } @@ -152,20 +164,27 @@ class modules implements externalCallable return moduleInfo::getAll(); } + public static function getByFeature(string $feature) { + return moduleInfo::getByFeature($feature); + } + public static function scan() { $rv = []; foreach (static::pseudoModules as $key => $val) { $rv[] = $key; } - foreach (scandir(__DIR__ . "/../../modules/") as $dir) { + foreach (scandir(static::modulesDir) as $dir) { if (preg_match("/^[.]/", $dir)) { continue; } - if (! is_dir(__DIR__ . "/../../modules/" . $dir)) { + if (! is_dir(static::modulesDir . $dir)) { continue; } - if (! file_exists(__DIR__ . "/../../modules/" . $dir . "/module.php") && ! file_exists(__DIR__ . "/../../modules/" . $dir . "/Autoloader.php")) { + if (! file_exists(static::modulesDir . $dir . "/module.json")) { + continue; + } + if (! file_exists(static::modulesDir . $dir . "/module.php") && ! file_exists(static::modulesDir . $dir . "/Autoloader.php")) { continue; } $rv[] = $dir; @@ -226,7 +245,7 @@ class modules implements externalCallable $mod->modPriority=$modPriority; } $mod->save(); - static::log($request->instance,__FUNCTION__, "Module ".$mod->name." installed",$request->user,"module",$mod->id,null,logEntry::sevInfo); + logEntry::add($request->instance, static::class, __FUNCTION__, null, "Module ".$mod->name." installed", "INFO", $request->user, "module", $mod->id); foxRequestResult::throw(201, "Created", $mod); } diff --git a/core/fox/oAuthClient.php b/core/fox/oAuthClient.php index 6d80672..23a81dd 100644 --- a/core/fox/oAuthClient.php +++ b/core/fox/oAuthClient.php @@ -145,7 +145,6 @@ class oAuthClient { } else { trigger_error(json_encode($res)); throw new foxException("Invalid token"); - exit; } } diff --git a/core/fox/oAuthProfile.php b/core/fox/oAuthProfile.php index 90821ea..851229e 100644 --- a/core/fox/oAuthProfile.php +++ b/core/fox/oAuthProfile.php @@ -75,7 +75,7 @@ class oAuthProfile extends baseClass implements externalCallable { if (! $request->user->checkAccess("adminAuthMethods", "core")) { throw new foxException("Forbidden", 403); } - return static::search(); + return static::search()->result; } public static function APIX_GET_disable(request $request) { diff --git a/core/fox/searchResult.php b/core/fox/searchResult.php new file mode 100644 index 0000000..70eb827 --- /dev/null +++ b/core/fox/searchResult.php @@ -0,0 +1,31 @@ +idx = $idx; + } + + public function push($val) { + $this->result[$this->idx] = $val; + $this->idx++; + $this->count++; + } + + public function __construct($idx=null) { + if (isset($idx)) { + $this->idx = $idx; + } + } + + public function setIndexByPage($page=null, $pagesize=null) { + if ($page===null) { $page = $this->page; } + $this->idx = (($page-1)*$pagesize)+1; + } +} +?> \ No newline at end of file diff --git a/core/fox/sql.php b/core/fox/sql.php index 9d1349e..75d573b 100644 --- a/core/fox/sql.php +++ b/core/fox/sql.php @@ -109,7 +109,6 @@ class sql mysqli_set_charset($this->mysqli, "utf8"); if (! $this->mysqli) { // Если дескриптор равен 0 соединение не установлено throw new Exception("SQL Connection to $this->server failed"); - exit(); } $this->connected = true; } @@ -135,7 +134,6 @@ class sql } ; return null; - exit(); } return $result; } @@ -194,7 +192,6 @@ class sql if (! $this->stmt) { $err = 'ERR:EXEC 1P' . mysqli_errno($this->mysqli) . ' ' . mysqli_error($this->mysqli); throw new Exception($err, mysqli_errno($this->mysqli)); - exit(); } call_user_func_array(array( $this->stmt, @@ -205,7 +202,6 @@ class sql $errNo = mysqli_errno($this->mysqli); $this->stmt->close(); throw new Exception($err, $errNo); - exit(); } return true; } @@ -218,7 +214,6 @@ class sql $this->stmt->close(); } throw new \Exception('ERR:EXEC 3P' . mysqli_errno($this->mysqli) . ' ' . mysqli_error($this->mysqli), mysqli_errno($this->mysqli)); - exit(); } $this->stmt->close(); return true; @@ -232,9 +227,9 @@ class sql function paramAdd($sqlParamName, $paramValue, $setNull = false) { if ($this->queryType == "insert") { - return $this->paramAddInsert($sqlParamName, $paramValue, $setNull); + $this->paramAddInsert($sqlParamName, $paramValue, $setNull); } elseif ($this->queryType == "update") { - return $this->paramAddUpdate($sqlParamName, $paramValue, $setNull); + $this->paramAddUpdate($sqlParamName, $paramValue, $setNull); } } diff --git a/core/fox/user.php b/core/fox/user.php index 3be9a64..32efb34 100644 --- a/core/fox/user.php +++ b/core/fox/user.php @@ -305,7 +305,7 @@ class user extends baseClass implements externalCallable if (! $request->user->checkAccess("adminUsers", "core")) { throw new foxException("Forbidden", 403); } - return static::search(); + return static::search()->result; } public static function API_POST_search(request $request) { @@ -315,7 +315,7 @@ class user extends baseClass implements externalCallable $pageSize=common::clearInput($request->requestBody->pageSize,"0-9"); if (empty($pageSize)) { $pageSize=$request->user->settings["pageSize"];} - return static::search(common::clearInput($request->requestBody->pattern), $pageSize); + return static::search(common::clearInput($request->requestBody->pattern), $pageSize)->result; } @@ -330,7 +330,7 @@ class user extends baseClass implements externalCallable public static function API_GET_sendEMailConfirmation(request $request) { $request->user->sendEMailConfirmation(); - static::log($request->instance,__FUNCTION__, "mailConfirmation sent for user ".$user->login,$request->user,"user",$user->id,null,logEntry::sevInfo); + static::log($request->instance,__FUNCTION__, "mailConfirmation sent for user ".$request->user->login,$request->user,"user",$request->user->id,null,logEntry::sevInfo); } public static function API_POST_validateEMailCode(request $request) { @@ -341,21 +341,6 @@ class user extends baseClass implements externalCallable foxException::throw("ERR", "Validation failed", 400); } } - - public static function APICall(request $request) { - if (!empty($request->function)) { - throw new foxException("Method not allowed",405); - } - - switch ($request->method) { - - default: - throw new foxException("Method not allowed",405); - } - - } - - } ?> \ No newline at end of file diff --git a/core/fox/userGroup.php b/core/fox/userGroup.php index f1f6ad7..d3e050c 100644 --- a/core/fox/userGroup.php +++ b/core/fox/userGroup.php @@ -163,7 +163,7 @@ class userGroup extends baseClass implements externalCallable if (! $request->user->checkAccess("adminUserGroups", "core")) { throw new foxException("Forbidden", 403); } - return static::search(); + return static::search()->result; } public static function API_POST_members(request $request) @@ -223,7 +223,7 @@ class userGroup extends baseClass implements externalCallable case "PUT": $grName=common::clearInput($request->requestBody->name); - $groups = userGroup::search($grName); + $groups = userGroup::search($grName)->result; foreach ($groups as $group) { if (trim(strtolower($group->name))==trim(strtolower($grName))) { foxException::throw("ERR", "Already exists", 409, "GAX"); diff --git a/core/fox/userGroupMembership.php b/core/fox/userGroupMembership.php index 26eaa74..d212bf8 100644 --- a/core/fox/userGroupMembership.php +++ b/core/fox/userGroupMembership.php @@ -158,7 +158,7 @@ class userGroupMembership extends baseClass implements externalCallable $res=static::search(null,$pageSize,$page,[ "user"=>$user, "group"=>$group, - ]); + ])->result; $rv=[]; foreach ($res as $ugm) { diff --git a/core/fox/userInvitation.php b/core/fox/userInvitation.php index aa8932a..abf65fb 100644 --- a/core/fox/userInvitation.php +++ b/core/fox/userInvitation.php @@ -118,7 +118,7 @@ class userInvitation extends baseClass implements externalCallable { } public static function API_GET_list(request $request) { - return static::search(); + return static::search()->result; } public static function APIX_GET_reSend(request $request) { diff --git a/docker-build/Dockerfile b/docker-build/Dockerfile index b51e8cb..63b5f6c 100644 --- a/docker-build/Dockerfile +++ b/docker-build/Dockerfile @@ -1,3 +1,3 @@ -FROM mxfox.ru/chimera/fox-web-basic:latest +FROM mxfox.ru/mxfox/fox-web-basic:latest COPY . /var/www/html COPY docker-build/rootfs / diff --git a/error.php b/error.php index a534a8e..66a1277 100644 --- a/error.php +++ b/error.php @@ -35,10 +35,11 @@ if (array_search($req[1], $jsons)!==false) { } ?> + + +Error <?php print $code; ?> - - - -
+
@@ -56,7 +57,6 @@ if (array_search($req[1], $jsons)!==false) { border: 2px solid red; background-color: rgba(0, 0, 0, 0.85); - margin: 0; padding: 70 0 0 0; position: absolute; left: calc(50% - 250px); @@ -68,7 +68,9 @@ if (array_search($req[1], $jsons)!==false) { vertical-align: center; font-family: 'Fira Mono', monospace; font-weight: 200; - font-size: 32px; color: #FF3500; margin: 1%"> + font-size: 32px; + color: #FF3500; + margin: 1%"> "; if (array_key_exists($code, $codes)) { diff --git a/fox-cron.d/cron.php b/fox-cron.d/cron.php index ac4feaf..47924f3 100755 --- a/fox-cron.d/cron.php +++ b/fox-cron.d/cron.php @@ -14,7 +14,6 @@ if (php_sapi_name() != 'cli') { throw new Exception("This script can be run via CLI only"); - exit; } use fox\moduleInfo; @@ -134,7 +133,7 @@ while(($xpid=pcntl_wait($status,WNOHANG)) >=0) { foreach($pids as $pid=>&$ttl) { if ($ttl < $ttlc) { print "Kill PID ".$pid." by TTL\n"; - var_dump(posix_kill($pid, SIGKILL)); + posix_kill($pid, SIGKILL); $ttl+=5; } } diff --git a/fox-start.d/10_migration.sh b/fox-start.d/10_migration.sh index 3fc4474..923a6f5 100755 --- a/fox-start.d/10_migration.sh +++ b/fox-start.d/10_migration.sh @@ -1,4 +1,5 @@ #!/bin/bash +php /var/www/html/cli/installPackages.php php /var/www/html/cli/migration.php php /var/www/html/cli/initialize.php diff --git a/index.php b/index.php index c0d7cf0..cdb438a 100644 --- a/index.php +++ b/index.php @@ -1,5 +1,5 @@ - + Chimera Fox diff --git a/modules/README.md b/modules/README.md index 3ad0c61..0800222 100644 --- a/modules/README.md +++ b/modules/README.md @@ -1,4 +1,4 @@ # Modules Folder This folder must be empty and don't used for any data except modules subfolders. Modules must compliant FoxAPImk2. -Modules may be added as mapped-folder for docker containers or prebuilt in it. \ No newline at end of file +Modules may be prebuilt in docker image or placed as packages into `packages` folder - it will be unpacked automatically while startup procedure. \ No newline at end of file diff --git a/packages/README.md b/packages/README.md new file mode 100644 index 0000000..dd7d064 --- /dev/null +++ b/packages/README.md @@ -0,0 +1,4 @@ +# Packages Folder +This folder must be empty and don't used for any data except modules packages. +Modules must compliant FoxAPImk2. +Modules will be unpacked automatically while startup procedure. \ No newline at end of file diff --git a/static/.htaccess b/static/.htaccess index 65fd03a..2a23bd5 100644 --- a/static/.htaccess +++ b/static/.htaccess @@ -2,7 +2,7 @@ RewriteEngine On RewriteCond %{REQUEST_FILENAME} -f -RewriteRule . - [S=5] +RewriteRule . - [S=6] RewriteRule "^theme/([^/]+)/main.css" "/modules/$1/theme/main.css" [L] RewriteRule "^theme/([^/]+)/img/([0-9a-zA-Z\.\-\_]*)" "/modules/$1/theme/img/$2" [L] @@ -10,3 +10,4 @@ RewriteRule "^theme/([^/]+)/font/([0-9a-zA-Z\.\-\_]*)" "/modules/$1/theme/font/$ RewriteRule "^theme/([^/]+)/css/([0-9a-zA-Z\.\-\_]*\.css)" "/modules/$1/theme/css/$2" [L] RewriteRule "^js/([^/]+)/([0-9a-zA-Z\.\-\_]*\.js)" "/modules/$1/js/$2" [L] +RewriteRule "^css/([^/]+)/([0-9a-zA-Z\.\-\_]*\.css)" "/modules/$1/css/$2" [L] diff --git a/static/js/core/coreModules.js b/static/js/core/coreModules.js index 8d448a5..e294b79 100644 --- a/static/js/core/coreModules.js +++ b/static/js/core/coreModules.js @@ -106,7 +106,7 @@ function moduleInstall_Click(el) { UI.createDialog( UI.addFieldGroup([ - UI.addField({item: "mod_name", title: langPack.core.iface.title, type: "input", reqx: "true", reqx: "true", regx: "^[A-Za-z0-9_-]*$", val: $(el.target).closest("tr").attr("modName")}), + UI.addField({item: "mod_name", title: langPack.core.iface.title, type: "input", reqx: "true", regx: "^[A-Za-z0-9_-]*$", val: $(el.target).closest("tr").attr("modName")}), UI.addField({item: "mod_priority", title: langPack.core.iface.modules.priority, type: "input", reqx: "true", regx: "^[0-9]*$", val: 100}), ]), langPack.core.iface.dialodAddButton, diff --git a/static/js/core/lang_ru.js b/static/js/core/lang_ru.js index d1c6240..a701f62 100644 --- a/static/js/core/lang_ru.js +++ b/static/js/core/lang_ru.js @@ -3,6 +3,7 @@ export var langItem={ modName: "core", err0: "Ошибка загрузки данных", err1: "Ошибка", + emptyTableText: "Здесь ничего нет. Совсем ничего. Если это ошибка - попробуйте добавить вручную.", errReqx: "Обязательное поле", errRegx: "Несовпадение формата", ok0: "Операция завершена успешно", @@ -34,7 +35,6 @@ export var langItem={ add: "Добавить", open: "Открыть", dialodAddButton: "Добавить", - dialodDelButton: "Удалить", dialodCreateButton: "Создать", dialodInfoTitle: "Информация", dialogRegisterButton: "Регистрация", @@ -109,7 +109,6 @@ export var langItem={ allTitle: "Пользователи", regCode: "Код регистрации", invitesTitle: "Приглашения", - regCode: "Код приглашения", inviteButtonTitle: "Пригласить", allowMultiUseTitle: "Разрешить множественное использование", allowMultiUseQTitle: "Множеств.", diff --git a/static/js/core/main.js b/static/js/core/main.js index 744411e..1de8ee1 100644 --- a/static/js/core/main.js +++ b/static/js/core/main.js @@ -79,7 +79,11 @@ export function boot(xlite) { } export function smartClick(ref) { - UI.setLocation($(ref.currentTarget).prop("href")); + if(typeof(ref)=="string") { + UI.setLocation(ref); + } else { + UI.setLocation($(ref.currentTarget).prop("href")); + } boot(true); return false; } diff --git a/static/js/core/ui.js b/static/js/core/ui.js index e4b5fad..da3ee04 100644 --- a/static/js/core/ui.js +++ b/static/js/core/ui.js @@ -13,7 +13,7 @@ var codes={ }; export function showError(code, message) { - if (message==undefined) { message=codes[code]}; + if (message==undefined) { message=codes[code]} if (message==undefined) { message="Internal server error"} $("
", { class: "error blanker bggray"}).appendTo("body"); $("
", { class: "error error_banner",html: "ERROR "+code+"
"+message}).appendTo("body"); @@ -28,9 +28,9 @@ export function click (ref) { } export function addButton(id, icon, title, color, onClick, cssClass, style) { - + var opts; if (typeof(id)=='object') { - var opts=id; + opts=id; id=opts.id; icon=opts.icon; title=opts.title; @@ -191,7 +191,7 @@ export function createTabsPanel(panels,ref) { $.each(panels,function (index,panel) { if (panel.id==undefined) {panel.id=index;} - $("
  • ",{append: $("",{href: "#tab-"+panel.id, text: panel.title})}) + $("
  • ",{append: $("",{href: "#tab-"+panel.id, id: "a-tab-"+panel.id, text: panel.title})}) .appendTo("#item_tabs_ul_list"); $("
    ", { id: "tab-"+panel.id, @@ -262,12 +262,13 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, let item_id=ref.item; let args = ref.args; - if (ref.args==undefined) { + if (ref.args==undefined && ref.val != undefined) { args = ref.val; + } else { + args={}; } switch(type) { - case 'password': - + case 'password': item = $("", {class: "i", id: ref.item, name: name, type:'password',width: 'calc(100% - 44px)'}) .add($("
    ",{class: "button short", style: "width: 25px; margin-right: 0; margin-left: 2; padding: 0; padding-top: 1; font-size: 13px;",append: $("",{class: 'far fa-eye'}) }) @@ -282,6 +283,13 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, })); break case 'passwordNew': + if (args.chars==undefined) { + switch(args.type) { + case "num": + args.chars="0123456789"; + } + } + item = $("", {class: "i", id: ref.item, name: name, width: 'calc(100% - 44px)'}) .add($("
    ",{ class: "button short", @@ -292,7 +300,7 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, if ($("#"+item_id).prop("disabled")) { return; } - let password=genPassword(); + let password=genPassword(args.chars, args.length); $("#"+item_id).val(password); $("#"+item_id).change(); if (typeof(ref.newPasswdGenCallback)=="function") { @@ -594,6 +602,16 @@ export function collectForm(formid, getall, withIDS, withREF, validate) } catch { } + data.getVals=function() { + let rv={}; + $.each(data,function(idx,val) { + if (typeof(val) =='object') { + rv[idx]=val.val; + } + }); + + return rv; + } return data; } @@ -661,13 +679,13 @@ export function on_valChanged(t_this) { export function progressBar_init() { $('.progressbar').each(function() { - el = $(this); - val = parseInt(el.attr('value')); - - el.progressbar({ - value: 0 - }); - progressBar_update(el, val); + let el = $(this); + let val = parseInt(el.attr('value')); + + el.progressbar({ + value: 0 + }); + progressBar_update(el, val); }); } @@ -990,7 +1008,7 @@ Number.prototype.pad = function(size) { } if (mode=='getPage') { - return sessionStorage.getItem($(this).prop('foxPager_prefix')+"pager");c + return sessionStorage.getItem($(this).prop('foxPager_prefix')+"pager"); } this.each(function(rid,ref) { @@ -1070,7 +1088,7 @@ Number.prototype.pad = function(size) { }).appendTo(ref); } else if (mode=='update') { - prefix = $(ref).prop("foxPager_prefix"); + let prefix = $(ref).prop("foxPager_prefix"); sessionStorage.setItem(prefix+"pager", options.page); $(ref).prop('foxPager_page',options.page); $(ref).prop('foxPager_pages',options.pages); diff --git a/static/js/core/userMailConfirm.js b/static/js/core/userMailConfirm.js index 44bec4e..c88eb12 100644 --- a/static/js/core/userMailConfirm.js +++ b/static/js/core/userMailConfirm.js @@ -37,7 +37,6 @@ function doValidation(code) { code: code, }, method: "core/user/validateEMailCode", - errDict: langPack.core.iface.users.errors, onSuccess: function(json) { UI.closeDialog('addgrp'); diff --git a/static/theme/core/css/jquery-ui.theme.css b/static/theme/core/css/jquery-ui.theme.css index 37d3146..8ae5305 100644 --- a/static/theme/core/css/jquery-ui.theme.css +++ b/static/theme/core/css/jquery-ui.theme.css @@ -78,7 +78,7 @@ a.ui-button, a:link.ui-button, a:visited.ui-button, .ui-button { - color: #aaaaa; + color: #aaaaaa; text-decoration: none; } .ui-state-hover, diff --git a/static/theme/core/main.css b/static/theme/core/main.css index 53e709c..31823f5 100644 --- a/static/theme/core/main.css +++ b/static/theme/core/main.css @@ -243,7 +243,6 @@ div.header_user border: 2px solid red; background-color: rgba(0, 0, 0, 0.85); - margin: 0; padding: 70px 0 0 0; position: absolute; left: calc(50% - 250px); @@ -254,7 +253,9 @@ div.header_user height: 150px; font-family: 'Fira Mono', monospace; font-weight: 200; - font-size: 32px; color: #FF3500; margin: 1%; + font-size: 32px; + color: #FF3500; + margin: 1%; z-index: 200; } @@ -403,7 +404,6 @@ div.t_navy_main, div.t_main { text-align: left; vertical-align: top; - padding-top: 0px; padding: 10px 7px 10px 7px; } @@ -674,7 +674,7 @@ li.xmenu { margin-left: 14px; } -t_navy_main .xmenu p { +.xmenu p { margin-left: 5px; } @@ -1437,7 +1437,6 @@ div.alert, p.alert top: 50%; margin-left: -350px; margin-top: -25px; - font-weight: bold; vertical-align: middle; padding-top: 10px; color: white; @@ -1590,7 +1589,6 @@ div.chevron.first .button div.login { - width: 300px; position: absolute; left: 50%; top: 50%; @@ -1672,7 +1670,7 @@ div.dialog textarea table.datatable td.code { - font-family: 'Share Tech Mono'; + font-family: Share Tech Mono, sans-serif; font-size: 16px; color: rgba(255,255,255,0.8); } @@ -1743,7 +1741,6 @@ div.accordion .ui-accordion-header.ui-state-active { /* accordion and tabs */ div.accordion .ui-accordion-header { - border-radius: 5px; background-color: rgba(var(--mxs-bg-rgba-prefix),0.7); font-family: 'Fira Sans Extra Condensed', sans-serif; font-weight: normal;