Merge branch 'develop' of https://github.com/friendica/friendica into develop

This commit is contained in:
Friendica 2018-04-19 19:24:35 +00:00
commit dc4b384414
15 changed files with 610 additions and 154 deletions

View file

@ -30,6 +30,7 @@
"bower-asset/base64": "^1.0",
"bower-asset/Chart-js": "^2.7",
"bower-asset/perfect-scrollbar": "^0.6",
"bower-asset/vue": "^2.5",
"npm-asset/jquery": "^2.0",
"npm-asset/jquery-colorbox": "^1.6",
"npm-asset/jquery-datetimepicker": "^2.4.0",

18
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "12b8df66213734281765cb6e2c5a786e",
"content-hash": "96062c2020a40f14b52e5e91c79995a7",
"packages": [
{
"name": "asika/simple-console",
@ -133,6 +133,22 @@
"description": "Minimalistic but perfect custom scrollbar plugin",
"time": "2017-01-10T01:04:09+00:00"
},
{
"name": "bower-asset/vue",
"version": "v2.5.16",
"source": {
"type": "git",
"url": "https://github.com/vuejs/vue.git",
"reference": "25342194016dc3bcc81cb3e8e229b0fb7ba1d1d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vuejs/vue/zipball/25342194016dc3bcc81cb3e8e229b0fb7ba1d1d6",
"reference": "25342194016dc3bcc81cb3e8e229b0fb7ba1d1d6",
"shasum": ""
},
"type": "bower-asset-library"
},
{
"name": "divineomega/password_exposed",
"version": "v2.5.1",

View file

@ -668,33 +668,7 @@ function conversation(App $a, $items, $mode, $update, $preview = false, $order =
$profile_name = $item['author-link'];
}
$tags = [];
$hashtags = [];
$mentions = [];
$searchpath = System::baseUrl()."/search?tag=";
$taglist = dba::select('term', ['type', 'term', 'url'],
["`otype` = ? AND `oid` = ? AND `type` IN (?, ?)", TERM_OBJ_POST, $item['id'], TERM_HASHTAG, TERM_MENTION],
['order' => ['tid']]);
while ($tag = dba::fetch($taglist)) {
if ($tag["url"] == "") {
$tag["url"] = $searchpath . strtolower($tag["term"]);
}
$tag["url"] = best_link_url($item, $sp, $tag["url"]);
if ($tag["type"] == TERM_HASHTAG) {
$hashtags[] = "#<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
$prefix = "#";
} elseif ($tag["type"] == TERM_MENTION) {
$mentions[] = "@<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
$prefix = "@";
}
$tags[] = $prefix."<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
}
dba::close($taglist);
$tags = \Friendica\Model\Term::populateTagsFromItem($item);
$sp = false;
$profile_link = best_link_url($item, $sp);
@ -764,9 +738,9 @@ function conversation(App $a, $items, $mode, $update, $preview = false, $order =
}
$body_e = $body;
$tags_e = $tags;
$hashtags_e = $hashtags;
$mentions_e = $mentions;
$tags_e = $tags['tags'];
$hashtags_e = $tags['hashtags'];
$mentions_e = $tags['mentions'];
$location_e = $location;
$owner_name_e = $owner_name;

View file

@ -405,12 +405,21 @@ function get_form_security_token($typename = '')
function check_form_security_token($typename = '', $formname = 'form_security_token')
{
if (!x($_REQUEST, $formname)) {
return false;
}
$hash = null;
if (!empty($_REQUEST[$formname])) {
/// @TODO Careful, not secured!
$hash = $_REQUEST[$formname];
}
if (!empty($_SERVER['HTTP_X_CSRF_TOKEN'])) {
/// @TODO Careful, not secured!
$hash = $_SERVER['HTTP_X_CSRF_TOKEN'];
}
if (empty($hash)) {
return false;
}
$max_livetime = 10800; // 3 hours

View file

@ -1234,12 +1234,6 @@ function prepare_body(array &$item, $attach = false, $is_preview = false)
$a = get_app();
Addon::callHooks('prepare_body_init', $item);
$searchpath = System::baseUrl() . "/search?tag=";
$tags = [];
$hashtags = [];
$mentions = [];
// In order to provide theme developers more possibilities, event items
// are treated differently.
if ($item['object-type'] === ACTIVITY_OBJ_EVENT && isset($item['event-id'])) {
@ -1247,37 +1241,11 @@ function prepare_body(array &$item, $attach = false, $is_preview = false)
return $ev;
}
$taglist = dba::p("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = ? AND `oid` = ? AND `type` IN (?, ?) ORDER BY `tid`",
intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
$tags = \Friendica\Model\Term::populateTagsFromItem($item);
while ($tag = dba::fetch($taglist)) {
if ($tag["url"] == "") {
$tag["url"] = $searchpath . strtolower($tag["term"]);
}
$orig_tag = $tag["url"];
$tag["url"] = best_link_url($item, $sp, $tag["url"]);
if ($tag["type"] == TERM_HASHTAG) {
if ($orig_tag != $tag["url"]) {
$item['body'] = str_replace($orig_tag, $tag["url"], $item['body']);
}
$hashtags[] = "#<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
$prefix = "#";
} elseif ($tag["type"] == TERM_MENTION) {
$mentions[] = "@<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
$prefix = "@";
}
$tags[] = $prefix . "<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
}
dba::close($taglist);
$item['tags'] = $tags;
$item['hashtags'] = $hashtags;
$item['mentions'] = $mentions;
$item['tags'] = $tags['tags'];
$item['hashtags'] = $tags['hashtags'];
$item['mentions'] = $tags['mentions'];
// Compile eventual content filter reasons
$filter_reasons = [];

View file

@ -50,7 +50,7 @@ class MemcachedCacheDriver extends BaseObject implements ICacheDriver
{
// We store with the hostname as key to avoid problems with other applications
return $this->memcached->set(
self::getApp()->get_hostname() . ":" . $key,
self::getApp()->get_hostname() . ':' . $key,
$value,
time() + $duration
);
@ -58,7 +58,9 @@ class MemcachedCacheDriver extends BaseObject implements ICacheDriver
public function delete($key)
{
return $this->memcached->delete($key);
$return = $this->memcached->delete(self::getApp()->get_hostname() . ':' . $key);
return $return;
}
public function clear()

View file

@ -21,6 +21,7 @@ class Console extends \Asika\SimpleConsole\Console
'extract' => __NAMESPACE__ . '\Console\Extract',
'globalcommunityblock' => __NAMESPACE__ . '\Console\GlobalCommunityBlock',
'globalcommunitysilence' => __NAMESPACE__ . '\Console\GlobalCommunitySilence',
'autoinstall' => __NAMESPACE__ . '\Console\AutomaticInstallation',
'maintenance' => __NAMESPACE__ . '\Console\Maintenance',
'newpassword' => __NAMESPACE__ . '\Console\NewPassword',
'php2po' => __NAMESPACE__ . '\Console\PhpToPo',
@ -42,6 +43,7 @@ Commands:
globalcommunityblock Block remote profile from interacting with this node
globalcommunitysilence Silence remote profile from global community page
help Show help about a command, e.g (bin/console help config)
autoinstall Starts automatic installation of friendica based on values from htconfig.php
maintenance Set maintenance mode for this node
newpassword Set a new password for a given user
php2po Generate a messages.po file from a strings.php file

View file

@ -0,0 +1,164 @@
<?php
namespace Friendica\Core\Console;
use Asika\SimpleConsole\Console;
use dba;
use Friendica\App;
require_once 'mod/install.php';
require_once 'include/dba.php';
class AutomaticInstallation extends Console
{
protected function getHelp()
{
return <<<HELP
Installation - Install Friendica automatically
Synopsis
bin/console autoinstall [-h|--help|-?] [-v] [-a]
Description
Installs Friendica with data based on the htconfig.php file
Notes:
Not checking .htaccess/URL-Rewrite during CLI installation.
Options
-h|--help|-? Show help information
-v Show more debug information.
-a All setup checks are required (except .htaccess)
HELP;
}
protected function doExecute()
{
// Initialise the app
$this->out("Initializing setup...\n");
$a = get_app();
$db_host = '';
$db_user = '';
$db_pass = '';
$db_data = '';
require_once 'htconfig.php';
$this->out(" Complete!\n\n");
// Check basic setup
$this->out("Checking basic setup...\n");
$checkResults = [];
$checkResults['basic'] = $this->runBasicChecks($a);
$errorMessage = $this->extractErrors($checkResults['basic']);
if ($errorMessage !== '') {
throw new \RuntimeException($errorMessage);
}
$this->out(" Complete!\n\n");
// Check database connection
$this->out("Checking database...\n");
$checkResults['db'] = array();
$checkResults['db'][] = $this->runDatabaseCheck($db_host, $db_user, $db_pass, $db_data);
$errorMessage = $this->extractErrors($checkResults['db']);
if ($errorMessage !== '') {
throw new \RuntimeException($errorMessage);
}
$this->out(" Complete!\n\n");
// Install database
$this->out("Inserting data into database...\n");
$checkResults['data'] = load_database();
if ($checkResults['data'] !== '') {
throw new \RuntimeException("ERROR: DB Database creation error. Is the DB empty?\n");
}
$this->out(" Complete!\n\n");
// Copy config file
$this->out("Saving config file...\n");
if (!copy('htconfig.php', '.htconfig.php')) {
throw new \RuntimeException("ERROR: Saving config file failed. Please copy .htautoinstall.php to .htconfig.php manually.\n");
}
$this->out(" Complete!\n\n");
$this->out("\nInstallation is finished\n");
return 0;
}
/**
* @param App $app
* @return array
*/
private function runBasicChecks($app)
{
$checks = [];
check_funcs($checks);
check_imagik($checks);
check_htconfig($checks);
check_smarty3($checks);
check_keys($checks);
if (!empty($app->config['php_path'])) {
check_php($app->config['php_path'], $checks);
} else {
throw new \RuntimeException(" ERROR: The php_path is not set in the config. Please check the file .htconfig.php.\n");
}
$this->out(" NOTICE: Not checking .htaccess/URL-Rewrite during CLI installation.\n");
return $checks;
}
/**
* @param $db_host
* @param $db_user
* @param $db_pass
* @param $db_data
* @return array
*/
private function runDatabaseCheck($db_host, $db_user, $db_pass, $db_data)
{
$result = array(
'title' => 'MySQL Connection',
'required' => true,
'status' => true,
'help' => '',
);
if (!dba::connect($db_host, $db_user, $db_pass, $db_data, true)) {
$result['status'] = false;
$result['help'] = 'Failed, please check your MySQL settings and credentials.';
}
return $result;
}
/**
* @param array $results
* @return string
*/
private function extractErrors($results)
{
$errorMessage = '';
$allChecksRequired = $this->getOption('a') !== null;
foreach ($results as $result) {
if (($allChecksRequired || $result['required'] === true) && $result['status'] === false) {
$errorMessage .= "--------\n";
$errorMessage .= $result['title'] . ': ' . $result['help'] . "\n";
}
}
return $errorMessage;
}
}

View file

@ -24,7 +24,7 @@ class CacheSessionHandler extends BaseObject implements SessionHandlerInterface
public function read($session_id)
{
if (!x($session_id)) {
if (empty($session_id)) {
return '';
}
@ -58,9 +58,9 @@ class CacheSessionHandler extends BaseObject implements SessionHandlerInterface
return true;
}
Cache::set('session:' . $session_id, $session_data, Session::$expire);
$return = Cache::set('session:' . $session_id, $session_data, Session::$expire);
return true;
return $return;
}
public function close()
@ -70,8 +70,9 @@ class CacheSessionHandler extends BaseObject implements SessionHandlerInterface
public function destroy($id)
{
Cache::delete('session:' . $id);
return true;
$return = Cache::delete('session:' . $id);
return $return;
}
public function gc($maxlifetime)

View file

@ -1803,6 +1803,8 @@ class DBStructure
]
];
\Friendica\Core\Addon::callHooks('dbstructure_definition', $database);
return $database;
}
}

View file

@ -9,6 +9,7 @@ use Friendica\Database\DBM;
use dba;
require_once 'boot.php';
require_once 'include/conversation.php';
require_once 'include/dba.php';
class Term
@ -168,4 +169,56 @@ class Term
}
}
}
/**
* Sorts an item's tags into mentions, hashtags and other tags. Generate personalized URLs by user and modify the
* provided item's body with them.
*
* @param array $item
* @return array
*/
public static function populateTagsFromItem(&$item)
{
$return = [
'tags' => [],
'hashtags' => [],
'mentions' => [],
];
$searchpath = System::baseUrl() . "/search?tag=";
$taglist = dba::select(
'term',
['type', 'term', 'url'],
["`otype` = ? AND `oid` = ? AND `type` IN (?, ?)", TERM_OBJ_POST, $item['id'], TERM_HASHTAG, TERM_MENTION],
['order' => ['tid']]
);
while ($tag = dba::fetch($taglist)) {
if ($tag["url"] == "") {
$tag["url"] = $searchpath . strtolower($tag["term"]);
}
$orig_tag = $tag["url"];
$tag["url"] = best_link_url($item, $sp, $tag["url"]);
if ($tag["type"] == TERM_HASHTAG) {
if ($orig_tag != $tag["url"]) {
$item['body'] = str_replace($orig_tag, $tag["url"], $item['body']);
}
$return['hashtags'][] = "#<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
$prefix = "#";
} elseif ($tag["type"] == TERM_MENTION) {
$return['mentions'][] = "@<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
$prefix = "@";
}
$return['tags'][] = $prefix . "<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
}
dba::close($taglist);
return $return;
}
}

View file

@ -1528,36 +1528,29 @@ class Diaspora
*/
private static function plink($addr, $guid, $parent_guid = '')
{
$r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr));
$contact = Contact::getDetailsByAddr($addr);
// Fallback
if (!DBM::is_result($r)) {
if (!$contact) {
if ($parent_guid != '') {
return "https://".substr($addr, strpos($addr, "@") + 1) . "/posts/" . $parent_guid . "#" . $guid;
return "https://" . substr($addr, strpos($addr, "@") + 1) . "/posts/" . $parent_guid . "#" . $guid;
} else {
return "https://".substr($addr, strpos($addr, "@") + 1) . "/posts/" . $guid;
return "https://" . substr($addr, strpos($addr, "@") + 1) . "/posts/" . $guid;
}
}
// Friendica contacts are often detected as Diaspora contacts in the "fcontact" table
// So we try another way as well.
$s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"])));
if (DBM::is_result($s)) {
$r[0]["network"] = $s[0]["network"];
if ($contact["network"] == NETWORK_DFRN) {
return str_replace("/profile/" . $contact["nick"] . "/", "/display/" . $guid, $contact["url"] . "/");
}
if ($r[0]["network"] == NETWORK_DFRN) {
return str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/");
}
if (self::isRedmatrix($r[0]["url"])) {
return $r[0]["url"]."/?f=&mid=".$guid;
if (self::isRedmatrix($contact["url"])) {
return $contact["url"] . "/?f=&mid=" . $guid;
}
if ($parent_guid != '') {
return "https://".substr($addr, strpos($addr, "@") + 1) . "/posts/" . $parent_guid . "#" . $guid;
return "https://" . substr($addr, strpos($addr, "@") + 1) . "/posts/" . $parent_guid . "#" . $guid;
} else {
return "https://".substr($addr, strpos($addr, "@") + 1) . "/posts/" . $guid;
return "https://" . substr($addr, strpos($addr, "@") + 1) . "/posts/" . $guid;
}
}
@ -2744,35 +2737,33 @@ class Diaspora
*
* @return array The fetched item
*/
private static function originalItem($guid, $orig_author, $author)
public static function originalItem($guid, $orig_author)
{
// Do we already have this item?
$r = q(
"SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`,
`author-name`, `author-link`, `author-avatar`
FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
dbesc($guid)
);
$fields = ['body', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
'author-name', 'author-link', 'author-avatar'];
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false];
$item = dba::selectfirst('item', $fields, $condition);
if (DBM::is_result($r)) {
if (DBM::is_result($item)) {
logger("reshared message ".$guid." already exists on system.");
// Maybe it is already a reshared item?
// Then refetch the content, if it is a reshare from a reshare.
// If it is a reshared post from another network then reformat to avoid display problems with two share elements
if (self::isReshare($r[0]["body"], true)) {
if (self::isReshare($item["body"], true)) {
$r = [];
} elseif (self::isReshare($r[0]["body"], false) || strstr($r[0]["body"], "[share")) {
$r[0]["body"] = Markdown::toBBCode(BBCode::toMarkdown($r[0]["body"]));
} elseif (self::isReshare($item["body"], false) || strstr($item["body"], "[share")) {
$item["body"] = Markdown::toBBCode(BBCode::toMarkdown($item["body"]));
$r[0]["body"] = self::replacePeopleGuid($r[0]["body"], $r[0]["author-link"]);
$item["body"] = self::replacePeopleGuid($item["body"], $item["author-link"]);
// Add OEmbed and other information to the body
$r[0]["body"] = add_page_info_to_body($r[0]["body"], false, true);
$item["body"] = add_page_info_to_body($item["body"], false, true);
return $r[0];
return $item;
} else {
return $r[0];
return $item;
}
}
@ -2788,21 +2779,19 @@ class Diaspora
}
if ($item_id) {
$r = q(
"SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`,
`author-name`, `author-link`, `author-avatar`
FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
intval($item_id)
);
$fields = ['body', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
'author-name', 'author-link', 'author-avatar'];
$condition = ['id' => $item_id, 'visible' => true, 'deleted' => false];
$item = dba::selectfirst('item', $fields, $condition);
if (DBM::is_result($r)) {
if (DBM::is_result($item)) {
// If it is a reshared post from another network then reformat to avoid display problems with two share elements
if (self::isReshare($r[0]["body"], false)) {
$r[0]["body"] = Markdown::toBBCode(BBCode::toMarkdown($r[0]["body"]));
$r[0]["body"] = self::replacePeopleGuid($r[0]["body"], $r[0]["author-link"]);
if (self::isReshare($item["body"], false)) {
$item["body"] = Markdown::toBBCode(BBCode::toMarkdown($item["body"]));
$item["body"] = self::replacePeopleGuid($item["body"], $item["author-link"]);
}
return $r[0];
return $item;
}
}
}
@ -2838,7 +2827,7 @@ class Diaspora
return true;
}
$original_item = self::originalItem($root_guid, $root_author, $author);
$original_item = self::originalItem($root_guid, $root_author);
if (!$original_item) {
return false;
}
@ -3556,24 +3545,21 @@ class Diaspora
// Skip if it isn't a pure repeated messages
// Does it start with a share?
if ((strpos($body, "[share") > 0) && $complete) {
return(false);
return false;
}
// Does it end with a share?
if (strlen($body) > (strrpos($body, "[/share]") + 8)) {
return(false);
return false;
}
$attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
// Skip if there is no shared message in there
if ($body == $attributes) {
return(false);
return false;
}
// If we don't do the complete check we quit here
if (!$complete) {
return true;
}
$guid = "";
preg_match("/guid='(.*?)'/ism", $attributes, $matches);
@ -3586,18 +3572,14 @@ class Diaspora
$guid = $matches[1];
}
if ($guid != "") {
$r = q(
"SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1",
dbesc($guid),
NETWORK_DFRN,
NETWORK_DIASPORA
);
if ($r) {
if (($guid != "") && $complete) {
$condition = ['guid' => $guid, 'network' => [NETWORK_DFRN, NETWORK_DIASPORA]];
$item = dba::selectFirst('item', ['contact-id'], $condition);
if (DBM::is_result($item)) {
$ret= [];
$ret["root_handle"] = self::handleFromContact($r[0]["contact-id"]);
$ret["root_handle"] = self::handleFromContact($item["contact-id"]);
$ret["root_guid"] = $guid;
return($ret);
return $ret;
}
}
@ -3614,28 +3596,22 @@ class Diaspora
$ret= [];
$ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile);
if (($ret["root_handle"] == $profile) || ($ret["root_handle"] == "")) {
return(false);
if ($profile != "") {
if (Contact::getIdForURL($profile)) {
$author = Contact::getDetailsByURL($profile);
$ret["root_handle"] = $author['addr'];
}
}
$link = "";
preg_match("/link='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "") {
$link = $matches[1];
if (!empty($guid)) {
$ret["root_guid"] = $guid;
}
preg_match('/link="(.*?)"/ism', $attributes, $matches);
if ($matches[1] != "") {
$link = $matches[1];
if (empty($ret) && !$complete) {
return true;
}
$ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link);
if (($ret["root_guid"] == $link) || (trim($ret["root_guid"]) == "")) {
return(false);
}
return($ret);
return $ret;
}
/**

View file

@ -0,0 +1,8 @@
#admin-users.adminpage { padding-left:0; padding-right: 0;}
#admin-users.adminpage > h1 { padding: 0 15px; }
#users img.icon, #deleted img.icon { height: 24px; }
.opened .caret { transform: rotate(180deg); }
tr.details td,
tr.details th
{ border-top: 0!important; }

View file

@ -20,6 +20,7 @@ $(function() {
}
});
function selectall(cls) {
$('.' + cls).prop('checked', true);
return false;
@ -28,4 +29,17 @@ $(function() {
$('.' + cls).prop('checked', false);
return false;
}
});
// Users
function confirm_delete(msg, uname){
return confirm(msg.format(uname));
}
function details(uid) {
$("#user-"+uid+"-detail").toggleClass("hidden");
$("#user-"+uid).toggleClass("opened");
return false;
}

View file

@ -0,0 +1,266 @@
<script type="text/javascript" src="view/theme/frio/js/mod_admin.js"></script>
<link rel="stylesheet" href="view/theme/frio/css/mod_admin.css" type="text/css" media="screen"/>
<div id="admin-users" class="adminpage generic-page-wrapper">
<h1>{{$title}} - {{$page}}</h1>
<form action="{{$baseurl}}/admin/users" method="post">
<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
<!--
**
*
* PENDING Users table
*
**
-->
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">{{$h_pending}}</h3></div>
{{if $pending}}
<table id="pending" class="table table-hover">
<thead>
<tr>
<th></th>
{{foreach $th_pending as $th}}<th>{{$th}}</th>{{/foreach}}
<th></th>
</tr>
</thead>
<tbody>
{{foreach $pending as $u}}
<tr>
<td><input type="checkbox" class="pending_ckbx" id="id_pending_{{$u.hash}}" name="pending[]" value="{{$u.hash}}" /></td>
<td>{{$u.created}}</td>
<td>{{$u.name}}</td>
<td>{{$u.email}}</td>
<td>
<a href="{{$baseurl}}/regmod/allow/{{$u.hash}}" title="{{$approve}}"><i class="fa fa-thumbs-up" aria-hidden="true"></i></a>
<a href="{{$baseurl}}/regmod/deny/{{$u.hash}}" title="{{$deny}}"><i class="fa fa-thumbs-down" aria-hidden="true"></i></a>
</td>
</tr>
<tr class="details">
<td></td>
<th>{{$pendingnotetext}}</th>
<td colspan="4">{{$u.note}}</td>
</tr>
{{/foreach}}
</tbody>
</table>
<div class="panel-footer">
<div class="row">
<div class="col-xs-3">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default selectall" data-select-all="pending_ckbx"><i class="fa fa-check-square-o" aria-hidden="true"></i></button>
<button type="button" class="btn btn-default selectnone" data-select-none="pending_ckbx"><i class="fa fa-square-o" aria-hidden="true"></i></button>
</div>
</div>
<div class="col-xs-9">
<button type="submit" name="page_users_deny" class="btn btn-primary"><i class="fa fa-thumbs-down" aria-hidden="true"></i> {{$deny}}</button>
<button type="submit" name="page_users_approve" class="btn btn-warinig"><i class="fa fa-thumbs-up" aria-hidden="true"></i> {{$approve}}</button>
</div>
</div>
</div>
{{else}}
<div class="panel-body text-center text-muted">{{$no_pending}}</div>
{{/if}}
</div>
<!--
**
*
* USERS Table
*
**
-->
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">{{$h_users}}</h3></div>
{{if $users}}
<table id="users" class="table table-hover">
<thead>
<tr>
<th></th>
<th></th>
{{foreach $th_users as $k=>$th}}
{{if $k < 2 || $order_users == $th.1 || ($k==5 && !in_array($order_users,[$th_users.2.1, $th_users.3.1, $th_users.4.1])) }}
<th>
<a href="{{$baseurl}}/admin/users/?o={{if $order_direction_users == "+"}}-{{/if}}{{$th.1}}">
{{if $order_users == $th.1}}
{{if $order_direction_users == "+"}}
&#8595;
{{else}}
&#8593;
{{/if}}
{{else}}
&#8597;
{{/if}}
{{$th.0}}</a>
</th>
{{/if}}
{{/foreach}}
<th></th>
</tr>
</thead>
<tbody>
{{foreach $users as $u}}
<tr id="user-{{$u.uid}}">
<td>
{{if $u.is_deletable}}
<input type="checkbox" class="users_ckbx" id="id_user_{{$u.uid}}" name="user[]" value="{{$u.uid}}"/>
{{else}}
&nbsp;
{{/if}}
</td>
<td><img class="icon" src="{{$u.micro}}" title="{{$u.nickname}}"></td>
<td><a href="{{$u.url}}" title="{{$u.nickname}}"> {{$u.name}}</a></td>
<td>{{$u.email}}</td>
{{if $order_users == $th_users.2.1}}
<td>{{$u.register_date}}</td>
{{/if}}
{{if $order_users == $th_users.3.1}}
<td>{{$u.login_date}}</td>
{{/if}}
{{if $order_users == $th_users.4.1}}
<td>{{$u.lastitem_date}}</td>
{{/if}}
{{if !in_array($order_users,[$th_users.2.1, $th_users.3.1, $th_users.4.1]) }}
<td>{{$u.page_flags}} {{if $u.is_admin}}({{$siteadmin}}){{/if}} {{if $u.account_expired}}({{$accountexpired}}){{/if}}</td>
{{/if}}
<td class="text-right">
<button type="button" class="btn-link" onclick="return details({{$u.uid}})"><span class="caret"></span></button>
</td>
</tr>
<tr id="user-{{$u.uid}}-detail" class="hidden details">
<td>&nbsp;</td>
<td colspan="4">
{{if $order_users != $th_users.2.1}}
<p><a href="{{$baseurl}}/admin/users/?o={{if $order_direction_users == "+"}}-{{/if}}{{$th_users.2.1}}">
&#8597; {{$th_users.2.0}}</a> : {{$u.register_date}}</p>
{{/if}}
{{if $order_users != $th_users.3.1}}
<p><a href="{{$baseurl}}/admin/users/?o={{if $order_direction_users == "+"}}-{{/if}}{{$th_users.3.1}}">
&#8597; {{$th_users.3.0}}</a> : {{$u.login_date}}</p>
{{/if}}
{{if $order_users != $th_users.4.1}}
<p><a href="{{$baseurl}}/admin/users/?o={{if $order_direction_users == "+"}}-{{/if}}{{$th_users.4.1}}">
&#8597; {{$th_users.4.0}}</a> : {{$u.lastitem_date}}</p>
{{/if}}
{{if in_array($order_users,[$th_users.2.1, $th_users.3.1, $th_users.4.1]) }}
<p><a href="{{$baseurl}}/admin/users/?o={{if $order_direction_users == "+"}}-{{/if}}{{$th_users.5.1}}">
&#8597; {{$th_users.5.0}}</a> : {{$u.page_flags}} {{if $u.is_admin}}({{$siteadmin}}){{/if}} {{if $u.account_expired}}({{$accountexpired}}){{/if}}</p>
{{/if}}
</td>
<td class="text-right">
{{if $u.is_deletable}}
<a href="{{$baseurl}}/admin/users/block/{{$u.uid}}?t={{$form_security_token}}" title="{{if $u.blocked}}{{$unblock}}{{else}}{{$block}}{{/if}}">
{{if $u.blocked==0}}
<i class="fa fa-ban" aria-hidden="true"></i>
{{else}}
<i class="fa fa-circle-o" aria-hidden="true"></i>
{{/if}}
</a>
<a href="{{$baseurl}}/admin/users/delete/{{$u.uid}}?t={{$form_security_token}}" title="{{$delete}}" onclick="return confirm_delete('{{$confirm_delete}}','{{$u.name}}')"><i class="fa fa-trash" aria-hidden="true"></i></a>
{{else}}
&nbsp;
{{/if}}
</td>
</tr>
{{/foreach}}
</tbody>
</table>
<div class="panel-footer">
<div class="row">
<div class="col-xs-3">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default selectall" data-select-all="users_ckbx"><i class="fa fa-check-square-o" aria-hidden="true"></i></button>
<button type="button" class="btn btn-default selectnone" data-select-none="users_ckbx"><i class="fa fa-square-o" aria-hidden="true"></i></button>
</div>
</div>
<div class="col-xs-9 text-right">
<button type="submit" name="page_users_block" class="btn btn-warning"> <i class="fa fa-ban" aria-hidden="true"></i> {{$block}} / <i class="fa fa-circle-o" aria-hidden="true"></i> {{$unblock}}</button>
<button type="submit" name="page_users_delete" class="btn btn-danger" onclick="return confirm_delete('{{$confirm_delete_multi}}')"><i class="fa fa-trash" aria-hidden="true"></i> {{$delete}}</button>
</div>
</div>
</div>
{{else}}
<div class="panel-body text-center bg-danger">NO USERS?!?</div>
{{/if}}
</div>
</form>
<!--
**
*
* DELETED Users table
*
**
-->
{{if $deleted}}
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">{{$h_deleted}}</h3></div>
<table id="deleted" class="table table-hover">
<thead>
<tr>
<th></th>
{{foreach $th_deleted as $k=>$th}}
{{if in_array($k,[0,1,5])}}
<th>{{$th}}</th>
{{/if}}
{{/foreach}}
</tr>
</thead>
<tbody>
{{foreach $deleted as $u}}
<tr>
<td><img class="icon" src="{{$u.micro}}" title="{{$u.nickname}}"></td>
<td><a href="{{$u.url}}" title="{{$u.nickname}}" >{{$u.name}}</a></td>
<td>{{$u.email}}</td>
<td>{{$u.deleted}}</td>
</tr>
{{/foreach}}
</tbody>
</table>
</div>
{{/if}}
<!--
**
*
* NEW USER Form
*
**
-->
<form action="{{$baseurl}}/admin/users" method="post">
<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">{{$h_newuser}}</h3></div>
<div class="panel-body">
{{include file="field_input.tpl" field=$newusername}}
{{include file="field_input.tpl" field=$newusernickname}}
{{include file="field_input.tpl" field=$newuseremail}}
</div>
<div class="panel-footer text-right">
<button type="submit" class="btn btn-primary">{{$submit}}</button>
</form>
</div>
</form>
</div>